参考放在首位
Behavior抽象类
public abstract static class Behavior<V extends View> {
public Behavior() {
}
public Behavior(Context context, AttributeSet attrs) {
}
public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {
}
//......省去众多方法
}
Behavior是CoordinatorLayout的一个内部泛型抽象类。内部类中指定的view类型规定了哪种类型的view的可以使用才Behavior。因此,如果没有特殊需求,直接指定view为View就行了。
方法简介
- onInterceptTouchEvent():是否拦截触摸事件
- onTouchEvent():处理触摸事件
- layoutDependsOn():确定使用Behavior的View要依赖的View的类型
- onDependentViewChanged():当被依赖的View状态改变时回调
- onDependentViewRemoved():当被依赖的View移除时回调
- onMeasureChild():测量使用Behavior的View尺寸
- onLayoutChild():确定使用Behavior的View位置
- onStartNestedScroll():嵌套滑动开始(ACTION_DOWN),确定Behavior是否要监听此次事件
- onStopNestedScroll():嵌套滑动结束(ACTION_UP或ACTION_CANCEL)
- onNestedScroll():嵌套滑动进行中,要监听的子 View的滑动事件已经被消费
- onNestedPreScroll():嵌套滑动进行中,要监听的子 View将要滑动,滑动事件即将被消费(但最终被谁消费,可以通过代码控制)
- onNestedFling():要监听的子 View在快速滑动中
- onNestedPreFling():要监听的子View即将快速滑动
(来源第一个链接,偷懒直接复制的)
方法使用情况
使用大概分为两种情况:
- 某个view需要根据监听另一个的行为来控制自己的行为,这个时候我们需要重写2个方法:
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
return false;
}
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
return false;
}
- 我们的view需要根据监听CoordinatorLayout中的子view的滚动行为来改变自己的状态,现在我们就需要重写下面的方法了:
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull TextView child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
return true;
}
@Override
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull TextView child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}
示例
图中布局分两个部分,头部和列表部分。上滑的时候头部向上偏移,下滑的时候头部向下偏移出现。
这里头部就只使用一个TextView。
自定义Behavior步骤
- 自定义Behavior。(demo继承CoordinatorLayout.Behavior)
- 添加字符串资源。(如:< string name=“behavior_sample_footer”>demo.com.SampleHeaderBehavior</ string>)
- 在布局中添加 app:layout_behavior属性。
1.自定义SampleHeaderBehavior
public class SampleHeaderBehavior extends CoordinatorLayout.Behavior<TextView> {
public SampleHeaderBehavior() {
}
public SampleHeaderBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull TextView child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
return true;
}
@Override
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull TextView child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
//上滑
if (dy > 0) {
float offest = child.getY() - dy;
if (Math.abs(offest) > child.getHeight()) {
offest = -child.getHeight();
} else {
//头部没有完全消失,偏移让CoordinatorLayout消费
consumed[1] = dy;
}
child.setTranslationY(offest);
}
//下滑
if (dy < 0) {
float offest = child.getY() - dy;
if (offest > 0) {
offest = 0;
}
child.setTranslationY(offest);
}
}
}
- onNestedPreScroll方法:返回true将会回调onNestedPreScroll方法。
- onNestedPreScroll方法:child是要设置Behavior的view,target为要监听的view。
因为头部是根据recyclerview的滑动来改变自身位置的,所以用到了这两个方法
dy为手指移动的距离,头部最大偏移距离为-height,最小距离为0。
consumed[1] = dy表示滑动事件由CoordinatorLayout消费
可以看出,下滑的时候滑动是完全交给recyclerview的,所以效果是如果recyclerview下滑的同时,头部也在向下偏移。如果代码修改如下:
//下滑
if (dy < 0) {
float offest = child.getY() - dy;
if (offest > 0) {
offest = 0;
} else {
consumed[1] = dy;
}
child.setTranslationY(offest);
}
能想象出是什么效果吗?
自定义RecyclerViewBehavior
要让recyclerview一直在头部的下方。
public class RecyclerViewBehavior extends CoordinatorLayout.Behavior<RecyclerView> {
public RecyclerViewBehavior() {
}
public RecyclerViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, RecyclerView child, View dependency) {
return dependency instanceof TextView;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, RecyclerView child, View dependency) {
//计算列表y坐标,最小为0
float y = dependency.getHeight() + dependency.getTranslationY();
if (y < 0) {
y = 0;
}
child.setY(y);
return true;
}
}
因为是根据头部的位置,来确定自身的位置,所以用了这两个方法
child就是要设置Behavior的recyclerview,根据头部位置,确定它的位置。
2.添加字符串资源
<string name="behavior_sample_header">com.example.custombehaviordemo.custumizes.SampleHeaderBehavior</string>
<string name="behavior_recyclerview">com.example.custombehaviordemo.custumizes.RecyclerViewBehavior</string>
只需要在string.xml文件夹中添加Behavior类的全路径。
3.xml布局添加Behavior属性
<?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">
<TextView
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#ff0000"
android:gravity="center"
android:text="Hello World"
android:textColor="#ffffff"
android:textSize="18sp"
app:layout_behavior="@string/behavior_sample_header" />
<android.support.v7.widget.RecyclerView
android:id="@+id/my_list"
android:layout_width="match_parent"
app:layout_behavior="@string/behavior_recyclerview"
android:layout_height="wrap_content" />
</android.support.design.widget.CoordinatorLayout>
因为自定义Behavior的时候继承的是CoordinatorLayout.Behavior,所以外层父布局,用的是CoordinatorLayout。为什么CoordinatorLayout可以使用Behavior?可以看看CoordinatorLayout源码分析和自定义类似的ViewGroup的系列文章(我还没看 )。
Activity中直接正常使用。
省略代码。。。。。
最后提示:应该是个人逻辑能力问题,方法调用会理解,但是关于位置偏移的处理理解较慢。虽然花些时间,但是学会了自己想学的东西。