blow your mind

The BYM series aims to share not only technology but also ideas, not just being a porter of code.

1. Background

The company’s business needs to make a search function imitated baidu, Baidu map’s search results list sliding effect is quite smooth, so I also want one. Then this article/funny

2

A. onTouchEvent and MotionEvent

OnTouchEvent is a familiar event that handles all gestures received by the View, along with dispatchTouchEvent and onInterceptTouchEvent.

  @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(ev);
    }
Copy the code

A MotionEvent is an event object generated when we click on the screen (with mouse, hand, pen, trackball) that contains absolute or relative motion. Common MovtionEvent actions are shown in the following table

Action note
MotionEvent.ACTION_MASK Action mask, used for multi-touch
MotionEvent.ACTION_DOWN Gestures are produced when the screen is pressed, and can also be used to detect the status of the button
MotionEvent.ACTION_UP Generated at the end of the gesture, which contains the final position
MotionEvent.ACTION_MOVE The position through which the gesture moves
MotionEvent.ACTION_CANCEL Generated when the gesture is interrupted
MotionEvent.ACTION_OUTSIDE The action generated by moving outside the View scope only provides the initial position
MotionEvent.ACTION_POINTER_DOWN Multi-touch press gesture
MotionEvent.ACTION_POINTER_UP Lifting gestures for multi-touch
MotionEvent.ACTION_HOVER_MOVE The gesture that changed without triggering down is passed to onGenericMotionEvent()
MotionEvent.ACTION_SCROLL Relative vertical or horizontal scroll offset, scroll gesture (action passed to child view), event passed to onGenericMotionEvent()
MotionEvent.ACTION_HOVER_ENTER Carriage return, event passed to onGenericMotionEvent()
MotionEvent.ACTION_HOVER_EXIT Exit the action and pass the event to onGenericMotionEvent()
MotionEvent.ACTION_BUTTON_PRESS The button was clicked, this is not a touch event, so the event is passed onGenericMotionEvent()
MotionEvent.ACTION_BUTTON_RELEASE The button is released, this is not a Touch event, so the event is passed onGenericMotionEvent()
MotionEvent.ACTION_POINTER_INDEX_MASK The index of the multi-touch point, getPointerId gets the identity and getX gets the actual location

b. setTranslationY

