【BYM】Android 仿百度搜索列表滑动效果

blow your mind

bym系列意在除开技术分享,还分享下思路,不止是做一个代码的搬运工。

1. 背景简介

公司业务需要做个仿百度的搜索功能,百度地图的搜索结果列表的滑动效果还挺丝滑的,所以我也想要一个。然后这篇文章/滑稽

i8bwf-29g7k.gif

2. 本期知识点

a. onTouchEvent和MotionEvent

onTouchEvent再熟悉不过了,处理View接收的所有手势,和dispatchTouchEvent以及onInterceptTouchEvent组成了Android中的手势的事件传递。

  @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(ev);
    }
复制代码

MotionEvent是我们(通过鼠标、手、笔、轨迹球)点击屏幕产生的事件对象,该事件包含绝对或相对运动。常见的MovtionEvent的Action如下表

Action 备注
MotionEvent.ACTION_MASK 动作掩码、用于多点触控
MotionEvent.ACTION_DOWN 手势按下屏幕时产生,也可以用于检测按钮的状态
MotionEvent.ACTION_UP 手势结束时产生,该动作包含最终位置
MotionEvent.ACTION_MOVE 手势移动时经过的位置
MotionEvent.ACTION_CANCEL 手势中断后产生
MotionEvent.ACTION_OUTSIDE 在View范围之外移动产生的动作,只提供初始位置
MotionEvent.ACTION_POINTER_DOWN 多点触控时的按下手势
MotionEvent.ACTION_POINTER_UP 多点触控时的抬起手势
MotionEvent.ACTION_HOVER_MOVE 未触发down就发生了改变的手势,事件将传递给onGenericMotionEvent()
MotionEvent.ACTION_SCROLL 相对垂直或水平的滚动偏移,滚动手势(动作会传递给子view),事件传递给onGenericMotionEvent()
MotionEvent.ACTION_HOVER_ENTER 回车动作,事件传递给onGenericMotionEvent()
MotionEvent.ACTION_HOVER_EXIT 退出动作,事件传递给onGenericMotionEvent()
MotionEvent.ACTION_BUTTON_PRESS 按钮被点击了,这不是touch event,所以事件传递给onGenericMotionEvent()
MotionEvent.ACTION_BUTTON_RELEASE 按钮被释放了,这不是touch event,所以事件传递给onGenericMotionEvent()
MotionEvent.ACTION_POINTER_INDEX_MASK 多点触控时点的索引,getPointerId获得标识,getX获得实际位置

b. setTranslationY

