Android自定義View實現拖動自動吸邊效果
本文實例為大傢分享瞭Android自定義View實現拖動自動吸邊的具體代碼,供大傢參考,具體內容如下
自定義View,一是為瞭滿足設計需求,二是開發者進階的標志之一。隨心所欲就是我等奮鬥的目標!!!
效果
實現邏輯
明確需求
1、實現控件跟隨手指拖動
2、實現控件自動貼邊
整理思路
1、既然要實現控件拖動,那麼就離不開onTouchEvent()
這個方法,需要監聽裡面的按下和滑動事件。
2、 要實現自動貼邊,需要監聽onTouchEvent()
中手指離開屏幕事件。對於貼邊的過程,我們用屬性動畫來解決。
3、事件的沖突問題也需要考慮,拖動、點擊關系到瞭事件的攔截。
動手實現
在需求明確、思路清晰的情況下就要開始動手實現(需要瞭解自定義View的一些基礎API),下面代碼中註釋寫的基本都差不多,很好理解。歡迎指出討論!!!
完整代碼
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 { //判斷是否需要滑動 if (customIsDrag) { //當前手指的坐標 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 //獲取父佈局頂點的坐標 mRootTopY = location[1] } } MotionEvent.ACTION_MOVE -> {//手指滑動 if (mRawX >= 0 && mRawX <= mRootMeasuredWidth && mRawY >= mRootTopY && mRawY <= (mRootMeasuredHeight + mRootTopY)) { //手指X軸滑動距離 val differenceValueX: Float = mRawX - mLastRawX //手指Y軸滑動距離 val differenceValueY: Float = mRawY - mLastRawY //判斷是否為拖動操作 if (!isDrug) { isDrug = sqrt(((differenceValueX * differenceValueX) + (differenceValueY * differenceValueY)).toDouble()) >= 2 } //獲取手指按下的距離與控件本身X軸的距離 val ownX = x //獲取手指按下的距離與控件本身Y軸的距離 val ownY = y //理論中X軸拖動的距離 var endX: Float = ownX + differenceValueX //理論中Y軸拖動的距離 var endY: Float = ownY + differenceValueY //X軸可以拖動的最大距離 val maxX: Float = mRootMeasuredWidth - width.toFloat() //Y軸可以拖動的最大距離 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) //開始移動 x = endX y = endY //記錄位置 mLastRawX = mRawX mLastRawY = mRawY } } MotionEvent.ACTION_UP -> {//手指離開 if (customIsAttach) { //判斷是否為點擊事件 if (isDrug) { val center = mRootMeasuredWidth / 2 //自動貼邊 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實現拖動並自動吸邊效果 * <p> * 處理滑動和貼邊 {@link #onTouchEvent(MotionEvent)} * 處理事件分發 {@link #dispatchTouchEvent(MotionEvent)} * </p> * * @attr customIsAttach //是否需要自動吸邊 * @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) { //判斷是否需要滑動 if (customIsDrag) { //當前手指的坐標 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(); //獲取父佈局頂點的坐標 mRootTopY = location[1]; } break; case MotionEvent.ACTION_MOVE://手指滑動 if (mRawX >= 0 && mRawX <= mRootMeasuredWidth && mRawY >= mRootTopY && mRawY <= (mRootMeasuredHeight + mRootTopY)) { //手指X軸滑動距離 float differenceValueX = mRawX - mLastRawX; //手指Y軸滑動距離 float differenceValueY = mRawY - mLastRawY; //判斷是否為拖動操作 if (!isDrug) { if (Math.sqrt(differenceValueX * differenceValueX + differenceValueY * differenceValueY) < 2) { isDrug = false; } else { isDrug = true; } } //獲取手指按下的距離與控件本身X軸的距離 float ownX = getX(); //獲取手指按下的距離與控件本身Y軸的距離 float ownY = getY(); //理論中X軸拖動的距離 float endX = ownX + differenceValueX; //理論中Y軸拖動的距離 float endY = ownY + differenceValueY; //X軸可以拖動的最大距離 float maxX = mRootMeasuredWidth - getWidth(); //Y軸可以拖動的最大距離 float maxY = mRootMeasuredHeight - getHeight(); //X軸邊界限制 endX = endX < 0 ? 0 : endX > maxX ? maxX : endX; //Y軸邊界限制 endY = endY < 0 ? 0 : endY > maxY ? maxY : endY; //開始移動 setX(endX); setY(endY); //記錄位置 mLastRawX = mRawX; mLastRawY = mRawY; } break; case MotionEvent.ACTION_UP://手指離開 //根據自定義屬性判斷是否需要貼邊 if (customIsAttach) { //判斷是否為點擊事件 if (isDrug) { float center = mRootMeasuredWidth / 2; //自動貼邊 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"> <!--是否需要自動吸邊--> <attr name="customIsAttach" format="boolean" /> <!--是否可拖曳--> <attr name="customIsDrag" format="boolean" /> </declare-styleable>
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Android自定義view貝塞爾曲線
- ViewPager2滑動沖突解決方案
- Android 控件自動貼邊實現實例詳解
- android控件實現單擊拖動效果
- Android自定義View繪制貝塞爾曲線的方法