Side slide menu to customize View

The introduction

All code has been uploadedGitHub, interested can download to view.

In daily life, we often see some applications that use the side slide menu, such as QQ and Didi, etc. Side slide animation can increase the activity of the application and make it less rigid, as shown in the figure:



Today our task is to implement a universal sideslip layout that supports not only left to right, but also up and down.

Custom FrameLayout

I choose to use the custom FrameLayout to achieve the effect of sideslip menu, because I hope to achieve four sides can be sideslip, and can configure the view of the sideslip menu, each sideslip menu can set the display proportion freely. So I designed a ViewItem class to hold the view’s zoom information.

Class ViewItem {View layout; class ViewItem {View layout; //View View float scale; // display scale}Copy the code

Display the main screen

By rewriting onLayout(Boolean changed, int left, int top, int right, int bottom), we can set the position and size of the view freely.

@Override protected void onLayout(boolean changed, int left, int top, int right, Int bottom) {// Remove all layouts removeAllViewsInLayout(); If (mHomeView! = null) { addView(mHomeView); mHomeView.layout(0, 0, mWidth, mHeight); }}Copy the code

Among themmWidthandmHeightRepresents the width and height of the space, can be inonSizeChanged(int w, int h, int oldw, int oldh)Method to update the width and height every time the size of the space changes.

The final effect is as follows:

The menu page is displayed

Add the following method to the code above, adding the up, down, left, and right side slider menu.

// Add the LEFT interface addItemView(mLeftViewItem, Gravity.LEFT); // Add the TOP interface addItemView(mTopViewItem, Gravity.TOP); // Add the RIGHT interface addItemView(mRightViewItem, Gravity.RIGHT); // Add the BOTTOM interface addItemView(mBottomViewItem, Gravity.BOTTOM);Copy the code

AddItemView (SideslipViewItem viewItem, int Position) sets the content and position to be displayed according to the viewItem and position passed

Private void addItemView(SideslipViewItem viewItem, SideslipViewItem, SideslipViewItem, SideslipViewItem, SideslipViewItem, viewItem) int position) { if (viewItem == null || viewItem.getLayout() == null) return; switch (position) { case Gravity.LEFT: { int x = (int) (-mWidth * viewItem.getScale()); viewItem.getLayout().layout(x, 0, 0, mHeight); } case Gravity.TOP: { int t = (int) (-mHeight * viewItem.getScale()); viewItem.getLayout().layout(0, t, 0, 0); } case Gravity.RIGHT: { int r = (int) (mWidth * (1 + viewItem.getScale())); viewItem.getLayout().layout(mWidth, 0, r, mHeight); } case Gravity.BOTTOM: { int b = (int) (mHeight * (1 + viewItem.getScale())); viewItem.getLayout().layout(0, mHeight, mWidth, b); }}}Copy the code

Add finger interaction

In order not to affect the internal view click response, we need to process the finger touch logic in the onInterceptTouchEvent(MotionEvent EV). If the value is true, the touch event is intercepted and not passed to the lower level. Let’s define a few variables:

private float mTouchStartX = 0; // Private float mTouchStartY = 0; Private float mTouchMoveX = 0; private float mTouchMoveX = 0; // Float x private float mTouchMoveY = 0; Y private float interceptMoveX = 0; Private float interceptmovey = 0; // The finger moves yCopy the code

Rewrite onInterceptTouchEvent (MotionEvent ev) :

@Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: {// Save the finger pressed coordinates mTouchStartX = ev.getx (); mTouchStartY = ev.getY(); mInterceptMoveX = mTouchStartX; mInterceptMoveY = mTouchStartY; mTouchMoveX = mTouchStartX; mTouchMoveY = mTouchStartY; touchDownTime = System.currentTimeMillis(); // Save the current break; } case MotionEvent.ACTION_MOVE: { float x = ev.getX(); float y = ev.getY(); mInterceptMoveX = x - mInterceptMoveX; mInterceptMoveY = y - mInterceptMoveY; /** * Determine if the finger is on the boundary when it is pressed * if it is on the boundary, and the finger slides a distance greater than 10; Intercept touch events */ if (computeIsTouchInSide(mTouchStartX, mTouchStartY) || isShowingSide) { if (Math.abs(mInterceptMoveX) > 10 || Math.abs(mInterceptMoveY) > 10) { return true; } } break; } } return super.onInterceptTouchEvent(ev); } /** * computeIsTouchInSide(float x, float y) { if (x < mWidth / 4 || x > mWidth / 4f * 3 || y < mHeight / 4 || y > mHeight / 4f * 3) return true; return false; }Copy the code

After intercepting the touch event, we handle the event inside onTouchEvent. Because we want to follow the finger, we need to handle motionEvent.action_move.

@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: { float x = event.getX(); float y = event.getY(); Float disX = x-mTouchmovex; float disX = x-mTouchmovex; float disY = y - mTouchMoveY; // Update the coordinates of the last finger slide mTouchMoveX = x; mTouchMoveY = y; Log.d(TAG, "onTouchEvent: x:" + disX); // Move view touchMoveViews(disX, disY); break; }}Copy the code

The final effect is as follows:

Add animation effects

Animate the layout and execute it when the finger is raised. Add MotionEvent.ACTION_CANCEL and MotionEvent.ACTION_UP to the code above

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
                float x = event.getX();
                float y = event.getY();

                //移动距离不够则不进行动画
                if (Math.abs(x - mTouchStartX) < 50 && Math.abs(y - mTouchStartY) < 50)
                    break;
                /**
                 * 计算手指抬起的位置,判断是否应该显示或者隐藏侧滑菜单
                 */
                if (computeIsShowSide(event.getX(), event.getY()))
                    animateShowSideView(event.getX(), event.getY(), mMoveSide);
                else
                    animateHideSideView(event.getX(), event.getY(), mMoveSide);

                break;
            }Copy the code

The method to determine whether to display or hide is as follows:

/** * @return true: display the slider menu false: */ private Boolean computeIsShowSide(float x, float y) {// If (isShowingSide &&! computeIsTouchInSide(mTouchStartX, mTouchStartY)) return true; else if (! isShowingSide && ! computeIsTouchInSide(mTouchStartX, mTouchStartY)) return false; long time = System.currentTimeMillis() - touchDownTime; float speed = 0; switch (mMoveSide) { case Gravity.LEFT: { speed = (x - mTouchStartX) / time; if (speed > 1) return true; break; } case Gravity.RIGHT: { speed = (x - mTouchStartX) / time; if (speed < -1) return true; break; } case Gravity.TOP: { speed = (y - mTouchStartY) / time; if (speed > 1) return true; break; } case Gravity.BOTTOM: { speed = (y - mTouchStartY) / time; if (speed < -1) return true; break; }} / / finger sliding more than half of the screen can also start animation if ((x - mTouchStartX) > mWidth / 2 | | (y - mTouchStartY) > mHeight / 2) return true; return false; }Copy the code

Set the animation, ObjectAnimator can set the animation, we just need to use it to get the intermediate interpolation, and then update the position of the view

Private void animateShowSideView(float); /** * Execute view animation ** execute while finger is raised ** @param x * @param y * @param gravity // Execute side animation */ private void animateShowSideView(float)  x, float y, int gravity) { float startVal = 0; float endVal = 0; switch (gravity) { case Gravity.LEFT: { if (mLeftViewItem == null) break; startVal = mLeftViewItem.getLayout().getX(); endVal = 0; break; } case Gravity.TOP: { if (mTopViewItem == null) break; startVal = mTopViewItem.getLayout().getY(); endVal = 0; break; } case Gravity.RIGHT: { if (mTopViewItem == null) break; startVal = mRightViewItem.getLayout().getX(); endVal = (1 - mRightViewItem.getScale()) * mWidth; break; } case Gravity.BOTTOM: { if (mBottomViewItem == null) break; startVal = mBottomViewItem.getLayout().getY(); endVal = (1 - mBottomViewItem.getScale()) * mHeight; break; }} // Clear the previous animation clearAnimation(); /** * interpolator interpolator = new interpolator (); Final ObjectAnimator animate = objectAnimator. ofFloat(this, "sideslip", startVal, endVal); animate.setInterpolator(bounceInterpolator); animate.setDuration(mAnimateTime); animate.start(); animate.addUpdateListener(updateListener); IsShowingSide = true; }Copy the code

Animation update listener, set the position of each frame view.

/ / animation updates to monitor private ValueAnimator. AnimatorUpdateListener updateListener = new ValueAnimator. AnimatorUpdateListener () { @Override public void onAnimationUpdate(ValueAnimator animation) { float cVal = (float) animation.getAnimatedValue(); Log.i(TAG, "onAnimationUpdate: " + cVal); switch (mMoveSide) { case Gravity.LEFT: { if (mLeftViewItem ! = null) mLeftViewItem.getLayout().setX(cVal); break; } case Gravity.TOP: { if (mTopViewItem ! = null) mTopViewItem.getLayout().setY(cVal); break; } case Gravity.RIGHT: { if (mRightViewItem ! = null) mRightViewItem.getLayout().setX(cVal); break; } case Gravity.BOTTOM: { if (mBottomViewItem ! = null) mBottomViewItem.getLayout().setY(cVal); break; }}}};Copy the code

The final effect is as follows:

Add the shadow

After the sideslip menu pops up, we might want people to focus on the sideslip menu, so we can shadow the rest of the screen to feel like the Dialog pops up. I do this by overlaying a View on top of the main screen and changing the background color of the View as the popover comes up. Add a shadow View first, add a shadow View after adding the main screen, so that you can overlay the main screen.

If (mHomeView! = null) { addView(mHomeView); mHomeView.layout(0, 0, mWidth, mHeight); } // Add shadow view mShadeView = new View(getContext()); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); mShadeView.setLayoutParams(params); addView(mShadeView);Copy the code

To achieve the dynamic shadow effect, change the opacity of the shadow while sliding on the side menu. Here I have used the left side menu as an example: In moveLeftView(float mx), add the following function:

// The menu is completely invisible x minus the current x is the change value, Float change = math.abs ((-mWidth * mleftViewitem.getScale ()) -mleftViewitem.getLayout ().getx ()); float change = math.abs ((-mWidth * mleftViewitem.getScale ()) -mleftViewitem.getLayout ().getx ()); float p = change / (mWidth * mLeftViewItem.getScale()); mShadeView.setAlpha(p);Copy the code

End result:

This article is by Bearever, except for the reprint/source, all original website, must be signed before reprint last edited: Friday, 09-29 10:05