源码注释: 设置此视图相对于其{@link#getTop()top}位置的垂直位置。这将有效地定位对象的后期布局,以及对象的布局放置位置。

我的理解是,实际的设置view在y轴上的相对距离。
image.png

public void setTranslationY(float translationY) {
        if (translationY != getTranslationY()) {//判断设置的与当前位置是否相同
            invalidateViewProperty(true, false);
            //RenderNode包含View的属性属性
            mRenderNode.setTranslationY(translationY);
            invalidateViewProperty(false, true);
            
            invalidateParentIfNeededAndWasQuickRejected();
            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
    }
    
    /**
     * 视图属性更改(alpha、translationXY等)的快速失效。
     * 我们不想设置任何标志或处理默认失效方法处理的所有情况>
     * 相反,我们只想在ViewRootImpl中用适当的dirty rect安排一次遍历。
     * 此方法调用ViewGroup中的快速失效方法,这些方法在层次结构中遍历,并根据需要转换dirty rect。
     *
     * 如果此视图中未使用显示列表属性,则该方法还处理正常的无效逻辑。
     * 该备份方法使用invalidateParent和forceRedraw标志来处理各种属性设置方法中使用的这些情
     *
     * @param invalidateParent 如果此视图中未使用显示列表属性,
     * 则强制调用invalidateParentCaches()
     * @param forceRedraw 如果此视图中未使用显示列表属性,请将视图标记为“已绘制”以强制传播无效
     */
    @UnsupportedAppUsage
    void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
        if (!isHardwareAccelerated()//是否支持硬件加速
                || !mRenderNode.hasDisplayList()//是否有需要绘制的缓冲数据
                || (mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {//view是否正在绘制
            if (invalidateParent) {//是否刷新父控件
                invalidateParentCaches();清楚parent view的缓存,不调用父控件的invalidate方法
            }
            if (forceRedraw) {
                mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
            }
            invalidate(false);//invalidate(boolean invalidateCache) 重绘(是否清除绘图缓存)
        } else {
            damageInParent();//告诉父视图破坏此View的边界。

        }
    }
    
复制代码
  • Display List 是一个缓存绘制命令的 Buffer,Display List 的本质是一个缓冲区,它里面记录了即将要执行的绘制命令序列。

  • Display List 是视图的基本绘制元素,包含元素原始属性(位置、尺寸、角度、透明度等),对应 Canvas 的 drawXxx()方法。

  • 视图信息传递流程:Canvas(Java API) —> OpenGL(C/C++ Lib) —> 驱动程序 —> GPU

c. getY()和getRawY()

  • getRawX()、getRawY()返回的是触摸点相对于屏幕的位置,
  • getX()、getY()返回的则是触摸点相对于View的位置。

3. 思路

  • 弯路1: 想着设置layoutparams.marginTop来改变位置,但是视图刷新效果不行
  • 弯路2: 使用ValueAnimation来修改translateY,但发现有deraution。
  • 瞎猫碰到死耗子: 直接使用setTranslateY方法,改变view的位置,在down的时候记录按下的点位,move的时候判断是否移除位置超过上限和下限,up的时候判断手势方向,并自动setTranslateY到指定的下一个位置。

4. 源码

/**
 * @authoer create by markfrain
 * @github https://github.com/furuiCQ
 * 高怀见物理 和气得天真
 * 时间: 5/8/21
 * 描述: BaiduRecyclView
 */
public class BaiduRecycleView extends RecyclerView {
    float lastY;
    float translateY;
    float lastDiff = 0f;
    float minVerticalY = 20;
    int topTranslateY = 10, centerTranslateY = 300, bottomTranslateY = 540;

    public BaiduRecycleView(@NonNull Context context) {
        super(context);
        init();
    }

    public BaiduRecycleView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public BaiduRecycleView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        translateY = DpUtils.dp2px(getContext(), centerTranslateY);
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastY = e.getRawY();
                return true;
            case MotionEvent.ACTION_UP:
                float rawY = lastY - e.getRawY();
                if (translateY < DpUtils.dp2px(getContext(), centerTranslateY)) {
                    translateY = DpUtils.dp2px(getContext(), rawY > 0 ? topTranslateY : centerTranslateY);
                    setTranslationY(translateY);
                } else if (translateY < DpUtils.dp2px(getContext(), bottomTranslateY)) {
                    translateY = DpUtils.dp2px(getContext(), rawY > 0 ? centerTranslateY : bottomTranslateY);
                    setTranslationY(translateY);
                }
                return true;
            case MotionEvent.ACTION_MOVE:
                rawY = lastY - e.getRawY();
                float distance = lastDiff == 0f ? lastDiff : rawY - lastDiff;
                lastDiff = rawY;
                if (rawY > minVerticalY || rawY < -minVerticalY) {
                    if (translateY - distance < DpUtils.dp2px(getContext(), topTranslateY)) {
                        translateY = DpUtils.dp2px(getContext(), topTranslateY);
                        setTranslationY(DpUtils.dp2px(getContext(), topTranslateY));
                    } else if (translateY - distance > DpUtils.dp2px(getContext(), bottomTranslateY)) {
                        translateY = DpUtils.dp2px(getContext(), bottomTranslateY);
                        setTranslationY(DpUtils.dp2px(getContext(), bottomTranslateY));
                    } else {
                        translateY -= distance;
                        setTranslationY(translateY);
                    }
                }
                return false;
        }
        return super.onTouchEvent(e);
    }


}

复制代码

5. 效果展示

4mn1i-c64n1.gif

6. AnyWay

如果你还有更好,更酷炫,更精简的实现代码,也欢迎你没事的时候实现一下,相信你也能收获一点自己的知识。不止做一个高级的Copy工程师~o( ̄▽ ̄)ゞ,觉得文章有blow your mind,欢迎点赞,收藏,评论,欢迎指出我的问题,感谢感谢Thanks♪(・ω・)ノ。

7.参考文献

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享