Set the vertical position of this view relative to its {@link#getTop () top} position. This effectively locates the late layout of the object, as well as where the object’s layout is placed.

I mean, actually set the relative distance of the view on the y axis.

public void setTranslationY(float translationY) {
        if(translationY ! = getTranslationY()) {// Check whether the setting is the same as the current position
            invalidateViewProperty(true.false);
            //RenderNode contains View properties
            mRenderNode.setTranslationY(translationY);
            invalidateViewProperty(false.true); invalidateParentIfNeededAndWasQuickRejected(); notifySubtreeAccessibilityStateChangedIfNeeded(); }}/** * Fast invalidation of view property changes (alpha, translationXY, etc.). * We don't want to set any flags or handle all cases handled by the default invalidation method > * Instead, we just want to arrange a traversal in the ViewRootImpl with the appropriate dirty Rect. * This method calls the quick-fail methods in the ViewGroup, which iterate through the hierarchy and convert the Dirty Rect as needed. * * This method also handles normal invalid logic if the display list property is not used in this view. * This backup method uses the invalidateParent and forceRedraw flags to handle these situations used in various property setting methods@paramInvalidateParent Force a call to invalidateParentCaches () * if the display list attribute is not used in this view@paramForceRedraw If the display list property is not used in this view, mark the view as drawn to force propagation of invalid */
    @UnsupportedAppUsage
    void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
        if(! isHardwareAccelerated()// Whether hardware acceleration is supported| |! mRenderNode.hasDisplayList()// Whether there is buffered data to draw|| (mPrivateFlags & PFLAG_DRAW_ANIMATION) ! =0) {// Whether the view is drawing
            if (invalidateParent) {// Whether to refresh the parent controlinvalidateParentCaches(); Know the parent view cache, do not call the parent control's invalidate method}if (forceRedraw) {
                mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
            }
            invalidate(false);//invalidate(Boolean invalidateCache)
        } else {
            damageInParent();// Tell the superview to break the View boundary.}}Copy the code
  • Display List is a Buffer that caches drawing commands. The essence of Display List is a Buffer that records the sequence of drawing commands to be executed.

  • Display List is the basic drawing element of the view. It contains the primitive attributes of the element (position, size, Angle, transparency, etc.) corresponding to the drawXxx() method of the Canvas.

  • View information transfer process: Canvas(Java API) – > OpenGL(C/C++ Lib) – > Driver – > GPU

C. getY () and getRawY ()

  • GetRawX () and getRawY() return the position of the touch point relative to the screen,
  • GetX () and getY() return the position of the touch point relative to the View.

3. The train of thought

  • Detour 1: Try to set layoutparams.margintop to change position, but view refresh won’t work
  • Detour 2: Use ValueAnimation to modify translateY but find deraution.
  • Blind cat meets dead mouse: directly use the setTranslateY method, change the position of the view, record the point position when down, judge whether the position exceeds the upper limit and lower limit when moving, judge the direction of gesture when up, and automatically setTranslateY to the next position specified.

4. The source code

/ * * *@authoer create by markfrain
 * @githubhttps://github.com/furuiCQ * Gao Huai see physical and naive * time: 5/8/21 * description: BaiduRecyclView * /
public class BaiduRecycleView extends RecyclerView {
    float lastY;
    float translateY;
    float lastDiff = 0f;
    float minVerticalY = 20;
    int topTranslateY = 10, centerTranslateY = 300, bottomTranslateY = 540;

    public BaiduRecycleView(@NonNull Context context) {
        super(context);
        init();
    }

    public BaiduRecycleView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public BaiduRecycleView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init(a) {
        translateY = DpUtils.dp2px(getContext(), centerTranslateY);
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastY = e.getRawY();
                return true;
            case MotionEvent.ACTION_UP:
                float rawY = lastY - e.getRawY();
                if (translateY < DpUtils.dp2px(getContext(), centerTranslateY)) {
                    translateY = DpUtils.dp2px(getContext(), rawY > 0 ? topTranslateY : centerTranslateY);
                    setTranslationY(translateY);
                } else if (translateY < DpUtils.dp2px(getContext(), bottomTranslateY)) {
                    translateY = DpUtils.dp2px(getContext(), rawY > 0 ? centerTranslateY : bottomTranslateY);
                    setTranslationY(translateY);
                }
                return true;
            case MotionEvent.ACTION_MOVE:
                rawY = lastY - e.getRawY();
                float distance = lastDiff == 0f ? lastDiff : rawY - lastDiff;
                lastDiff = rawY;
                if (rawY > minVerticalY || rawY < -minVerticalY) {
                    if (translateY - distance < DpUtils.dp2px(getContext(), topTranslateY)) {
                        translateY = DpUtils.dp2px(getContext(), topTranslateY);
                        setTranslationY(DpUtils.dp2px(getContext(), topTranslateY));
                    } else if (translateY - distance > DpUtils.dp2px(getContext(), bottomTranslateY)) {
                        translateY = DpUtils.dp2px(getContext(), bottomTranslateY);
                        setTranslationY(DpUtils.dp2px(getContext(), bottomTranslateY));
                    } else{ translateY -= distance; setTranslationY(translateY); }}return false;
        }
        return super.onTouchEvent(e); }}Copy the code

5. Effect display

6. AnyWay

If you have a better, cooler, leaner implementation code, you are welcome to implement it in your spare time, and I’m sure you can gain some knowledge of your own. If you are blue, you can change your mind. If you are blue, you can change your mind.

7. References

  • The Android source code
  • Android rendering — Display List