above

I saw the side slider bar of Cooldog app is interesting, with visual scrolling difference and zoom effect, so I tried to achieve one myself. This component could be implemented using a HorScrollView, but using a HorScrollView would still overwrite the touch event, and the HorScrollView doesn’t help the control at all, so use a lighter ViewGroup instead.

Let’s look at the effect first

How to achieve visual scrolling difference effect

My implementation method is rather stupid, in layout according to a sliding parameter offset to carry out layout dislocation increment. The layout code is as follows

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // to layout menu view and content view
        if(contentView ! =null) {
            int contentHeight = contentView.getMeasuredHeight();
            final int contentLeft = (int) (l + slideOffset * MAX_DRAG_FACTOR * (r - l));
            final int contentRight = contentLeft + contentView.getMeasuredWidth();
            final int contentTop = t + (b - t - contentHeight) / 2;
            final int contentBottom = contentTop + contentHeight;
            contentView.layout(contentLeft, contentTop, contentRight, contentBottom);
        }
        if(slideMenuView ! =null) {
            final int slideMenuWidth = slideMenuView.getMeasuredWidth();
            final int slideMenuHeight = slideMenuView.getMeasuredHeight();
            // Poor visual scrolling
            final int menuLeft = (int) (l - (1 - slideOffset) * MAX_DRAG_FACTOR * (r - l) * slideMenuParallaxOffset);
            final intmenuRight = menuLeft + slideMenuWidth; slideMenuView.layout(menuLeft, t, menuRight, t + slideMenuHeight); }}Copy the code

A contentView and a menuView are used for layout, but where are they retrieved by the control? Or, how does the control know which is contentView and which is menuVIew? Here, I use the method of getting the child control ID based on attR. As shown in the figure below

 <com.kongdy.slidemenulib.SlideMenuLayout
        android:id="@+id/sml_menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorAccent"
        app:sml_content_id="@+id/cl_content"
        app:sml_menu_id="@+id/cl_slide_menu"
        app:sml_scale_mode="true">
			
			<android.support.constraint.ConstraintLayout
	            android:id="@id/cl_content">.</android.support.constraint.ConstraintLayout>

			 <android.support.constraint.ConstraintLayout
		            android:id="@id/cl_slide_menu">.</android.support.constraint.ConstraintLayout>

        </com.kongdy.slidemenulib.SlideMenuLayout>
Copy the code

Assign the control ID of the menuView and contentView to the property. This is not the end, however, because we get two ids in the constructor, but we can’t get either control because the layout is not inflate completed. But, thankfully, Android provides us with this method. As follows:

    @Override
    protected void onFinishInflate(a) {
        super.onFinishInflate();

        slideMenuView = findViewById(slideMenuId);
        contentView = findViewById(contentViewId);

        if (null! = contentView) bringChildToFront(contentView); }Copy the code

We also use the bringChildToFront method, which is provided by the viewGroup. Let’s look at this method:

 @Override
    public void bringChildToFront(View child) {
        final int index = indexOfChild(child);
        if (index >= 0) {
            removeFromArray(index);
            addInArray(child, mChildrenCount);
            child.mParent = this; requestLayout(); invalidate(); }}Copy the code

This method takes the target child view out of the childiList and puts it back at the end of the childList, so when the viewGroup is rendering it, it will put it in the last rendering, and it will be displayed at the top. This ensures that our contentView is always displayed at the top of our current viewGroup.

Handling touch events

Before the Android picture clipping Mosaic implementation (two) : touch implementation explained the process of touch. In this control, the distribution mechanism of viewGroup has been very perfect, we do not need to rewrite dispatchTouchEvent, only need to write onInterceptTouchEvent to determine whether to intercept. The code for onInterceptTouchEvent is as follows:

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // handle weather intercept touch event
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {
                Rect rect = new Rect();
                contentView.getDrawingRect(rect);

                final int touchDownX = (int) ev.getX();
                final int touchDownY = (int) ev.getY();

                if (rect.contains(touchDownX, touchDownY)) {
                    viewMode = VIEW_MODE_TOUCH;
                    return true; }}break;
            case MotionEvent.ACTION_MOVE: {
                if (viewMode == VIEW_MODE_DRAG)
                    return true;
                if (viewMode == VIEW_MODE_TOUCH) {
                    Rect rect = new Rect();
                    contentView.getDrawingRect(rect);

                    final int touchDownX = (int) ev.getX();
                    final int touchDownY = (int) ev.getY();

                    if (rect.contains(touchDownX, touchDownY)) {
                        viewMode = VIEW_MODE_DRAG;
                        final ViewParent viewParent = getParent();
                        if(viewParent ! =null)
                            viewParent.requestDisallowInterceptTouchEvent(false);
                        return true;
                    } else{ resetTouchMode(); }}}break;
        }
        return super.onInterceptTouchEvent(ev);
    }
Copy the code

