Android自定義View實(shí)現(xiàn)拖動(dòng)自動(dòng)吸邊效果
本文實(shí)例為大家分享了Android自定義View實(shí)現(xiàn)拖動(dòng)自動(dòng)吸邊的具體代碼,供大家參考,具體內(nèi)容如下
自定義View,一是為了滿足設(shè)計(jì)需求,二是開發(fā)者進(jìn)階的標(biāo)志之一。隨心所欲就是我等奮斗的目標(biāo)?。?!
效果

實(shí)現(xiàn)邏輯
明確需求
1、實(shí)現(xiàn)控件跟隨手指拖動(dòng)
2、實(shí)現(xiàn)控件自動(dòng)貼邊
整理思路
1、既然要實(shí)現(xiàn)控件拖動(dòng),那么就離不開onTouchEvent()這個(gè)方法,需要監(jiān)聽里面的按下和滑動(dòng)事件。
2、 要實(shí)現(xiàn)自動(dòng)貼邊,需要監(jiān)聽onTouchEvent()中手指離開屏幕事件。對于貼邊的過程,我們用屬性動(dòng)畫來解決。
3、事件的沖突問題也需要考慮,拖動(dòng)、點(diǎn)擊關(guān)系到了事件的攔截。
動(dòng)手實(shí)現(xiàn)
在需求明確、思路清晰的情況下就要開始動(dòng)手實(shí)現(xiàn)(需要了解自定義View的一些基礎(chǔ)API),下面代碼中注釋寫的基本都差不多,很好理解。歡迎指出討論?。。?/p>
完整代碼
Kotlin
class AttachButton: View {
? ? private var mLastRawX: Float = 0F
? ? private var mLastRawY: Float = 0F
? ? private var isDrug = false
? ? private var mRootMeasuredWidth = 0
? ? private var mRootMeasuredHeight = 0
? ? private var mRootTopY = 0
? ? private var customIsAttach = false
? ? private var customIsDrag = false
? ? constructor(context: Context) : this(context, null)
? ? constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
? ? constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
? ? ? ? context,
? ? ? ? attrs,
? ? ? ? defStyleAttr
? ? ) {
? ? ? ? isClickable = true
? ? ? ? initAttrs(context, attrs)
? ? }
? ? private fun initAttrs(context: Context, attrs: AttributeSet?) {
? ? ? ? attrs?.let {
? ? ? ? ? ? val mTypedAttay = context.obtainStyledAttributes(it, R.styleable.AttachButton)
? ? ? ? ? ? customIsAttach =
? ? ? ? ? ? ? ? mTypedAttay.getBoolean(R.styleable.AttachButton_customIsAttach, true)
? ? ? ? ? ? customIsDrag =
? ? ? ? ? ? ? ? mTypedAttay.getBoolean(R.styleable.AttachButton_customIsDrag, true)
? ? ? ? ? ? mTypedAttay.recycle()
? ? ? ? }
? ? }
? ? override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
? ? ? ? super.dispatchTouchEvent(event)
? ? ? ? return true
? ? }
? ? override fun onTouchEvent(event: MotionEvent?): Boolean {
? ? ? ? event?.let {
? ? ? ? ? ? //判斷是否需要滑動(dòng)
? ? ? ? ? ? if (customIsDrag) {
? ? ? ? ? ? ? ? //當(dāng)前手指的坐標(biāo)
? ? ? ? ? ? ? ? val mRawX = it.rawX
? ? ? ? ? ? ? ? val mRawY = it.rawY
? ? ? ? ? ? ? ? when (it.action) {
? ? ? ? ? ? ? ? ? ? MotionEvent.ACTION_DOWN -> {//手指按下
? ? ? ? ? ? ? ? ? ? ? ? isDrug = false
? ? ? ? ? ? ? ? ? ? ? ? //記錄按下的位置
? ? ? ? ? ? ? ? ? ? ? ? mLastRawX = mRawX
? ? ? ? ? ? ? ? ? ? ? ? mLastRawY = mRawY
? ? ? ? ? ? ? ? ? ? ? ? if (parent is ViewGroup) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? val mViewGroup = parent as ViewGroup
? ? ? ? ? ? ? ? ? ? ? ? ? ? val location = IntArray(2)
? ? ? ? ? ? ? ? ? ? ? ? ? ? mViewGroup.getLocationInWindow(location)
? ? ? ? ? ? ? ? ? ? ? ? ? ? //獲取父布局的高度
? ? ? ? ? ? ? ? ? ? ? ? ? ? mRootMeasuredHeight = mViewGroup.measuredHeight
? ? ? ? ? ? ? ? ? ? ? ? ? ? mRootMeasuredWidth = mViewGroup.measuredWidth
? ? ? ? ? ? ? ? ? ? ? ? ? ? //獲取父布局頂點(diǎn)的坐標(biāo)
? ? ? ? ? ? ? ? ? ? ? ? ? ? mRootTopY = location[1]
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? MotionEvent.ACTION_MOVE -> {//手指滑動(dòng)
? ? ? ? ? ? ? ? ? ? ? ? if (mRawX >= 0 && mRawX <= mRootMeasuredWidth && mRawY >= mRootTopY && mRawY <= (mRootMeasuredHeight + mRootTopY)) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? //手指X軸滑動(dòng)距離
? ? ? ? ? ? ? ? ? ? ? ? ? ? val differenceValueX: Float = mRawX - mLastRawX
? ? ? ? ? ? ? ? ? ? ? ? ? ? //手指Y軸滑動(dòng)距離
? ? ? ? ? ? ? ? ? ? ? ? ? ? val differenceValueY: Float = mRawY - mLastRawY
? ? ? ? ? ? ? ? ? ? ? ? ? ? //判斷是否為拖動(dòng)操作
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (!isDrug) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? isDrug =
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? sqrt(((differenceValueX * differenceValueX) + (differenceValueY * differenceValueY)).toDouble()) >= 2
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? //獲取手指按下的距離與控件本身X軸的距離
? ? ? ? ? ? ? ? ? ? ? ? ? ? val ownX = x
? ? ? ? ? ? ? ? ? ? ? ? ? ? //獲取手指按下的距離與控件本身Y軸的距離
? ? ? ? ? ? ? ? ? ? ? ? ? ? val ownY = y
? ? ? ? ? ? ? ? ? ? ? ? ? ? //理論中X軸拖動(dòng)的距離
? ? ? ? ? ? ? ? ? ? ? ? ? ? var endX: Float = ownX + differenceValueX
? ? ? ? ? ? ? ? ? ? ? ? ? ? //理論中Y軸拖動(dòng)的距離
? ? ? ? ? ? ? ? ? ? ? ? ? ? var endY: Float = ownY + differenceValueY
? ? ? ? ? ? ? ? ? ? ? ? ? ? //X軸可以拖動(dòng)的最大距離
? ? ? ? ? ? ? ? ? ? ? ? ? ? val maxX: Float = mRootMeasuredWidth - width.toFloat()
? ? ? ? ? ? ? ? ? ? ? ? ? ? //Y軸可以拖動(dòng)的最大距離
? ? ? ? ? ? ? ? ? ? ? ? ? ? val maxY: Float = mRootMeasuredHeight - height.toFloat()
? ? ? ? ? ? ? ? ? ? ? ? ? ? //X軸邊界限制
? ? ? ? ? ? ? ? ? ? ? ? ? ? endX = if (endX < 0) 0F else (if (endX > maxX) maxX else endX)
? ? ? ? ? ? ? ? ? ? ? ? ? ? //Y軸邊界限制
? ? ? ? ? ? ? ? ? ? ? ? ? ? endY = if (endY < 0) 0F else (if (endY > maxY) maxY else endY)
? ? ? ? ? ? ? ? ? ? ? ? ? ? //開始移動(dòng)
? ? ? ? ? ? ? ? ? ? ? ? ? ? x = endX
? ? ? ? ? ? ? ? ? ? ? ? ? ? y = endY
? ? ? ? ? ? ? ? ? ? ? ? ? ? //記錄位置
? ? ? ? ? ? ? ? ? ? ? ? ? ? mLastRawX = mRawX
? ? ? ? ? ? ? ? ? ? ? ? ? ? mLastRawY = mRawY
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? MotionEvent.ACTION_UP -> {//手指離開
? ? ? ? ? ? ? ? ? ? ? ? if (customIsAttach) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? //判斷是否為點(diǎn)擊事件
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (isDrug) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? val center = mRootMeasuredWidth / 2
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //自動(dòng)貼邊
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (mLastRawX <= center) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //向左貼邊
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? animate()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .setInterpolator(BounceInterpolator())
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .setDuration(500)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .x(0F)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .start()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //向右貼邊
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? animate()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .setInterpolator(BounceInterpolator())
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .setDuration(500)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .x(mRootMeasuredWidth - width.toFloat())
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .start()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? //是否攔截事件
? ? ? ? return if (isDrug) isDrug else super.onTouchEvent(event)
? ? }
}Java
/**
?* 自定義View實(shí)現(xiàn)拖動(dòng)并自動(dòng)吸邊效果
?* <p>
?* 處理滑動(dòng)和貼邊 {@link #onTouchEvent(MotionEvent)}
?* 處理事件分發(fā) {@link #dispatchTouchEvent(MotionEvent)}
?* </p>
?*
?* @attr customIsAttach ?//是否需要自動(dòng)吸邊
?* @attr customIsDrag ? ?//是否可拖曳
?*/
public class AttachButton extends View {
? ? private float mLastRawX;
? ? private float mLastRawY;
? ? private final String TAG = "AttachButton";
? ? private boolean isDrug = false;
? ? private int mRootMeasuredWidth = 0;
? ? private int mRootMeasuredHeight = 0;
? ? private int mRootTopY = 0;
? ? private boolean customIsAttach;
? ? private boolean customIsDrag;
? ? public AttachButton(Context context) {
? ? ? ? this(context, null);
? ? }
? ? public AttachButton(Context context, @Nullable AttributeSet attrs) {
? ? ? ? this(context, attrs, 0);
? ? }
? ? public AttachButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
? ? ? ? super(context, attrs, defStyleAttr);
? ? ? ? setClickable(true);
? ? ? ? initAttrs(context, attrs);
? ? }
? ? /**
? ? ?* 初始化自定義屬性
? ? ?*/
? ? private void initAttrs(Context context, AttributeSet attrs) {
? ? ? ? TypedArray mTypedAttay = context.obtainStyledAttributes(attrs, R.styleable.AttachButton);
? ? ? ? customIsAttach = mTypedAttay.getBoolean(R.styleable.AttachButton_customIsAttach, true);
? ? ? ? customIsDrag = mTypedAttay.getBoolean(R.styleable.AttachButton_customIsDrag, true);
? ? ? ? mTypedAttay.recycle();
? ? }
? ? @Override
? ? public boolean dispatchTouchEvent(MotionEvent event) {
? ? ? ? super.dispatchTouchEvent(event);
? ? ? ? return true;
? ? }
? ? @Override
? ? public boolean onTouchEvent(MotionEvent ev) {
? ? ? ? //判斷是否需要滑動(dòng)
? ? ? ? if (customIsDrag) {
? ? ? ? ? ? //當(dāng)前手指的坐標(biāo)
? ? ? ? ? ? float mRawX = ev.getRawX();
? ? ? ? ? ? float mRawY = ev.getRawY();
? ? ? ? ? ? switch (ev.getAction()) {
? ? ? ? ? ? ? ? case MotionEvent.ACTION_DOWN://手指按下
? ? ? ? ? ? ? ? ? ? isDrug = false;
? ? ? ? ? ? ? ? ? ? //記錄按下的位置
? ? ? ? ? ? ? ? ? ? mLastRawX = mRawX;
? ? ? ? ? ? ? ? ? ? mLastRawY = mRawY;
? ? ? ? ? ? ? ? ? ? ViewGroup mViewGroup = (ViewGroup) getParent();
? ? ? ? ? ? ? ? ? ? if (mViewGroup != null) {
? ? ? ? ? ? ? ? ? ? ? ? int[] location = new int[2];
? ? ? ? ? ? ? ? ? ? ? ? mViewGroup.getLocationInWindow(location);
? ? ? ? ? ? ? ? ? ? ? ? //獲取父布局的高度
? ? ? ? ? ? ? ? ? ? ? ? mRootMeasuredHeight = mViewGroup.getMeasuredHeight();
? ? ? ? ? ? ? ? ? ? ? ? mRootMeasuredWidth = mViewGroup.getMeasuredWidth();
? ? ? ? ? ? ? ? ? ? ? ? //獲取父布局頂點(diǎn)的坐標(biāo)
? ? ? ? ? ? ? ? ? ? ? ? mRootTopY = location[1];
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? case MotionEvent.ACTION_MOVE://手指滑動(dòng)
? ? ? ? ? ? ? ? ? ? if (mRawX >= 0 && mRawX <= mRootMeasuredWidth && mRawY >= mRootTopY && mRawY <= (mRootMeasuredHeight + mRootTopY)) {
? ? ? ? ? ? ? ? ? ? ? ? //手指X軸滑動(dòng)距離
? ? ? ? ? ? ? ? ? ? ? ? float differenceValueX = mRawX - mLastRawX;
? ? ? ? ? ? ? ? ? ? ? ? //手指Y軸滑動(dòng)距離
? ? ? ? ? ? ? ? ? ? ? ? float differenceValueY = mRawY - mLastRawY;
? ? ? ? ? ? ? ? ? ? ? ? //判斷是否為拖動(dòng)操作
? ? ? ? ? ? ? ? ? ? ? ? if (!isDrug) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (Math.sqrt(differenceValueX * differenceValueX + differenceValueY * differenceValueY) < 2) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? isDrug = false;
? ? ? ? ? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? isDrug = true;
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? //獲取手指按下的距離與控件本身X軸的距離
? ? ? ? ? ? ? ? ? ? ? ? float ownX = getX();
? ? ? ? ? ? ? ? ? ? ? ? //獲取手指按下的距離與控件本身Y軸的距離
? ? ? ? ? ? ? ? ? ? ? ? float ownY = getY();
? ? ? ? ? ? ? ? ? ? ? ? //理論中X軸拖動(dòng)的距離
? ? ? ? ? ? ? ? ? ? ? ? float endX = ownX + differenceValueX;
? ? ? ? ? ? ? ? ? ? ? ? //理論中Y軸拖動(dòng)的距離
? ? ? ? ? ? ? ? ? ? ? ? float endY = ownY + differenceValueY;
? ? ? ? ? ? ? ? ? ? ? ? //X軸可以拖動(dòng)的最大距離
? ? ? ? ? ? ? ? ? ? ? ? float maxX = mRootMeasuredWidth - getWidth();
? ? ? ? ? ? ? ? ? ? ? ? //Y軸可以拖動(dòng)的最大距離
? ? ? ? ? ? ? ? ? ? ? ? float maxY = mRootMeasuredHeight - getHeight();
? ? ? ? ? ? ? ? ? ? ? ? //X軸邊界限制
? ? ? ? ? ? ? ? ? ? ? ? endX = endX < 0 ? 0 : endX > maxX ? maxX : endX;
? ? ? ? ? ? ? ? ? ? ? ? //Y軸邊界限制
? ? ? ? ? ? ? ? ? ? ? ? endY = endY < 0 ? 0 : endY > maxY ? maxY : endY;
? ? ? ? ? ? ? ? ? ? ? ? //開始移動(dòng)
? ? ? ? ? ? ? ? ? ? ? ? setX(endX);
? ? ? ? ? ? ? ? ? ? ? ? setY(endY);
? ? ? ? ? ? ? ? ? ? ? ? //記錄位置
? ? ? ? ? ? ? ? ? ? ? ? mLastRawX = mRawX;
? ? ? ? ? ? ? ? ? ? ? ? mLastRawY = mRawY;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? case MotionEvent.ACTION_UP://手指離開
? ? ? ? ? ? ? ? ? ? //根據(jù)自定義屬性判斷是否需要貼邊
? ? ? ? ? ? ? ? ? ? if (customIsAttach) {
? ? ? ? ? ? ? ? ? ? ? ? //判斷是否為點(diǎn)擊事件
? ? ? ? ? ? ? ? ? ? ? ? if (isDrug) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? float center = mRootMeasuredWidth / 2;
? ? ? ? ? ? ? ? ? ? ? ? ? ? //自動(dòng)貼邊
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (mLastRawX <= center) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //向左貼邊
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AttachButton.this.animate()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .setInterpolator(new BounceInterpolator())
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .setDuration(500)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .x(0)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .start();
? ? ? ? ? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //向右貼邊
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AttachButton.this.animate()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .setInterpolator(new BounceInterpolator())
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .setDuration(500)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .x(mRootMeasuredWidth - getWidth())
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .start();
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? //是否攔截事件
? ? ? ? return isDrug ? isDrug : super.onTouchEvent(ev);
? ? }
}自定義屬性
<declare-styleable name="AttachButton"> ? ? ? ? <!--是否需要自動(dòng)吸邊--> ? ? ? ? <attr name="customIsAttach" format="boolean" /> ? ? ? ? <!--是否可拖曳--> ? ? ? ? <attr name="customIsDrag" format="boolean" /> </declare-styleable>
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實(shí)踐之帶加載效果的下拉刷新上拉加載更多
這篇文章主要給大家介紹了關(guān)于Android實(shí)踐之下拉刷新上拉加載更多的相關(guān)資料,實(shí)現(xiàn)的效果在現(xiàn)在的很多項(xiàng)目中都能用到,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12
Android使用API實(shí)現(xiàn)圖像扭曲效果示例
這篇文章主要介紹了Android使用API實(shí)現(xiàn)圖像扭曲效果,涉及Android坐標(biāo)運(yùn)算與圖形繪制相關(guān)操作技巧,需要的朋友可以參考下2017-08-08
Android 修改viewpage滑動(dòng)速度的實(shí)現(xiàn)代碼
由于Viewpager的滑動(dòng)速度是固定的,所以很頭疼,下面小編通過實(shí)例代碼給大家分享android 修改viewpage滑動(dòng)速度的方法,需要的朋友參考下吧2017-09-09
PopupWindow?RecyclerView實(shí)現(xiàn)下拉選擇Spinner示例解析
這篇文章主要介紹了PopupWindow?RecyclerView實(shí)現(xiàn)下拉選擇Spinner示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
Android編程實(shí)現(xiàn)基于BitMap獲得圖片像素?cái)?shù)據(jù)的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)基于BitMap獲得圖片像素?cái)?shù)據(jù)的方法,對比分析了兩種獲取圖片像素的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11

