Imitation QQ drawer navigation bar

1, Demand

Reproduce the effect of QQ drawer navigation bar.

2, Implementation mode

  • DrawerLayout
  • ViewDragHelper
  • Customize ViewGroup

3, Research

  • DrawerLayout

    DrawerLayout contains two sub layouts, the content layout and the menu bar layout. Implement the addDrawerListener interface and perform logical processing in the callback method.

  • ViewDragHelper
    View drag and drop the help class, create an instance and override the callback method processing logic of the callback interface.

  • Customize ViewGroup
    Override the methods related to the event to handle the layout of the event.

  • Determine implementation

    • The implementation of DrawerLayout is simple and convenient, but it is difficult to understand the principle due to encapsulation.
    • The tryCaptureView() method of ViewDragHelper can only capture View that needs to be moved, and we need to displace the layout of the menu bar. We need to call captureChildView() in the onViewCaptured() method to import the layout that needs to be operated.
public void captureChildView(@NonNull View childView, int activePointerId) {
        if (childView.getParent() != mParentView) {
            throw new IllegalArgumentException("captureChildView: parameter must be a descendant "
                    + "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
        }
        mCapturedView = childView;
        mActivePointerId = activePointerId;
        mCallback.onViewCaptured(childView, activePointerId);
        setDragState(STATE_DRAGGING);
    }

In captureChildView(), mCapturedView represents the captured View. We need to pass in the menu bar layout View here, but this method will also call onViewCaptured() method to form an endless loop. You can add a variable to the onviewcaptured () method to determine whether it is the first call to avoid an endless loop. However, in the ViewDragHelper event method callback, the onViewCaptured() method will still be called to pass in the View containing events. Therefore, even if the View exchange is carried out, the View containing events will be captured in the end, which is difficult to implement.

  • To customize the ViewGroup, you only need to override the dispatchTouchEvent() method and logically process the event.

4, Analysis

1. When sliding on the main page, the menu bar slides, that is, the sliding distance of the menu bar depends on the sliding distance.
2. When the finger is released, the sliding distance is greater than or equal to 1 / 2 of the screen width, and the menu bar layout will automatically cover the screen, otherwise the content layout will be displayed.
3. When the menu bar slides, the main page will be animated with zoom and transparency.
When the event ends, the menu bar always has two states: out and full
When sliding right, the position of the event at the end of the event is always greater than or equal to the position at the beginning of the event (when equal to, the menu bar is all out of the screen, so when less than, no effect can be seen and no judgment will be made directly). It's like sliding on the left.

5, Code implementation

Because it is a custom ViewGroup, the FrameLayout layout is implemented.

class FirstFrameLayout(context: Context, attributeSet: AttributeSet? = null) : FrameLayout(context, attributeSet){}

Get the sub layout object in onFinishInflate()

override fun onFinishInflate() {
        super.onFinishInflate()
        menuLayout = getChildAt(1) as RelativeLayout
        contentLayout = getChildAt(0) as FrameLayout
    }

Override the onLayout() layout to determine the sub layout location

override fun onLayout(p0: Boolean, p1: Int, p2: Int, p3: Int, p4: Int) {
        secondLayout.layout(-right, top, left, bottom)
        firstLayout.layout(left,top,right,bottom)
    }

Set menu bar status

enum class State {
    OUT,
    FULL,
}

Override the dispatchTouchEvent() method to intercept events

    var first = 0f
    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                first = ev.x //Record the first DOWN event and the X-axis coordinate position
            }
            MotionEvent.ACTION_MOVE -> {
                val dif = ev.x - first //MOVE distance per MOVE event
                if (flag == State.OUT) { //Slide the menu bar from left to right
                    if (dif > 0) { 
                        menuLayout.left = -right
                        menuLayout.right = 0
                        menuLayout.offsetLeftAndRight(dif.toInt()) //Displacement of menu bar layout
                        scaleSecond(dif) //Content layout animation
                    }
                } else if (flag == State.FULL) { //Slide the menu bar from right to left
                    if (dif < 0) {
                        menuLayout.left = 0
                        menuLayout.right = right
                        menuLayout.offsetLeftAndRight(dif.toInt())
                        scaleSecond1(dif)
                    }
                }
            }
            MotionEvent.ACTION_UP -> {
                val dif = ev.x - first//UP event moving distance
                if (flag == State.OUT) { //Slide from outside to inside to stop
                    flag = if (dif >= right / 2) { //If the distance is greater than 1 / 2, the FULL screen status of the menu bar is set to FULL
                        menuLayout.layout(left, top, right, bottom)
                        State.FULL
                    } else {//If the distance is less than 1 / 2, the left side of the menu bar remains unchanged
                        menuLayout.layout(-right, top, left, bottom)
                        release() 
                        State.OUT
                    }
                } else if (flag == State.FULL) {
                    flag = if (dif < 0 && abs(dif) >= right / 2) {
                        menuLayout.layout(-right, top, left, bottom)
                        State.OUT
                    } else {
                        menuLayout.layout(left, top, right, bottom)
                        release1()
                        State.FULL
                    }
                }
            }
        }

        return true //You must return true, otherwise only the DOWN event will be intercepted, and subsequent UP and MOVE events will not be processed.
    }
    //Empty the contentLayout animation
    private fun release() {
        contentLayout.scaleX = 1f
        contentLayout.scaleX = 1f
        contentLayout.alpha = 1f
    }
    //contentLayout zoom and transparency animation
    private fun scaleSecond(dif: Float) {
        val scale = 1 - ((dif / right) * 0.05f)
        val alpha = 1 - ((dif / right) * 0.6f) 
        contentLayout.scaleX = scale
        contentLayout.scaleY = scale
        contentLayout.alpha = alpha
    }

6, Summary

Before implementation, the drawer effect should be classified, and different treatments should be done for different states to improve efficiency.

Tags: Android

Posted by eves on Wed, 22 Sep 2021 08:45:50 +0530