So we’re checking whether the touch point that we dropped is inside the contentView, and then we’re checking whether the touch point that we first slid is still inside the contentView, and if both are true, Call requestDisallowInterceptTouchEvent method request parent don’t intercept the next touch events themselves, and returns true, the touch events to the touchEvent viewGroup to deal with. Here’s the code inside the touchEvent:


 @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                preTouchX = event.getX();
                preTouchY = event.getY();
                isClickEvent = true;
                break;
            case MotionEvent.ACTION_MOVE: {
                final float currentTouchX = event.getX();
                final float offsetX = currentTouchX - preTouchX;
                if(Math.abs(offsetX) > touchSlop || ! isClickEvent) { isClickEvent =false;
                    int contentLeft = contentView.getLeft();
                    int preCalcLeft = (int) (contentLeft + offsetX);
                    if (preCalcLeft >= 0 && preCalcLeft <= getWidth() * MAX_DRAG_FACTOR) {
                        slideOffset = preCalcLeft / (getWidth() * MAX_DRAG_FACTOR);
                        reDraw();
                    }
                    preTouchX = currentTouchX;
                } else {
                    isClickEvent = true; }}break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
                Rect contentViewRect = new Rect();
                contentView.getDrawingRect(contentViewRect);
                if(isClickEvent && isOpen() && contentViewRect.contains((int)event.getX(),(int)event.getY())) {
                    animToClose();
                } else {
                    int contentLeft = contentView.getLeft();
                    final int currentWidth = getWidth();
                    final int halfWidth = currentWidth / 2;
                    int animFactor = (contentLeft + halfWidth) / currentWidth;
                    if (animFactor > 0) {
                        animToOpen();
                    } else{ animToClose(); } resetTouchMode(); }}break;
        }
        return true;
    }

Copy the code

Here, we first calculate offsetX, the moving distance between the current touch point and the last touch point, and then judge whether this offsetX is greater than touchSlop, which is the minimum sliding value obtained from the system in the constructor. When this value is exceeded, we judge it to slide and set isClickEvent to false, otherwise isClickEvent is set to true, which corresponds to the click event. After the drag state is entered, we anticipate the left value of the contentView. If this value is less than the left edge, or greater than the maximum slide distance to the right, it is not allowed, although the preCalcLeft left is calculated as the current slide displacement rate for global use.

animation

Finally, when we touch lift or cancel, we will do a sliding animation. The animation is very simple. I will post the code here:

   public void animToClose(a) {
        if (viewMode == VIEW_MODE_ANIM)
            return;
        viewMode = VIEW_MODE_ANIM;
        Animator valueAnimator = createValueAnim(slideOffset, 0f, SLIDE_MODE_CLOSE);
        valueAnimator.start();
    }

    public void animToOpen(a) {
        if (viewMode == VIEW_MODE_ANIM)
            return;
        viewMode = VIEW_MODE_ANIM;
        Animator valueAnimator = createValueAnim(slideOffset, 1f, SLIDE_MODE_OPEN);
        valueAnimator.start();
    }

    private Animator createValueAnim(float startValue, float endValue, final int result_mode) {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(startValue, endValue);
        valueAnimator.setDuration(DEFAULT_ANIMATION_TIME);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                slideOffset = (float) animation.getAnimatedValue(); reDraw(); }}); valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                viewMode = VIEW_MODE_IDLE;
                slideMode = result_mode;
            }

            @Override
            public void onAnimationCancel(Animator animation) { viewMode = VIEW_MODE_IDLE; slideMode = result_mode; }});return valueAnimator;
    }
Copy the code

How to use

Start by adding your project’s root directory gradle:

	allprojects {
		repositories {
			...
			maven { url 'https://jitpack.io'}}}Copy the code

Then add the dependency:

dependencies {
		compile 'com. Making. Kongdy: SlideMenuLayout: v1.0.2'
	}
Copy the code

In the project, the XML tag is declared as follows:

 <com.kongdy.slidemenulib.SlideMenuLayout
        android:id="@+id/sml_menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorAccent"
        app:sml_content_id="@+id/cl_content"
        app:sml_menu_id="@+id/cl_slide_menu"
        app:sml_scale_mode="true">
			
			<android.support.constraint.ConstraintLayout
	            android:id="@id/cl_content">.</android.support.constraint.ConstraintLayout>

			 <android.support.constraint.ConstraintLayout
		            android:id="@id/cl_slide_menu">.</android.support.constraint.ConstraintLayout>

        </com.kongdy.slidemenulib.SlideMenuLayout>
Copy the code
  1. App :sml_content_id Content control ID
  2. App :sml_menu_id ID of a menu control
  3. App :sml_scale_mode Specifies whether to enable content control scaling

Commonly used method

  1. AnimToOpen () performs the open menu animation
  2. AnimToClose () performs the close menu animation
  3. IsOpen () Indicates whether the menu is currently open

This article code :github.com/Kongdy/Slid… Personal Github address :github.com/Kongdy Personal Gold Digger homepage :juejin.cn/user/289357… CSDN homepage: blog.csdn.net/u014303003