本系列文章一览:
- Android材料设计Material Design 开篇前言
- Android材料设计之ToolBar+CardView+沉浸标题栏
- Android材料设计之FloatingActionButton+Snackbar+SheetX3
- Android材料设计之BottomNavigationBar+TabLayout
- Android材料设计之AppBarLayout+CoordinatorLayout
- Android材料设计之CollapsingToolbarLayout+Palette
- Android材料设计之DrawerLayout+NavigationView+TextInputLayout
- 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.使用结果:
自定义Behavior.gif4.关于几个参数
debug.pngdebug一下,应该很清楚的看出
view
和dependency
分别对应的是什么
总得来说behavior就是:
在CoordinatorLayout中,让AppBarLayout里的首控件和添加behavior的控件进行联动,而实现酷炫逆天的效果,
其中child为添加了behavior的那个控件,dependency为AppBarLayout。
三、对第一个Behavior的分析
目测:当dependcy移动自身高度之后
onDependentViewChanged
将不再回调
1.对dependcy的操作
移动时变化dependcy.gif既然两个View都在手上,那玩玩呗:让移动时TextView的背景色随机变化,这些知道dependcy是谁了吧
/**
* 确定使用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.几个重要的参数:
dependency移动分析.png dependency测试.gif注意:为了看一下getY和getTop的区别,这里特意
setTranslationY(100)
可以看出getY包含了setTranslationY的值,getTop不包括setTranslationY,所以按需使用(如果没有平移,随便用)
可以看到移动的有效长度是dependency的高度,一旦超过onDependentViewChanged
将不再回调
/**
* 当被依赖的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进行联动
联动.gif这里处理很简单:将child反方向进行移动,但效果看起来还不错
在布局中加入了一个TextView占一下视觉空间,不然空空的不好看
/**
* 当被依赖的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.移动方向和分度值
获取移动分度值.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:
onNestedScroll.png接触目标view时才会回调:onStartNestedScroll
加了layout_behavior的View是child
平移 | 缩放 |
---|---|
/**
* 作者:张风捷特烈<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--github | 2018-11-30 | Android材料设计之Behavior攻坚战 |
2.更多关于我
笔名 | 微信 | 爱好 | |
---|---|---|---|
张风捷特烈 | 1981462002 | zdl1994328 | 语言 |
我的github | 我的简书 | 我的掘金 | 个人网站 |
3.声明
1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持
icon_wx_200.png
网友评论