Android材料设计之Behavior攻坚战

本系列文章一览:

前言

Behavior应该和CoordinatorLayout一对的,这里单独说因为Behavior比较难一点
经过前面的bottom_sheet_behavior、appbar_scrolling_view_behavior 应该对 behavior有一定的认识
注意:改动自定义behavior路径时一定要改使用到的地方,不然肯定崩,一定要改!! 一定要改!!

本文内容:
  • 1.认识Behavior的使用方式
  • 2.自定义Behavior,分析layoutDependsOn回调和onDependentViewChanged回调
  • 3.自定义Behavior,分析onNestedScroll回调和onNestedPreScroll回调

一、简单认识
1.使用

在CoordinatorLayout和AppBarLayout那篇貌似也没有碰到Behavior啊
不过仔细想一下,好像有个地方比较特殊,那就是app:layout_behavior

<android.support.v7.widget.RecyclerView
        android:id="@+id/rv_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
2.string字符串:

再看一下这个string是什么鬼==>原来是一个安卓design包中内置的一个字符串
看起来很像一个类名有没有:AppBarLayout的内部类ScrollingViewBehavior

<string name="appbar_scrolling_view_behavior" translatable="false">android.support.design.widget.AppBarLayout$ScrollingViewBehavior</string>
3.对应类

果然有这个类:android.support.design.widget.AppBarLayout.ScrollingViewBehavior

    public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior {
        public ScrollingViewBehavior() {}
        public ScrollingViewBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
            final TypedArray a = context.obtainStyledAttributes(attrs,
                    R.styleable.ScrollingViewBehavior_Layout);
            setOverlayTop(a.getDimensionPixelSize(
                    R.styleable.ScrollingViewBehavior_Layout_behavior_overlapTop, 0));
            a.recycle();
        }
        //省略n行......
    }

二、自定义Behavior
1.既然安卓内部可以玩,那么我们也可以自定义玩玩
/**
 * 作者:张风捷特烈<br/>
 * 时间:2018/11/28 0028:17:02<br/>
 * 邮箱:1981462002@qq.com<br/>
 * 说明:最简单的behavior
 */
public class FirstBehavior extends CoordinatorLayout.Behavior<View> {
    public FirstBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof AppBarLayout;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        return true;
    }
}

2.模仿安卓内置behavior
<string name="behavior_first">com.toly1994.vvi_mds.v04_behavior.FirstBehavior</string>
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/al_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
            <TextView
                android:id="@+id/id_tv_moving"
                style="@style/TVTestCenter"
                android:background="@color/feise"
                app:layout_scrollFlags="scroll"
                android:text="Flag"/>
    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/behavior_first"/>

</android.support.design.widget.CoordinatorLayout>


3.使用结果:
9414344-f61b8acc554642c0.gif
自定义Behavior.gif

4.关于几个参数

debug一下,应该很清楚的看出viewdependency分别对应的是什么

9414344-8b23f71b16d58d12.png
debug.png
总得来说behavior就是:
在CoordinatorLayout中,让AppBarLayout里的首控件和添加behavior的控件进行联动,而实现酷炫逆天的效果,
其中child为添加了behavior的那个控件,dependency为AppBarLayout。

三、对第一个Behavior的分析

目测:当dependcy移动自身高度之后onDependentViewChanged将不再回调

1.对dependcy的操作

既然两个View都在手上,那玩玩呗:让移动时TextView的背景色随机变化,这些知道dependcy是谁了吧

9414344-c3d8f60c4818879a.gif
移动时变化dependcy.gif
/**
 * 确定使用Behavior的View要依赖的View的类型:
 * 返回false:onDependentViewChanged不触发
 *
 * @param parent     CoordinatorLayout布局容器
 * @param child      装载behavior的控件
 * @param dependency 被联动的控件
 * @return
 */
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
    return dependency instanceof AppBarLayout;
}

/**
 * 当被依赖的View状态改变时回调
 *
 * @param parent     CoordinatorLayout布局容器
 * @param child      装载behavior的控件
 * @param dependency 被联动的控件
 * @return
 */
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
    ViewGroup dep = (ViewGroup) dependency;//AppBarLayout
    View childTv = dep.getChildAt(0);//获取第一个孩子
    childTv.setBackgroundColor(ColUtils.randomRGB());//背景设置随机色
    return true;
}

2.几个重要的参数:

注意:为了看一下getY和getTop的区别,这里特意setTranslationY(100)
可以看出getY包含了setTranslationY的值,getTop不包括setTranslationY,所以按需使用(如果没有平移,随便用)
可以看到移动的有效长度是dependency的高度,一旦超过onDependentViewChanged将不再回调

9414344-1175b7ac35f88ed1.png
dependency移动分析.png
9414344-e224025fa839136d.gif
dependency测试.gif
/**
 * 当被依赖的View状态改变时回调
 *
 * @param parent     CoordinatorLayout布局容器
 * @param child      装载behavior的控件
 * @param dependency 被联动的控件
 * @return
 */
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
    dependency.setTranslationY(100);
    float dependencyY = dependency.getY();
    int dependencyHeight = dependency.getHeight();
    int dependencyTop = dependency.getTop();
    Log.e("onDependentViewChanged","dependencyY:" + dependencyY + ",dependencyHeight:" + dependencyHeight + ",dependencyTop:" + dependencyTop + L.l());

    int childHeight = child.getHeight();
    float childY = child.getY();
    int childTop = child.getTop();

    Log.e("onDependentViewChanged","childHeight:" + childHeight + ",childY:" + childY + ",childTop:" + childTop + L.l());
    return true;
}

