Android自定義View實現(xiàn)拖動自動吸邊效果
本文實例為大家分享了Android自定義View實現(xiàn)拖動自動吸邊的具體代碼,供大家參考,具體內(nèi)容如下
自定義View,一是為了滿足設(shè)計需求,二是開發(fā)者進(jìn)階的標(biāo)志之一。隨心所欲就是我等奮斗的目標(biāo)?。?!
效果
實現(xiàn)邏輯
明確需求
1、實現(xiàn)控件跟隨手指拖動
2、實現(xiàn)控件自動貼邊
整理思路
1、既然要實現(xiàn)控件拖動,那么就離不開onTouchEvent()
這個方法,需要監(jiān)聽里面的按下和滑動事件。
2、 要實現(xiàn)自動貼邊,需要監(jiān)聽onTouchEvent()
中手指離開屏幕事件。對于貼邊的過程,我們用屬性動畫來解決。
3、事件的沖突問題也需要考慮,拖動、點擊關(guān)系到了事件的攔截。
動手實現(xiàn)
在需求明確、思路清晰的情況下就要開始動手實現(xiàn)(需要了解自定義View的一些基礎(chǔ)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) { ? ? ? ? ? ? ? ? //當(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 ? ? ? ? ? ? ? ? ? ? ? ? ? ? //獲取父布局頂點的坐標(biāo) ? ? ? ? ? ? ? ? ? ? ? ? ? ? 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實現(xiàn)拖動并自動吸邊效果 ?* <p> ?* 處理滑動和貼邊 {@link #onTouchEvent(MotionEvent)} ?* 處理事件分發(fā) {@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) { ? ? ? ? ? ? //當(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(); ? ? ? ? ? ? ? ? ? ? ? ? //獲取父布局頂點的坐標(biāo) ? ? ? ? ? ? ? ? ? ? ? ? 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://手指離開 ? ? ? ? ? ? ? ? ? ? //根據(jù)自定義屬性判斷是否需要貼邊 ? ? ? ? ? ? ? ? ? ? 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>
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android 修改viewpage滑動速度的實現(xiàn)代碼
由于Viewpager的滑動速度是固定的,所以很頭疼,下面小編通過實例代碼給大家分享android 修改viewpage滑動速度的方法,需要的朋友參考下吧2017-09-09PopupWindow?RecyclerView實現(xiàn)下拉選擇Spinner示例解析
這篇文章主要介紹了PopupWindow?RecyclerView實現(xiàn)下拉選擇Spinner示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07Android編程實現(xiàn)基于BitMap獲得圖片像素數(shù)據(jù)的方法
這篇文章主要介紹了Android編程實現(xiàn)基于BitMap獲得圖片像素數(shù)據(jù)的方法,對比分析了兩種獲取圖片像素的技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11