1. Foreword:
picture.png
Effect:
aaa.gif
2. Analysis
1. Idea
1. Since the control dragging is to be realized, the onTouchEvent() method is inseparable, and the press and slide events in it need to be monitored. 2. To realize automatic bordering, it is necessary to monitor the event that the finger leaves the screen in onTouchEvent(). For the process of bonding, we use attribute animation to solve it. 3. The conflict of events also needs to be considered. Dragging and clicking are related to the interception of events.
2. Key points
1. Pay attention to the response to the event 2. Calculation of sliding boundaries
3. Java complete code:
/** * Customize View to achieve dragging and automatic edge suction effect * <p> * Handle swipe and snap {@link #onTouchEvent(MotionEvent)} * Handle event dispatch {@link #dispatchTouchEvent(MotionEvent)} *</p> * * @attr customIsAttach //Whether automatic edge suction is required * @attr customIsDrag //Whether it can be dragged */ 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); } /** * Initialize custom properties */ 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) { //Determine whether to slide if (customIsDrag) { //The coordinates of the current finger float mRawX = ev. getRawX(); float mRawY = ev. getRawY(); switch (ev. getAction()) { case MotionEvent.ACTION_DOWN://finger press isDrug = false; //Record the pressed position mLastRawX = mRawX; mLastRawY = mRawY; ViewGroup mViewGroup = (ViewGroup) getParent(); if (mViewGroup != null) { int[] location = new int[2]; mViewGroup.getLocationInWindow(location); // Get the height of the parent layout mRootMeasuredHeight = mViewGroup.getMeasuredHeight(); mRootMeasuredWidth = mViewGroup.getMeasuredWidth(); / / Get the coordinates of the parent layout vertex mRootTopY = location[1]; } break; case MotionEvent.ACTION_MOVE://finger slide if (mRawX >= 0 & amp; & amp; mRawX <= mRootMeasuredWidth & amp; & amp; mRawY >= mRootTopY & amp; & amp; mRawY <= (mRootMeasuredHeight + mRootTopY)) { //finger X-axis sliding distance float differenceValueX = mRawX - mLastRawX; //Finger Y-axis sliding distance float differenceValueY = mRawY - mLastRawY; //Determine whether it is a drag operation if (!isDrug) { if (Math. sqrt(differenceValueX * differenceValueX + differenceValueY * differenceValueY) < 2) { isDrug = false; } else { isDrug = true; } } //Get the distance between the distance pressed by the finger and the X-axis of the control itself float ownX = getX(); //Get the distance between the distance pressed by the finger and the Y axis of the control itself float ownY = getY(); //The distance dragged by the X axis in theory float endX = ownX + differenceValueX; //The distance dragged by the Y axis in theory float endY = ownY + differenceValueY; //The maximum distance that the X axis can drag float maxX = mRootMeasuredWidth - getWidth(); //The maximum distance that the Y axis can drag float maxY = mRootMeasuredHeight - getHeight(); //X-axis boundary limit endX = endX < 0 ? 0 : endX > maxX ? maxX : endX; //Y-axis boundary limit endY = endY < 0 ? 0 : endY > maxY ? maxY : endY; // start moving setX(endX); setY(endY); //record location mLastRawX = mRawX; mLastRawY = mRawY; } break; case MotionEvent.ACTION_UP://finger leave //Determine whether we need to paste according to the custom attribute if (customIsAttach) { //Determine whether it is a click event if (isDrug) { float center = mRootMeasuredWidth / 2; //Automatic border if (mLastRawX <= center) { //paste to the left AttachButton. this. animate() .setInterpolator(new BounceInterpolator()) .setDuration(500) .x(0) .start(); } else { // stick to the right AttachButton. this. animate() .setInterpolator(new BounceInterpolator()) .setDuration(500) .x(mRootMeasuredWidth - getWidth()) .start(); } } } break; } } //Whether to intercept the event return isDrug ? isDrug : super.onTouchEvent(ev); } }
4. Kotlin complete code:
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 { //Determine whether to slide if (customIsDrag) { //The coordinates of the current finger val mRawX = it.rawX val mRawY = it.rawY when (it. action) { MotionEvent.ACTION_DOWN -> {//finger press isDrug = false //Record the pressed position mLastRawX = mRawX mLastRawY = mRawY if (parent is ViewGroup) { val mViewGroup = parent as ViewGroup val location = IntArray(2) mViewGroup. getLocationInWindow(location) // Get the height of the parent layout mRootMeasuredHeight = mViewGroup.measuredHeight mRootMeasuredWidth = mViewGroup.measuredWidth / / Get the coordinates of the parent layout vertex mRootTopY = location[1] } } MotionEvent.ACTION_MOVE -> {//finger slide if (mRawX >= 0 & amp; & amp; mRawX <= mRootMeasuredWidth & amp; & amp; mRawY >= mRootTopY & amp; & amp; mRawY <= (mRootMeasuredHeight + mRootTopY)) { //finger X-axis sliding distance val differenceValueX: Float = mRawX - mLastRawX //Finger Y-axis sliding distance val differenceValueY: Float = mRawY - mLastRawY //Determine whether it is a drag operation if (!isDrug) { isDrug = sqrt(((differenceValueX * differenceValueX) + (differenceValueY * differenceValueY)).toDouble()) >= 2 } //Get the distance between the distance pressed by the finger and the X-axis of the control itself val ownX = x //Get the distance between the distance pressed by the finger and the Y axis of the control itself val ownY = y //The distance dragged by the X axis in theory var endX: Float = ownX + differenceValueX //The distance dragged by the Y axis in theory var endY: Float = ownY + differenceValueY //The maximum distance that the X axis can drag val maxX: Float = mRootMeasuredWidth - width.toFloat() //The maximum distance that the Y axis can drag val maxY: Float = mRootMeasuredHeight - height.toFloat() //X-axis boundary limit endX = if (endX < 0) 0F else (if (endX > maxX) maxX else endX) //Y-axis boundary limit endY = if (endY < 0) 0F else (if (endY > maxY) maxY else endY) // start moving x = endX y = endY //record location mLastRawX = mRawX mLastRawY = mRawY } } MotionEvent.ACTION_UP -> {//finger leave if (customIsAttach) { //Determine whether it is a click event if (isDrug) { val center = mRootMeasuredWidth / 2 //Automatic border if (mLastRawX <= center) { //paste to the left animate() .setInterpolator(BounceInterpolator()) .setDuration(500) .x(0F) .start() } else { // stick to the right animate() .setInterpolator(BounceInterpolator()) .setDuration(500) .x(mRootMeasuredWidth - width.toFloat()) .start() } } } } } } } //Whether to intercept the event return if (isDrug) isDrug else super.onTouchEvent(event) } }
4. Simple drag
import android. content. Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; public class AdsorbentViews extends ImageView { private int maxWidth; private int maxHeight; private int viewWidth; private int viewHeight; private float downx; private float downy; private Context mContext; public CustomViews(Context context) { this(context, null); } public CustomViews(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomViews(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); DisplayMetrics outMetrics = new DisplayMetrics(); WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); windowManager.getDefaultDisplay().getRealMetrics(outMetrics); //screen width maxWidth = outMetrics. widthPixels; // the height of the screen maxHeight = outMetrics. heightPixels; /** * Width and height of the control */ viewWidth = canvas. getWidth(); viewHeight = canvas. getHeight(); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event. getAction()) { case MotionEvent. ACTION_DOWN: clearAnimation(); downx = event. getX(); downy = event. getY(); return true; case MotionEvent. ACTION_MOVE: float moveX = event.getRawX() - downx; float moveY = event.getRawY() - downy; moveX = moveX < 0 ? 0 : (moveX + viewWidth > maxWidth) ? (maxWidth - viewWidth) : moveX; moveY = moveY < 0 ? 0 : (moveY + viewHeight) > maxHeight ? (maxHeight - viewHeight) : moveY; this.setY(moveY); this.setX(moveX); return true; case MotionEvent. ACTION_UP: // do adsorption effect float centerX = getX() + viewWidth / 2; if (centerX > maxWidth/2){ // snap to the right animate().setInterpolator(new DecelerateInterpolator()) .setDuration(500) .x(maxWidth-viewWidth) .y(maxHeight-viewHeight) .start(); } else { animate().setInterpolator(new DecelerateInterpolator()) .setDuration(500) .x(0) .y(maxHeight-viewHeight) .start(); } return true; default: return super.onTouchEvent(event); } } }
Last
If you want to become an architect or want to break through the 20-30K salary range, then don’t be limited to coding and business, but you must be able to select models, expand, and improve programming thinking. In addition, a good career plan is also very important, and the habit of learning is very important, but the most important thing is to be able to persevere. Any plan that cannot be implemented consistently is empty talk.
If you have no direction, here I would like to share with you a set of “Advanced Notes on the Eight Major Modules of Android” written by the senior architect of Ali, to help you organize the messy, scattered and fragmented knowledge systematically, so that you can systematically and efficiently Master the various knowledge points of Android development.
Compared with the fragmented content we usually read, the knowledge points of this note are more systematic, easier to understand and remember, and are arranged strictly according to the knowledge system.
Full set of video materials:
1. Interview Collection
2. Collection of source code analysis
3. Collection of open source frameworks
Welcome everyone to support with one click and three links. If you need the information in the article, you can directly scan the CSDN official certification WeChat card at the end of the article to get it for free↓↓↓