Android custom viewGroup realizes the second floor of Taobao and handles multi finger touch events

  • preface

    Strolling around Taobao without doing anything, I found that there was a second floor of Taobao on the home page. As shown below:

    The UI effect is very interesting, so I plan to roll one by hand.

  • thinking

    The ScrollView is the first thing that comes to mind when scrolling the view, and then handle his gesture sliding event to dynamically change the position of the view to achieve the effect.

  • realization

    New MScrollView inherits ScrollView

    public class MScrollView extends ScrollView {
    
    public MScrollView(Context context) {
        super(context);
    }
    
    public MScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    }
    

    UI layout file:

        <com.dullyoung.myapplication.view.MScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@mipmap/ic_launcher"
        android:fillViewport="true"
        android:overScrollMode="never">
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="3000dp"
            android:background="@color/purple_200" />
    

    If we want to control the position of the view, we can obtain the sub view after the viewgroup drawing is completed. In order to facilitate the display of scrolling status, I use TextView internally to facilitate the display of data. In fact, any view can be used

    private TextView mChildView;
      @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (mChildView == null && getChildCount() > 0) {
            mChildView = (TextView) getChildAt(0);
        }
    }
    

Then process OnTouchEvent

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mChildView != null) {
            resolveTouchEvent(ev);
        }
        return super.onTouchEvent(ev);
    }

resolveTouchEvent is to handle touch events. To achieve the effect of the second floor of Taobao, you only need to handle motionevnet ACTION_ Just move. The idea is to calculate the sliding offset in sliding, and then call view.. Layout redefines the location of the view. Visually, it looks like the view has been pulled down

//Last finger slide position y
int y;
//Record the initial position of the view
private final Rect mRect = new Rect();
 case MotionEvent.ACTION_MOVE:
              
                int cY = (int) ev.getY();

                if (mRect.isEmpty()) {//Record the initial position of the view
                    mRect.set(mChildView.getLeft(), mChildView.getTop(),
                            mChildView.getRight(), mChildView.getBottom());
                }
                //dy is the offset, and the view position will be changed according to the offset
                int dy = (cY - y);
                //The first start drop-down starts at 0

                //The bottom is not allowed to slide
                if (mChildView.getTop() < 0 || (mChildView.getTop() == 0 && dy < 0)) {
                    return;
                }
                y = cY;
                mChildView.layout(mChildView.getLeft(), mChildView.getTop() + 2 * dy / 3,
                        mChildView.getRight(), mChildView.getBottom() + 2 * dy / 3);

                if (dy >= 0 && mChildView.getTop() > getHeight() / 4) {
                    couldCallBack = true;
                    mChildView.setText("Let go and open a new interface");
                } else {
                    couldCallBack = false;
                    mChildView.setText("top offset:" + mChildView.getTop());
                }

                break;
                //Then handle the callback event when lifting 
case MotionEvent.ACTION_UP:
                if (couldCallBack) {
                    Toast.makeText(getContext(), "Callback", Toast.LENGTH_LONG).show();
                    couldCallBack = false;
                }
                if (!mRect.isEmpty()) {
                    resetPosition();
                }
                break;

	//After lifting, judge whether the rolling distance requirements are met according to the sliding offset, and then return to the original position
    private void resetPosition() {
        Animation animation = new TranslateAnimation(0, 0, mChildView.getTop(),
                mRect.top);
        animation.setDuration(200);
        animation.setFillAfter(true);
        mChildView.startAnimation(animation);
        mChildView.layout(mRect.left, mRect.top, mRect.right, mRect.bottom);
        mRect.setEmpty();
        mChildView.setText("Drop down to open a new interface");
        couldCallBack = false;
    }

That's it. Finger drop mchildview Gettop() > getheight() / 4 when the drop-down height of the view exceeds one fourth of the screen height, the event will be recalled. That's it!

  • bug

    However, in the process of happy play, it is found that when multi finger manipulation, the view will jump around. So. Let's have more finger touch...

  • Multi finger touch

    Introduction: the difference between multi finger touch and single finger touch is that one finger obtains motionevent Getaction is OK, but mostly motionevent getActionMasked().

    In the early days, the old Android api did not support multi touch or motionevent when only two points were supported ACTION_ POINTER_ 1_ DOWN,MotionEvent.ACTION_POINTER_2_DOWN,MotionEvent.ACTION_POINTER_3_DOWN and corresponding lifting events.

    But the above has been abandoned with more and more supporting contacts, so we need to use two new events motionevent ACTION_ POINTER_ Down and motionevent ACTION_ POINTER_ Up, the two states are triggered when a finger is pressed on the screen, and then a finger is pressed or raised.
    Therefore, a new concept of marking fingers is introduced. PointerId and PointerIndex

  • PointerId PointerIndex and ActionIndex differences and uses

    • The number of PointerId finger will not change with the lifting of other fingers. Press to confirm that it does not change.
    • PointerIndex finger index. It will change as other fingers lift up, sorting from 0.
    • ActionIndex the index of the currently active finger.
  • Problem solving

    The previous problem of view jumping when multiple fingers are pressed is that the final position Y of the last scroll when the novice finger is pressed and the current trigger ACTION_MOVE's finger is uncertain. Therefore, we only need to use the PointerID to always determine that we can manipulate the view with the same finger, so there will be no problem.
    When only one finger is pressed, the PointerId will take this one, and the Y coordinate of the last scrolled position should be changed immediately, otherwise there will be random jumps

    //global variable
      private int pointerId;
    
    
    //Gets the index of the currently active finger
    int actionIndex = ev.getActionIndex();
     case MotionEvent.ACTION_DOWN:
                //Press for the first time to take the 0
                pointerId = ev.getPointerId(0);
                y = (int) ev.getY(actionIndex);
                break;
    

If a new finger is pressed, the new finger shall prevail

case MotionEvent.ACTION_POINTER_DOWN:
                pointerId = ev.getPointerId(actionIndex);
                y = (int) ev.getY(actionIndex);
                break;

When a finger is raised, judge whether it is the current finger. If so, change a finger as the current finger

     case MotionEvent.ACTION_POINTER_UP:
                if (pointerId == ev.getPointerId(actionIndex)) {
                    int newPointIndex = actionIndex == 0 ? 1 : 0;
                    pointerId = ev.getPointerId(newPointIndex);
                    y = (int) ev.getY(newPointIndex);
                }
                break;

When this status is called back, it must be that two or more fingers are on the screen, and one of them will call back when it is raised. So when taking the new finger index here, use actionindex = = 0? 1: 0. If the current release is the first one, take the second one. If it is not the first one, take the first one.

At the same time, action_ When move handles view scrolling, it also uses specific finger scrolling events.

case MotionEvent.ACTION_MOVE:
                int index2 = ev.findPointerIndex(pointerId);
                int cY = (int) ev.getY(index2);

That's it~
Look at the effect picture: you can know the sliding distance in real time

More than a quarter of the screen will recall the event

summary

Custom view is the difficulty of Android development, but it is not difficult as long as the principle is understood. Sometimes thinking is very important.
I first solved the problem of multi finger jumping, and I have been struggling with how to obtain the coordinates of the novice finger and how to assign the value of the new Y. I didn't think of this method of fixing fingers and monitoring a finger all the time.

When you see an interesting UI, you should think more, do more, write more and practice more~

Keywords: Java Android Design Pattern

Added by kam_uoc on Wed, 19 Jan 2022 18:16:17 +0200