美文网首页安卓开发笔记Android资源收录Android效果/自定义
来来来,随老夫撸一个支付宝玩玩——自定义Behavior的正确打

来来来,随老夫撸一个支付宝玩玩——自定义Behavior的正确打

作者: 桑小年 | 来源:发表于2017-07-03 14:49 被阅读1973次

最近在和项目经理都斗智斗勇的时候,突然被甩过来一个类似支付宝首页的功能需求,虽然网上有一些类似的功能,但是都是以前比较老一些的版本,于是决定自己来定制一个,老规矩,先上图

GIF.gif

要实现这样一个效果,首先想到的自然就是 CoordinatorLayout;

什么是CoordinatorLayout?
CoordinatorLayout是用来协调其子view们之间动作的一个父view,而Behavior就是用来给CoordinatorLayout的子view们实现交互的。关于这个控件,大神们已经介绍的很详细了,这里我就不过多啰嗦,直接开撸了

关于Behavior
要自定义Behavior,首先要搞清楚两个核心View,child 和 dependency;他们分别代表的是要应用behavior的View 和触发behavior并与child进行互动的View。

要实现上面的的效果,需要实现以下几个关键方法

  • layoutDependsOn(CoordinatorLayout parent, View child, View dependency)
    用来判断child是否有一个对应的dependency,如果有就返回true,默认情况下返回的是false

  • onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection)
    此方法可用于动态更改childView的布局,如果自定义Behaior,这个方法一定返回true,否则将使用默认Behavior的布局

  • onDependentViewChanged(CoordinatorLayout parent, View child, View dependency)
    此方法在dependencyView发生变化的时候调用,在这里,我们可以对child根据dependency的变化进行一定的操作

  • onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes)
    此方法表示开始滑动,最后一个参数表示的是滑动方向,并且只有在返回值为true的时候才能出发接下来的操作,在这里可以进行一些过滤操作,比如值接受垂直方向的滑动等

  • onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes)
    在onStartNestedScroll返回为true的时候调用,此时表示CoordinatorLayout已经拦截了滑动,在这里可以做一些滑动初始化的操作

  • onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed)
    在开始嵌套滑动之前,会执行此操作,dx、dy分别表示用户手指滑动的距离,consumed则表示在操作过程中,消耗掉的滑动距离,例如:

consumed[1] = dy;

此时表示垂直方向的滑动被全部消耗,将不会被传递到下一步操作,相对应的child,如RecycleView将不能接受的滑动操作,不会进行滑动

  • onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
    此方法在嵌套滑动时候调用,可以多滑动过程进行操作

OK,各位看官,主要方法介绍完了,我懂,现在要开始贴代码了对吧,老夫混迹各大网站多年,不会被打的,代码来了

首先,由于 我们确定是否应用自定义Behavior,如果是就返回true

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        if (child != null) {
            childeView = new WeakReference<View>(child);
        }
        if (dependency != null && dependency instanceof RelativeLayout) {
            dependentView = new WeakReference<>(dependency);
            return true;
        }
        return super.layoutDependsOn(parent, child, dependency);
    }

然后在这里,我们对控件的布局进行操作,在这里,我用的是一个RecycleView作为child,RelativeLayout作为dependView并且将child一直位于dependView之下

  @Override
    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
        child.layout(0, 0, parent.getWidth(), (parent.getHeight() - dependentView.get().getHeight()));
        if (heardSize == -1) {
          //获取dependView的初始高度
            heardSize = dependentView.get().getHeight();

        //设置child的偏移量,使之一直处于dependView之下
            minHeard = dependentView.get().findViewById(R.id.rl_icon).getHeight();
            child.setTranslationY(heardSize);
        }

        return true;
    }

在滑动过程中,dependView在位置或者大小发生改变时候都会调用此方法,在此方法中,可以对dependView随着位置和大小变化进行不同操作

 @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        View view = dependentView.get();

        float translationY = child.getTranslationY();
        float min = minHeard*1.0f/heardSize;
        float pro = (translationY) / heardSize;
        View child1 = view.findViewById(R.id.ll);
        child1.setPivotY(0);
        child1.setPivotX(0);
        View titleView = dependentView.get().findViewById(R.id.rl_icon);
        titleView.setPivotY(0);
        titleView.setPivotX(0);

      //根据当前高度变化,来更改dependView中部分控件的透明度,实现头部部分控件渐显渐隐的效果
        titleView.setAlpha(1 - pro);
        if (pro<=min+0.1){
            titleView.setAlpha(1);
        }