3.判断dependency位移方向,让child进行联动

这里处理很简单:将child反方向进行移动,但效果看起来还不错
在布局中加入了一个TextView占一下视觉空间,不然空空的不好看

9414344-3434b5b9ff92a618.gif
联动.gif
/**
 * 当被依赖的View状态改变时回调
 *
 * @param parent     CoordinatorLayout布局容器
 * @param child      装载behavior的控件
 * @param dependency 被联动的控件
 * @return
 */
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
    child.setTranslationY(-dependency.getY());
    return true;
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/al_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
            <TextView
                android:id="@+id/id_tv_moving"
                style="@style/TVTestCenter"
                android:background="@color/feise"
                app:layout_scrollFlags="scroll"
                android:text="Flag"/>
    </android.support.design.widget.AppBarLayout>


    <TextView
        android:id="@+id/hide"
        style="@style/TVTestCenter"
        android:background="@color/yase"
        app:layout_scrollFlags="scroll"
        android:text="Find Me"/>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/behavior_first"/>

</android.support.design.widget.CoordinatorLayout>

4.移动方向和分度值
9414344-92328fd1bb067394.gif
获取移动分度值.gif
//添加成员变量
private float curY;

 /**
  * 当被依赖的View状态改变时回调
  *
  * @param parent     CoordinatorLayout布局容器
  * @param child      装载behavior的控件
  * @param dependency 被联动的控件
  * @return
  */
 @Override
 public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
     float dy = dependency.getTop() - curY; //dy>0 ----下移
     float faction;//移动的分度值
     if (dy <= 0) {//上移动
         faction = -dependency.getTop() * 1.f / dependency.getHeight();
     } else {
         faction = 1 - (-dependency.getTop() * 1.f / dependency.getHeight());
     }
//  dependency.setTranslationY(-dependency.getTop());//让dependency不移动
    dependency.setPivotX(dependency.getWidth()/2);//旋转
    dependency.setPivotY(dependency.getHeight()/2);
    dependency.setRotation(360 * faction);//旋转
    child.setTranslationY(-curY);
    curY = dependency.getY();//更新curY
    return true;
}

四、自定义FloatingActionButton伴随列表移动

onNestedScroll和onNestedPreScroll

1.布局
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/id_fab_b"
        android:layout_width="50dp"
        android:layout_height="50dp"
        app:layout_behavior="@string/behavior_second"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/dp_32"
        android:src="@drawable/icon_love"/>

</android.support.design.widget.CoordinatorLayout>

2.自定义Behavior:
9414344-d23d399971d39cfd.png
onNestedScroll.png

接触目标view时才会回调:onStartNestedScroll
加了layout_behavior的View是child

平移缩放
9414344-ab7dea5b9f18a583.gif
9414344-683c150a4135d371.gif
/**
 * 作者:张风捷特烈<br/>
 * 时间:2018/11/30 0030:14:34<br/>
 * 邮箱:1981462002@qq.com<br/>
 * 说明:FloatingActionButton伴随动画
 */
public class FabFollowListBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
    private static final int MIN_DY = 30;
    private static final String TAG = "FabFollowListBehavior";

    public FabFollowListBehavior(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
    }

    /**
     * 初始时不调用,滑动时调用---一次滑动过程,之调用一次
     */
    @Override
    public boolean onStartNestedScroll(
            @NonNull CoordinatorLayout coordinatorLayout,
            @NonNull FloatingActionButton child,
            @NonNull View directTargetChild,
            @NonNull View target, int axes, int type) {
        return true;
    }

    /**
     * @param dyConsumed 每次回调前后的Y差值
     */
    @Override
    public void onNestedScroll(
            @NonNull CoordinatorLayout coordinatorLayout,
            @NonNull FloatingActionButton child,
            @NonNull View target, int dxConsumed,
            int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);

        //平移隐现
        if (dyConsumed > MIN_DY) {//上滑:消失
            showOrNot(coordinatorLayout, child, false).start();
        } else if (dyConsumed < -MIN_DY) {//下滑滑:显示
            showOrNot(coordinatorLayout, child, true).start();
        }

        //仅滑动时消失
//        if (dyConsumed > MIN_DY || dyConsumed < -MIN_DY) {//上滑:消失
//            showOrNot(child).start();
//        }
    }

    private Animator showOrNot(CoordinatorLayout coordinatorLayout, final View fab, boolean show) {
        //获取fab头顶的高度
        int hatHeight = coordinatorLayout.getBottom() - fab.getBottom() + fab.getHeight();
        int end = show ? 0 : hatHeight;
        float start = fab.getTranslationY();
        ValueAnimator animator = ValueAnimator.ofFloat(start, end);
        animator.addUpdateListener(animation ->
                fab.setTranslationY((Float) animation.getAnimatedValue()));
        return animator;
    }

    private Animator showOrNot(final View fab) {
        //获取fab头顶的高度
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1);

        animator.addUpdateListener(animation -> {
            fab.setScaleX((Float) animation.getAnimatedValue());
            fab.setScaleY((Float) animation.getAnimatedValue());
        });
        return animator;
    }
}

Behavior基本概念就这样,由于并不是非常常用,更深的用法等有需求再深究吧


后记:捷文规范
1.本文成长记录及勘误表
项目源码日期备注
V0.1--github2018-11-30Android材料设计之Behavior攻坚战
2.更多关于我
笔名QQ微信爱好
张风捷特烈1981462002zdl1994328语言
我的github我的简书我的掘金个人网站
3.声明

1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持


9414344-8a0c95a090041a0d.png
icon_wx_200.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值