一、Behavior的介绍
1、什么是Behavior
上篇文章我们讲到CoordinatorLayout
配合AppBarLayout
、CollapsingToolbarLayout
实现了Toolbar的隐藏和折叠,但他们之间能够进行交互,其实就是通过一个介质CoordinatorLayout.Behavior
实现的。Behavior
是CoordinatorLayout
用来和各个子View通信用的代理类,用来协调CoordinatorLayout
的Child Views之间的交互行为,但使用的前提是Behavior
只有是CoordinatorLayout
的直接子View才有意义。
2、引用Behavior的两种方式
- app:layout_behavior布局属性
在布局中设置,值为自定义Behavior类的名字字符串(包含路径) - @CoordinatorLayout.DefaultBehavior类注解
在需要使用Behavior的控件源码定义中添加该注解,然后通过反射机制获取
二、Behavior 的两种机制
1、Dependent 机制
1.1 Dependent 介绍
这种机制主要是用来描述 两个Child View 之间的绑定依赖关系,设置Behavior 属性的Child View 跟随依赖对象Dependency View 的大小位置改变而发生改变,因此它用于一个View 监听另一个View 的状态变化。我们需要通过两个方法,来实现绑定:
/**
* 确定要依赖的对象
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return super.layoutDependsOn(parent, child, dependency);
}
/**
* 当依赖的对象发生变化时会自动回调这个方法
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
return super.onDependentViewChanged(parent, child, dependency);
}
1.2、举例说明 Dependent 机制
可以看到,我对红色方块 进行拖动的时候,绿色的方块也发生了位置变化。
先自定义一个DependentBehavior,代码如下:
public class DependentBehavior extends CoordinatorLayout.Behavior<View> {
/**
* 构造方法
*
* @param context
* @param attrs
*/
public DependentBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 确定要依赖的对象
*
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency.getId() == R.id.btn_first;
}
/**
* 当依赖的对象发生变化时会自动回调这个方法
*
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
// 根据要依赖的x坐标,更改当前控件的X坐标
child.setX(parent.getWidth() - dependency.getX() - dependency.getWidth());
child.setY(dependency.getY());
return super.onDependentViewChanged(parent, child, dependency);
}
}
activity_dependent.xml 代码:
<?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:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_first"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="100dp"
android:background="@android:color/holo_red_light" />
<Button
android:id="@+id/btn_two"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="right"
android:layout_marginRight="20dp"
android:layout_marginTop="100dp"
android:background="@android:color/holo_green_light"
app:layout_behavior=".DependentBehavior" />
</android.support.design.widget.CoordinatorLayout>
主界面代码:
public class DependentActivity extends AppCompatActivity implements View.OnTouchListener {
private Button mbtnFirst;
private int lastX, lastY; //保存手指点下的点的坐标
private int mWidth, mHeight;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dependent);
mbtnFirst = (Button) findViewById(R.id.btn_first);
//设置屏幕触摸事件
mbtnFirst.setOnTouchListener(this);
}
/**
* 当activity 的焦点发生改变时(View 已经绘制完成,可以获得宽高)
*
* @param hasFocus
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
mWidth = mbtnFirst.getWidth();
mHeight = mbtnFirst.getHeight();
}
/**
* 触屏事件处理
*
* @param view
* @param event
* @return
*/
@Override
public boolean onTouch(View view, MotionEvent event) {
// 处理多点触摸的ACTION_POINTER_DOWN和ACTION_POINTER_UP事件
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//将点下的点的坐标保存
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//计算出需要移动的距离
int dx = (int) event.getRawX() - lastX;
int dy = (int) event.getRawY() - lastY;
//将移动距离加上,现在本身距离边框的位置
int left = view.getLeft() + dx;
int top = view.getTop() + dy;
//获取到layoutParams然后改变属性,在设置回去
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) view
.getLayoutParams();
layoutParams.height = mHeight;
layoutParams.width = mWidth;
layoutParams.leftMargin = left;
layoutParams.topMargin = top;
view.setLayoutParams(layoutParams);
//记录最后一次移动的位置
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
break;
}
return true;
}
}
2、Nested机制
2.1 Nested 介绍
Nested机制要求CoordinatorLayout包含了一个实现了NestedScrollingChild接口的滚动视图控件,比如v7包中的RecyclerView,设置Behavior属性的Child View会随着这个控件的滚动而发生变化,涉及到的方法有:
onStartNestedScroll(View child, View target, int nestedScrollAxes)
onNestedPreScroll(View target, int dx, int dy, int[] consumed)
onNestedPreFling(View target, float velocityX, float velocityY)
onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
onNestedFling(View target, float velocityX, float velocityY, boolean consumed)
onStopNestedScroll(View target)
下面我将简单描述下四个方法:
/**
* 会遍历每一个 子View,询问它们是否对滚动列表的滚动事件感兴趣,若 Behavior.onStartNestedScroll 方法返回 true,
* 则表示感兴趣,那么滚动列表后续的滚动事件都会分发到该 子View的Behavior
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int axes, int type) {
return true;
}
/**
* 处理 子View 的滚动事件
*/
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed, int type) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}
/**
* 检测滚动距离的最终消费情况,可以继续处理 滚动事件
*/
@Override
public void onNestedScroll( CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
}
/**
* 检测到滚动事件的结束
*/
@Override
public void onStopNestedScroll( CoordinatorLayout coordinatorLayout, View child, View target, int type) {
super.onStopNestedScroll(coordinatorLayout, child, target, type);
}
2.2、举例说明 Nested 机制
可以看到,当下拉列表时,红色布局就会消失,向上滑动时,红色布局就会出现。
先创建一个NestedBehavior,代码如下:
public class NestedBehavior extends CoordinatorLayout.Behavior<View> {
int offsetTotal = 0;
public NestedBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 会遍历每一个 子View,询问它们是否对滚动列表的滚动事件感兴趣,若 Behavior.onStartNestedScroll 方法返回 true,
* 则表示感兴趣,那么滚动列表后续的滚动事件都会分发到该 子View的Behavior
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int axes, int type) {
return true;
}
/**
* 处理 子View 的滚动事件
*/
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed, int type) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
offset(child, dy);
}
public void offset(View child, int dy) {
// 上次保存的位置
int old = offsetTotal;
// 当前的位置
int curr = offsetTotal - dy;
// 保证子控件的位置一直在 0-控件高度之间
curr = Math.max(curr, -child.getHeight());
curr = Math.min(curr, 0);
offsetTotal = curr;
if (old == offsetTotal) {
return;
}
// 原来的位置 - 当前的位置 = 要移动的位置
int delta = old - offsetTotal;
child.offsetTopAndBottom(delta);
}
}
activity_nested.xml 代码:
<?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/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:background="@android:color/holo_red_light"
android:gravity="center"
app:layout_behavior=".NestedBehavior">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="自定义Behavior"
android:textColor="@android:color/white"
android:textSize="20sp" />
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>