//根据高度变化,隐藏或展示部分控件
        if (pro>0.95){
            titleView.setVisibility(View.GONE);
        }else {
            titleView.setVisibility(View.VISIBLE);

        }
        if (pro >= min && pro <= 1) {

            child1.setAlpha(pro);
            if (pro < 0.7) {
                child1.setVisibility(View.GONE);
            } else {
                child1.setVisibility(View.VISIBLE);
            }

            return true;
        }

        return super.onDependentViewChanged(parent, child, dependency);
    }

对指定方向的滑动进行操作

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        //仅仅拦截垂直方向的滑动操作
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
    }

对于已经拦截的滑动,进行初始化处理

  @Override
    public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
    
      //清除停止滑动后的动画效果
        clearAnimotor();
        isScroll = false;
    }

在滑动过程中,控件进行操作,在滑动过程中,动态更改dependView的高度和child的偏移量

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        if (dyUnconsumed > 0) {
            return;
        }
        View view = dependentView.get();
        ViewGroup.LayoutParams params = view.getLayoutParams();
        int height = (int) child.getTranslationY();
        if (dyUnconsumed < 0&&params!=null) {
            int h = height - dyUnconsumed;

            if (h >= 0 &&h<= heardSize) {
                params.height = h;
                view.setLayoutParams(params);
                child.setTranslationY(h);
                if (child instanceof BasicRefrushRecycleView){
                    BasicRefrushRecycleView recycleView = (BasicRefrushRecycleView) child;
                    recycleView.setViewHeight(recycleView.getEndView(),0);
                }
            }

        }
    }

在用户手指离开屏幕之后,停止滑动,此时根据当前状态,执行相应的动画,扩展或者缩小dependView

 @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {

        int height = dependentView.get().getHeight();
        float translationY = childeView.get().getTranslationY();
        if (translationY > height) {
            isExpand = true;
        } else {
            isExpand = false;
        }

        if (isExpand) {
            float pro = ((translationY - height) * 1.0f / heardSize);
            creatExpendAnimator(translationY, height, (int) (500 * pro));
        }


        if (!isScroll && height > minHeard && height < heardSize) {
            childeView.get().setScrollY(0);
            if (height < 0.7 * heardSize) {//上滑
                float pro = (height - minHeard) * 1.0f / (heardSize - minHeard);
                creatAnimation(height, minHeard, (int) (500 * pro));
            } else {//下滑
                float pro = (heardSize - height) * 1.0f / (heardSize - minHeard);
                creatAnimation(height, heardSize, (int) (500 * pro));
            }
            isScroll = true;
        }


    }

好了,到这里基本就结束,由于本人数学属于战五渣渣,所以惯性滑动部分就没有处理了,关于child,我使用的是一个自己封装的RecycleView库,可以上拉刷新下拉加载,同时也可以轻松实现多复杂条目的加载实现,而且是面向holder开发的哦,相当解耦啊,广告就到这里了,不过暂时使用的项目不多,并且自我感觉优化不足,所以就先不开放了,大家随便用一个RecycleView代替就行了,兴趣的小伙伴可以私信我,一定会满足你的;

更新一下:主要是这个效果是在我们项目中使用,所以本人为了偷懒拿出来了关键的自定义控件
代码,但是很多小伙伴私信我,希望可以有一个完整的demo,因为这个源码没办法直接使用太坑爹啊

现在:我偷偷的吧公司的自己写了一下小Demo,可以直接运行的,但是大家记得偷偷的拿代码,打枪的不要,因为老板知道了,会把我的屎都打出来的。。。。
(源码已经更新,谢谢各位了,赶紧拿走吧)
最后:源码

相关文章

