自定义 ViewGroup ,实现卡片堆叠效果

本文介绍了如何通过自定义ViewGroup实现卡片堆叠的滑动效果,详细讲解了分析、测量大小、触摸事件分发以及实现过程。涉及到自定义布局的测量、触摸事件拦截和处理,以及在滑动过程中对子视图位置的动态调整。
摘要由CSDN通过智能技术生成
需求

想做一个卡片堆叠效果的滑动,两个视图,滑动过程将第二个view叠加在第一个view上边,形成叠加的效果,有点像 NestedScrollView + CoordinatorLayout + Toolbar 的效果。预览图如下:

效果图

看起来有点像 NestedScrollView+Toolbar 的效果,只是里面的变换不一样,往上推过程第二个控件往上移动,第一个 view 不发生变换。我这个是自定义viewgroup方式实现,其实用 NestedScroll 也能实现,难度应该会更低,下次再使用 NestedScroll 实现。

分析

使用自定义 ViewGroup 来做这个效果,有两个需要解决的点,一是关于第二个 view 的大小测量,有用过 NestedScrollView 嵌套过 ListView 的同学肯定知道嵌套后 ListView 只显示一行,必须去重写 ListViewonMeasure 才能解决显示不完全的问题,如果不指定 ListView 的具体大小,需自行计算第二个 view 的大小;二是触摸事件的分发处理,在滑动过程如果第二 view 没有推到顶,父布局要消费这个事件,如果到顶了,需要把事件继续下发给第二个 view 。

  1. 测量大小
    测量 view 大小这里不展开,ListView 的话在 adapterNotifyDataSetChange 后根据父布局已确定的大小重新测量,其他也一样,需要注意的一点是,叠加的视图(即最下面的那个 view ,下面用 target 代替)上移上去,也就是说改变视图的 top 大小,所以在测绘 target 的时候,建议把 target的高度再加上需位移大小,这样移动到最上面的时候,视图最下面不会出现空白区域。
  2. 使用 MarginLayoutParams
    无论是测量大小还是摆放 view ,能用 margin 肯定是最好的,所以需要重写 ViewGrouppublic LayoutParams generateLayoutParams(AttributeSet attrs) 方法,返回 MarginLayoutParams ,否则子 view 获取 LayoutParams 强转为 MarginLayoutParams 会出类转换异常,需注意
  3. 触摸事件
    • 触摸事件分发复习一下大致流程
      最上层下发触摸事件,由 dispatchTouchEvent 分发,中途由 onInterceptTouchEvent 决定是否拦截,拦截的话不再下发,进入拦截view的 onTouchEvent ,不拦截的继续下发到子 view ,重复上一步骤分发 dispatch,如果 onTouchEvent 消耗了该事件(return true)则不再往上回调 onTouchEvent,否则继续上传回最顶层view,当然,子 view 也可以请求父布局不准消耗触摸事件,强制要求下发,如可滑动的视图,请求父布局 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) 是否禁止拦截触摸事件。
    • 关于拦截 ACTION_MOVE 不触发问题
      这个从逻辑层面来解释比较容易,我们可以理解移动的形成首先由手指按下,再到手指抬起,中间产生的位移,即为移动,那么也就说需要先拦截到 ACTION_DOWN ,向系统通知这是一个有效的按压,才会触发下一个 move 事件,没有 down 作为前提,是没有 move 存在的可能,所以需要在拦截 ACTION_DOWN 时候返回 true。
    • 事件继续下发
      当我们滑动到最顶部的时候,这时候的移动对我们来说已经没有用了,需要把这个事件传递给子 view,但是 move 的前提是什么?是 ACTION_DOWN,所以需要在继续下发之前,先主动下发一个 ACTION_DOWN ,再去分发 ACTION_MOVE
实现

Talk is cheap,show me the code.

  1. 测量布局的就不写了,不明白可以看看 android 的 LinearLayout/RelativeLayout 等布局写法

  2. onLayout 可以参考 LinearLayout ,只是我们需要在摆放 target 视图时把位移高度加进去,如下:

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         
        int count = getChildCount();
    
        int layoutTop = top;
        int limitFirstChild = 0;
        for (int i = 0; i < count; i++) {
         
            View child = getChildAt(i);
            if (child.getVisibility() != View.VISIBLE) {
         
                continue;
            }
            int width = child.getMeasuredWidth();
            int height = child.getMeasuredHeight();
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
            left += lp.leftMargin;
            layoutTop += lp.topMargin;
            if (i == 0 && limitOffset == 0) {
         
                limitFirstChild = layoutTop + height / 2;//留存第一个view的top+高度/2,遮盖一半的视图
            }
    
            if (i == count - 1 
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值