android-CoordinatorLayout之Behavior

1. 简单使用

CoordinatorLayout作为一个中间桥梁性质的布局,协调着内部的childView。之前对CoordinatorLayout有点误解,以为需要配合AppBarLayout才有一些比较炫酷的特效,大错特错,Behavior是CoordinatorLayout能够有协调作用以及能支持各种炫酷特效的的关键因素

1.1 布局文件中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="#ffffff"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#ff0000"
android:gravity="center"
android:text="Hello World"
android:textColor="#ffffff"
android:textSize="18sp"
app:layout_behavior="@string/behavior_sample_title" />
<com.example.zengwei.coord.MoveView
android:text="你好"
android:textColor="#2b2b2b"
android:gravity="center"
android:background="#0188FB"
android:layout_marginTop="200dp"
android:layout_width="100dp"
android:layout_height="100dp" />
</android.support.design.widget.CoordinatorLayout>

最关键的地方就在于app:layout_behavior,利用这个属性来确定的绑定的目标childView

指定绑定目标有3种方式:

1.在xml布局通过app:layout_behavior
2.在Java代码中,child.getLayoutParams().setBehavior()来指定
3.在目标childView类上,通过@DefaultBehavior来指定

1.2 MoveView

MoveView就是一个继承TextView的很简单的自定义View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MoveView extends TextView {
private float lastX, lastY;

public MoveView(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
float x = event.getRawX();
float y = event.getRawY();
if (action == MotionEvent.ACTION_MOVE) {
CoordinatorLayout.MarginLayoutParams layoutParams = (CoordinatorLayout.MarginLayoutParams) getLayoutParams();
//计算当前的左上角坐标
float left = layoutParams.leftMargin + x - lastX;
float top = layoutParams.topMargin + y - lastY;
//设置坐标
layoutParams.leftMargin = (int) left;
layoutParams.topMargin = (int) top;
setLayoutParams(layoutParams);
}
lastX = x;
lastY = y;
return true;
}
}

主要就是重写onTouchEvent()来使MoveView可以根据手指滑动在屏幕改变位置

1.3 一个简单的自定义Behavior

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class SampleTitleBehavior extends CoordinatorLayout.Behavior<View> {
// 列表顶部和title底部重合时,列表的滑动距离。
private float deltaY;
boolean aBoolean=true;
public SampleTitleBehavior() {
}
public SampleTitleBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}

/**
* 使用该Behavior的View要监听哪个类型的View的状态变化。
* 其中参数parant代表CoordinatorLayout,child代表使用该Behavior的View,dependency代表要监听的View。
* 这里要监听RecyclerView
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof MoveView;
}

/**
* 当被监听的View状态变化时会调用该方法,参数和上一个方法一致。
* 所以我们重写该方法,当MoveView的位置变化时,进而改变title的位置。
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
if(aBoolean){
deltaY=dependency.getY();
aBoolean=false;
}
if(deltaY-dependency.getY()<=150){
child.setY(dependency.getY()-deltaY);
}
return true;
}

}

CoordinatorLayout.Behavior这里使用泛型将绑定的childView限制为了TextView,可以根据实际需求来指定类型,也可以直接指定为View

注意:
当在布局文件中使用了Behavior后,Behavior代码中确定的交互行为便直接奏效,初始化第一次加载CoordinatorLayout时,使用了Behavior的ChildView受到onDependentViewChanged()方法的影响,第一次加载的位置也会受到影响,导致和布局文件中指定的位置不相同

官方有好几个非常好的学习资料,例如:
android.support.design.widget.AppBarLayout$ScrollingViewBehavior

一个依赖AppBarLayout后,处理滑动事件的Behavior,对Behavior中的属性及方法有了大概了解后,可以学习具体细节的设计和优化

2. Behavior 行为

需要注意的是Behavior可以几乎包括所有的交互行为,配合ViewDragHelper应该能够实现出一些很炫酷的交互效果

2.1 常用的方法

构造方法有两个:
默认:public Behavior() {}
布局:public Behavior(Context context, AttributeSet attrs) { }
两个构造方法也比较容易理解,一个是默认的空参的构造方法,一个是带有布局属性AttributeSet的方法,有了这个构造方法,可以直接在布局文件中使用

根据Behavior的特性,可以将内部的方法分以下类:

测量与布局:

1
2
测量:public boolean onMeasureChild(){}
布局:public boolean onLayoutChild(){}

特定状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//当Behavior添加到参数实例时,回调
public void onAttachedToLayoutParams(){}

//当Behavior与参数实例分离时,回调
public void onDetachedFromLayoutParams(){}

//当Behavior关联的对象想要定位到特定的矩形时,回调
public boolean onRequestChildRectangleOnScreen(){}

//当一个ChildView设置为回避属性时,回调
public boolean getInsetDodgeRect(){}

//当窗口发生改变时,回调
public WindowInsetsCompat onApplyWindowInsets(){}

//需要保存临时状态信息,回调
public Parcelable onSaveInstanceState(){}

//需要恢复临时状态信息,回调
public void onRestoreInstanceState(){}

//作用未知
public int getScrimColor(){}

//作用未知
public float getScrimOpacity(){}

确定依赖与绑定对象:

1
2
//根据参数来确定依赖与绑定对象
public boolean layoutDependsOn(){}

当依赖对象发生改变时:

1
2
3
4
5
//当依赖对象发生改变,包括位置,大小,颜色,进行回调
public boolean onDependentViewChanged(){}

//当依赖对象被移除时,进行回调
public void onDependentViewRemoved(){}

事件相关:

1
2
3
4
5
//拦截事件,在CoordinatorLayout把事件分发到childView之前
public boolean onInterceptTouchEvent(){}

//消费事件
public boolean onTouchEvent(){}

嵌套滑动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//CoordinatorLayout中的滑动嵌套childView开始启动一次嵌套滚动时,回调
public boolean onStartNestedScroll(){}

//嵌套滑动结束时,回调
public void onStopNestedScroll(){}

//当一次嵌套滑动被CoordiantorLayout识别并确定时,进行回调
public void onNestedScrollAccepted(){}

//嵌套滚动正在进行中并且绑定目标childView已经开始滚动或者被CoordinatorLayout接受后试图滚动
public void onNestedScroll(){}

//嵌套滚动正在准备更新进度,并且是在绑定目标childView已经出现滚动距离之前,回调
public void onNestedPreScroll(){}

//当嵌套滚动的childView正在开始fling或者一个动作确认为fling
public boolean onNestedFling(){}

//当滑动嵌套childView检测到适当的条件,马上开始一次fling事件前回调
public boolean onNestedPreFling(){}

暂时就这么分,分类并不算合理,也无所谓,目的是以后自己回头来看时,能比较清晰能快速定位方法是干嘛的
转:https://www.jianshu.com/p/9fdd271541d9