Visualização personalizada - efeito de deslizamento lateral da página principal de imitação do Kugou Music

Visão geral

Recentemente, quando eu estava usando o Kugou Music, descobri que a página inicial do Kugou Music tem um bom efeito de deslizamento lateral. Não pude deixar de coçar minhas mãos.

yuan.gif

Análise de eficácia

  1. Podemos ver que ele está dividido em duas partes (página de menu, página de tema), então aqui usamos um ViewGroup personalizado
  2. Ao deslizar para a esquerda, haverá uma animação de zoom-in e zoom-out, onde você pode basicamente determinar a necessidade de lidar com eventos de toque e a animação ao tocar
  3. Processamento de abertura e fechamento ao deslizar rapidamente (classe de processamento de limpeza GestureDetector)

Realizar ideias

  1. Aqui escolhemos herdar o layout de rolagem horizontal HorizontalScrollView e, em seguida, escrevemos o arquivo de layout.

concluir

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);
}
复制代码

Depois de executá-lo, descobri que o layout estava errado e completamente confuso.Aqui definimos a largura da página do menu e da página do tema no código.

  • A largura da página do menu Aqui definimos um atributo menu_width
  • Largura da página do tema = largura da tela
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;
}
复制代码

O efeito atual é que ele pode ser deslizado e a largura do layout do menu e do conteúdo da página principal é normal. O problema atual é:

  1. Quando você entra, o menu está aberto, normalmente deve estar fechado
  2. Quando o dedo é levantado, deve-se julgar se o menu está aberto ou fechado de acordo com a posição atual
  3. Não há animação correspondente ao deslizar

wei.gif

  • O primeiro ponto é que quando você entra, o menu está aberto. Podemos rolar o KGSlidingMenu para a posição correspondente quando o layout estiver concluído. A implementação específica é a seguinte
@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);
}
复制代码
  • O segundo ponto é que quando o dedo é levantado, deve-se julgar se o menu está aberto ou fechado de acordo com a posição atual. A condição para julgar a abertura e fechamento do menu é comparar a distância que deslizamos com a largura do menu

    1). Ao fechar, deslize para a direita, se a distância de deslizamento for menor que a metade da largura do menu, é quando levantamos o dedo, o menu deve ser fechado, e quando for maior que a metade da largura do menu, o menu deve ser aberto.

    2) Ao abrir, deslize para a esquerda, se a distância de deslizamento for menor que a metade da largura do menu, é quando levantamos o dedo, o menu deve ser aberto, e quando for maior que a metade da largura do menu, o menu deve ser fechado.

  • Portanto, precisamos lidar com o evento deslizante de toque correspondente em 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);
}
复制代码
  • O terceiro ponto não tem animação correspondente ao deslizar, podemos ver através do efeito

    1). O efeito de animação à direita é um efeito de zoom

    2). O efeito de animação à esquerda tem gradiente, dimensionamento e deslocamento

    O progresso e os efeitos dessas animações são determinados de acordo com a distância de deslizamento, então precisamos obter a distância de deslizamento

//滚动回调  处理右边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);
   }
复制代码

Neste ponto, podemos ver que o efeito está basicamente formado, vamos tratar da otimização dos detalhes.

  • Lide com o efeito de deslizamento rápido do dedo. Se o nosso evento de toque responde ao deslizamento rápido, não execute as operações relevantes no onTouchEvent, caso contrário, haverá eventos conflitantes com efeitos incorretos, portanto, precisamos adicionar este código ao 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;
    }
});
复制代码
  • Se você prestar atenção, verá que quando o menu é aberto, clicamos na parte do tema no lado direito do menu, ele não responderá aos eventos relevantes da página do tema, mas apenas fechará o menu. Somente quando o menu está fechado, os eventos relevantes da parte do tema à direita serão acionados, então precisamos julgar se o menu está fechado no evento de interceptação onInterceptTouchEvent e interceptar o evento, então precisamos adicionar um identificador isMenuOpen ao acima método de abertura e fechamento do menu para distinguir se o menu está aberto ou não. Após a interceptação do evento, fechamos diretamente o menu , portanto não há necessidade de realizar as operações relacionadas ao levantamento do dedo no 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;
}
复制代码

Até agora, o efeito relevante está basicamente concluído, vamos dar uma olhada

wan.gif

Acho que você gosta

Origin juejin.im/post/7085922621761519623
Recomendado
Clasificación