网友评论

  • 9df2a6e99159:小哥,如果我只想要实现你这里屏幕中间的下拉刷新,不想要上面的折叠效果是不是就不行啊
    桑小年:可以的
  • 9df2a6e99159:小哥,想问下能只实现下面的局部下拉刷新,而不实现上面的上滑折叠效果么
    桑小年:可以啊,下面的整体是一个RecycleView,去掉Behavior就可以了
  • Solang:下面recyclerView上划到顶端会卡一下,再划才睡显示出dependeseView,我不想让它卡,怎么处理?
  • 莫离境:我看支付宝 关于 界面的开源信息,首页用的是 XRecyclerView?
    桑小年:是的,不过我这个Demo主要是想研究一下实现这中效果,重要的是Behavior的学习使用,其他的一些是我个人喜欢这样去使用,自己写出来的轮子用起来比较顺手啊
  • D13954:child1.setPivotY(0);
    child1.setPivotX(0);
    View titleView = dependentView.get().findViewById(R.id.rl_icon);
    titleView.setPivotY(0);
    titleView.setPivotX(0);

    这里配置了锚点,把它注释之后,没有变化,楼主能解析下吗?
  • D13954:楼主那个,钉钉联系人详细资料里面的那个,头像偏移怎么撸的呢,这个demo够我好好磨一段时间了。:clap:
  • 魏魏魏魏:其实我还是没有明白 dependency 和 child 这两个 View ,或者说不确定这两个概念是否理解正确!比如一个 CoordinatorLayout 布局下有一个 AppBarLayout 和一个
    NestedScrollView ,Behavior 这个属性是赋值给 NestedScrollView ,那这里是否是 child
    指 NestedScrollView,dependency 指 AppBarLayout,请大神指点一下,谢谢!
  • very_mrq:秦始皇的仙丹我都不服,就服你
  • 549da53acb37:baserecyclevie还有那块需要继续优化呢?
    549da53acb37:@桑小年 嗯,可以做的更好啊,继续吧楼主
    桑小年:其实主要是代码略微有些乱,而且刷新效果比较少,还有局部刷新也没有封装优化,这些都需要开发者去实现,轮子不是很圆啊,不过一般的使用还是没问题的
    桑小年:这个只是初步封装,如果要求不高的话是可以正常使用的
  • 73ece15c815f:dependency不是我们主动操作的view ,child是跟随的view那,为什么是dependency instanceof RelativeLayout ,,有点不明白啊。新手,指教一下啊
    桑小年:楼上说的是正确的,上面滚动的那一大块蓝色的,就是dependency,recyclerview则是child,在layoutDependsOn方法中有介绍,child即为Behavior依赖的View ,这里就是recyclerview;为了保证dependView是我们需要的,所以我增加了一个判断
    隔壁王较瘦: @小呢个李 relativelayout是你recyclerview上面那一部分,下面是滚动view,上面是绑定滚动事件的view,就是说recyclerview和上面那个布局绑定,等到滑动的时候上面的view能够监听到recyclerview的滑动做出相应的处理,应该大概是这样吧
  • ZCJ风飞:在github中给老哥提了两个问题,忘老哥有时间给解答下啊:
    桑小年:好的大兄弟,已经恢复了哦
  • NathansLiu:很帅 恩...你有freestyle么
    0ddc1f7125e0:不会被打的,屎都打出来,233333333333
    桑小年:@NathansLiu 呦呦,切克闹,煎饼果子来一套!谢谢评委老师
  • Laddie:项目正好需要,老哥,谢谢哦
    桑小年:@Laddie 能用就好,代码能被别人认可,本就是很开心的事啊
  • loneyzhou:作者你好,我最近也在做仿支付宝首页的效果,我想问下你的布局下方是个刷新的recyclerView,有没有实现像支付宝那样,如果你的手指在整个布局上半部分(上面菜单那一部分布局)往下拉,会触发下面的recyclerView慢慢随手指移动,直到刷新的效果?这块要怎么做呢。。
    桑小年:抱歉,这个暂时没有实现自己定义一下头布局,改一下滑动效果就可以了:sob:
  • 1947ab339b43:老哥,上半部分是toolbar的联动效果?有Demo完整的源码不?
    桑小年:源码已经更新,老哥请重新批阅
  • gyymz1993:你好,有那个baserecycleview吗
    549da53acb37:@桑小年 这个baserecycleview库优化完了没?很想看看啊哈哈:smile:
    桑小年:源码已经更新,直接使用吧
    桑小年:这个库暂部分功能尚未优化完全,如果需要的话可以私信我

本文标题:来来来,随老夫撸一个支付宝玩玩——自定义Behavior的正确打

本文链接:https://www.haomeiwen.com/subject/gujvcxtx.html