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

桑小年 / 文 发表于2017-07-25 08:41 次阅读 仿支付宝,behavior,自定义view,android

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


![GIF.gif](http://upload-images.jianshu.io/upload_images/2599466-3a7bd996bd887dac.gif?imageMogr2/auto-orient/strip)


要实现这样一个效果,首先想到的自然就是 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,可以直接运行的,但是大家记得偷偷的拿代码,打枪的不要,因为老板知道了,会把我的屎都打出来的。。。。

(源码已经更新,谢谢各位了,赶紧拿走吧)

最后:[源码](https://github.com/sangxiaonian/AliBehaver)


收藏 赞 (4) 踩 (3)