Custom View - Imitation Kugou Music main page side sliding effect

Overview

Recently, when I was using Kugou Music, I found that Kugou Music's homepage has a good side-slip effect. I couldn't help itching my hands.

yuan.gif

Effectiveness analysis

  1. We can see that it is divided into two parts (menu page, theme page), so here we use a custom ViewGroup
  2. When swiping to the left, there will be a zoom-in and zoom-out animation, where you can basically determine the need to handle touch events and the animation when touching
  3. Open and close processing when sliding quickly (GestureDetector clean up processing class)

Realize ideas

  1. Here we choose to inherit the HorizontalScrollView horizontal scrolling layout, and then write the layout file.

accomplish

public KGSlidingMenu(Context context) {
    this(context, null);
}

public KGSlidingMenu(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public KGSlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}
复制代码

After running it, I found that the layout was wrong and completely messed up. Here we set the width of the menu page and theme page in the code.

  • The width of the menu page Here we define an attribute menu_width
  • Theme page width = screen width
private View mMenuView;
private View mContainerView;
private int mMenuWidth;//需要自定义属性获取



public KGSlidingMenu(Context context) {
    this(context, null);
}

public KGSlidingMenu(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public KGSlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.KGSlidingMenu);
    mMenuWidth = Math.round(ta.getDimension(R.styleable.KGSlidingMenu_menu_width, 325));
    ta.recycle();
 }
 
 
@Override
protected void onFinishInflate() {//在onCreate中执行
    super.onFinishInflate();
    //这个方法代表整个布局解析完毕
    //指定宽高 :内容页的宽度为屏幕的宽度、菜单页的宽度由使用者自己在xml中定义  app:menu_width=""
    //获取菜单和容器View
    //获取的是LinearLayout
    ViewGroup mRootView = (ViewGroup) getChildAt(0);
    if (mRootView == null) {
        throw new NullPointerException("child can not be null !!!");
    }
    if (mRootView.getChildCount() != 2) {
        throw new RuntimeException("Only two can be placed View !!!");
    }
    //获取的是菜单和容器的跟布局
    mMenuView = mRootView.getChildAt(0);
    mContainerView = mRootView.getChildAt(1);
    //给菜单布局设置指定宽度  给容器布局设置屏幕的宽高
    //只能通过layoutParams设置宽高
    mMenuView.getLayoutParams().width = mMenuWidth;
    mContainerView.getLayoutParams().width = getScreenWidth(mContext);
  
}

//获取屏幕宽度
public int getScreenWidth(Context mContext) {
    WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
    DisplayMetrics displayMetrics = new DisplayMetrics();
    wm.getDefaultDisplay().getMetrics(displayMetrics);
    return displayMetrics.widthPixels;
}
复制代码

The current effect is that it can be swiped, and the layout width of the menu and main page content is normal. The current problem is:

  1. When you first come in, the menu is open, normally it should be closed
  2. When the finger is lifted, it should be judged whether the menu is open or closed according to the current position
  3. There is no corresponding animation when swiping

wei.gif

  • The first point is that when you first come in, the menu is open. We can scroll to the corresponding position when the layout is completed. The specific implementation is as follows
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {//在onResume中执行
    super.onLayout(changed, l, t, r, b);
    //初始化进来 mMenuView 模式是不显示的
    //用来摆放子View,等所有子View  摆放完毕才能滚动
    scrollTo(mMenuWidth, 0);
}
复制代码
  • The second point is that when the finger is raised, it should be judged whether the menu is open or closed according to the current position. The condition for judging the opening and closing of the menu is to compare the distance we slide with the width of the menu

    1). When closing, swipe to the right, if the sliding distance is less than half the width of the menu, this is when we lift our finger, the menu should be closed, and when it is greater than half the width of the menu, the menu should be opened.

    2). When opening, slide to the left, if the sliding distance is less than half of the menu width, this is when we lift our finger, the menu should be opened, and when it is greater than half of the menu width, the menu should be closed.

  • So we need to handle the corresponding touch sliding event in onTouchEvent()

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (mGestureDetector != null) {
        if (mGestureDetector.onTouchEvent(event)) return true; //快速滑动执行了,就不要执行onTouch中手指抬起事件
    }
    if (onInterceptTouchEvent(event)) return true;

    //手指抬起
    if (MotionEvent.ACTION_UP == event.getAction()) {
        int mCurrentScrollX = getScrollX();
        float mCurrentScrollY = getScrollY(); //避免竖向触摸时触发事件
        if (Math.abs(mCurrentScrollX) > Math.abs(mCurrentScrollY) * 2 / 3) {
            if (mCurrentScrollX < mMenuWidth / 2) {//未滚动到mMenuView宽度的一半
                //打开菜单
                openMenu();
            } else {
                //关闭菜单
                closeMenu();
            }
        }

        return false;//确保 super.onTouchEvent(event)  不会执行
    }

    return super.onTouchEvent(event);
}
private void closeMenu() {
    smoothScrollTo(mMenuWidth, 0)
}


private void openMenu() {
    smoothScrollTo(0, 0);
}
复制代码
  • The third point has no corresponding animation when sliding, we can see through the effect

    1). The animation effect on the right is a zoom effect

    2). The animation effect on the left has gradient, scaling and displacement

    The progress and effects of these animations are determined according to the sliding distance, so we need to get the sliding distance

//滚动回调  处理右边View的缩放  左边View的缩放和透明度
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
   super.onScrollChanged(l, t, oldl, oldt);
   //l:left 变化mMenuWidth -> 0
   //算一个梯度值
   float scale = 1f * l / mMenuWidth; //从1->0
  
   //计算右边的缩放值
   float rightScaleValue = 0.85f + 0.15f * scale;
   //设置右边的缩放  默认以view的中心点缩放
   ViewCompat.setPivotX(mContainerView, 0);
   ViewCompat.setPivotY(mContainerView, mContainerView.getMeasuredHeight() / 2);
   ViewCompat.setScaleX(mContainerView, rightScaleValue);
   ViewCompat.setScaleY(mContainerView, rightScaleValue);

   //设置右边的菜单的透明度  由半透明到全透明 0。85-1
   //缩放 0.85-1

   //透明度
   float leftAlphaValue = (1 - scale) * 0.5f + 0.5f;
   ViewCompat.setAlpha(mMenuView, leftAlphaValue);

   float leftScaleValue = (1 - scale) * 0.15f + 0.85f;
   ViewCompat.setScaleX(mMenuView, leftScaleValue);
   ViewCompat.setScaleY(mMenuView, leftScaleValue);

   //刚开始退出是在右边而不是左边
   //设置平移
   // ViewCompat.setTranslationX(mMenuView,l);抽屉效果
   ViewCompat.setTranslationX(mMenuView, 0.2f * l);
   }
复制代码

At this point, we can see that the effect is basically formed. Let's deal with the optimization of details.

  • Deal with the effect of fast sliding of the finger. If our touch event responds to fast sliding, do not perform the relevant operations in onTouchEvent, otherwise there will be conflicting events with incorrect effects, so we need to add this code to onTouchEvent
if (mGestureDetector != null) {
    if (mGestureDetector.onTouchEvent(event)) return true; //快速滑动执行了,就不要执行onTouch中手指抬起事件
}
复制代码
mGestureDetector = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() {

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {//快速滑动
        //只要快速滑动就会回调
        //打开的时候往右快速滑动,就去关闭  关闭的时候往左边快速滑动就去打开
        //快速往右边滑动是一个正数  快速往左边滑动是一个负数
        if (Math.abs(velocityX) < Math.abs(velocityY) * 2 / 3) return false;
        if (isMenuOpen) {
            //打开的时候往右快速滑动,就去关闭
            if (velocityX < 0) {
                closeMenu();
                return true;
            }
        } else {
            //关闭的时候往左边快速滑动就去打开
            if (velocityX > 0) {
                openMenu();
                return true;
            }
        }
        return false;
    }
});
复制代码
  • If you pay attention, you can find that when the menu is opened, we click on the theme part on the right side of the menu, it will not respond to the relevant events of the theme page, but only close the menu. Only when the menu is closed, will the relevant events of the theme part on the right be triggered, so We need to judge whether the menu is closed in the interception event onInterceptTouchEvent and intercept the event, so we need to add an identifier isMenuOpen to the above method of opening and closing the menu to distinguish whether the menu is open or not. After the event is intercepted, we directly close the menu , so there is no need to perform the operations related to finger lift in onTouchEvent.
if (onInterceptTouchEvent(event)) return true;
复制代码
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    if (isMenuOpen && event.getX() > mMenuView.getWidth() && event.getAction() == MotionEvent.ACTION_UP) {
        closeMenu();
        return true;
    }
    return false;
}
复制代码

So far the relevant effect is basically completed, let's take a look

wan.gif

Guess you like

Origin juejin.im/post/7085922621761519623
Recommended