一、Behavior是什么鬼?
Behavior是CoordinatorLayout的一个静态抽象内部类,拥有两个构造函数,一个泛型。
public static abstract class Behavior<V extends View> {
public Behavior() {}
public Behavior(Context context, AttributeSet attrs) {}
......
......
}
这个泛型的参数View是指定使用这个Behaior的View,其他的view是不能使用,比如AppBarLayout里面的Behavior就传入了AppBarLayout。
000.PNG
二、Behavior 能干嘛了?
三、怎么干?
想干事总要有几套功法,所以Behavior给我们提供了几招制敌之法,下面我们来一一的看下:
- layoutDependsOn();确定你是否依赖那些view,所依赖的view改变时,使用当前Behavior的view作出相应的变化。
- onDependentViewChanged();当依赖的view发生变化时,使用当前Behavior的view作出相应的变化。
- onStartNestedScroll();依赖的view开始发生滑动变化(所以来的view必须实现NestedScrollingChild,否则将不支持该类滑动方法。系统只有NestedScrollView和RecyclerView实现了)
- onNestedScroll();依赖的view发生滑动变化
- onNestedFling();当依赖view快速滑动式调用
四、开搞
首先我们实现一下这样的效果(模仿):
04.gif- 第一步,新建一个类叫做MyBehavior继承致CoordinatorLayout.Behavior,这里不传入泛型参数,所以得view都可以用。然后写需要依赖view实现什么效果代码如下:
public class MyBehavior extends CoordinatorLayout.Behavior {
private static final String STRING = "TOP";
Context mContext;
public MyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
}
//当依赖的view发生改变时
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
//始终处于topvView的下方
child.setY(dependency.getY()+dependency.getHeight());
return true;
}
//确定是否依赖该view,ture为依赖
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return STRING.equals(dependency.getTag());
}
}
- 第二步,新建一个布局,给需要显示在上面的view添加一个TAG值为代码中STRING 的值,然后给需要的view添加layout_behavior属性值为MyBehavIor的包路径,全部代码如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent" android:fitsSystemWindows="true">
<View
android:id="@+id/top"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#aec"
android:tag="TOP" />
<View
android:id="@+id/bottom"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#fce"
app:layout_behavior=".MyBehavior"
/>
</android.support.design.widget.CoordinatorLayout>
Activity代码实现如下:
public class BehaviorAct extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.behavior_layout);
/**
* 在上面的view响应事件进行滑动
*/
findViewById(R.id.top).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_MOVE){
v.setY(event.getRawY());
}
return true;
}
});
}
}
05.gif然后我们在实现这样的一个效果
代码编写步骤和上面一样,只是在新建的Behaivor中使用的时上文上述方法中的后三种,代码如下所示:
public class ScrollBehavior extends CoordinatorLayout.Behavior {
int offsetTotal = 0;
private int childHeight;//滑动子项的高度,也是上滑控制的极限
private boolean isScrolling = true;
private int beforValue = 0;
private int count;
public ScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
//继承的
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
/**
* 这里要返回true,才会接收到后续的滑动事件
* (理解:也就是说我们在这里可以做各种判断来确定,要操作的view是否要对下一步滑动作出反应)
*/
childHeight = child.getHeight();
return true;
}
//对滑动事件作出处理
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, final View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
Log.e("sun","数据******"+dyConsumed);
set(child, dyConsumed);
}
private void set(View child, int dyConsumed) {
int old = offsetTotal;//上一次的位置
int top = old - dyConsumed;//这一次应该到达的位置
top = Math.max(top,-childHeight);//上滑时和子view的高度比较(这样在如果在上偏移(top)超过view高度时,能防止偏移过度)
top = Math.min(top,0);//下滑时和0比较(这样如果向下偏移过度)
offsetTotal = top;//将这次的位置记录下来
if(old == top){
return;
}
int data = top - old;
child.offsetTopAndBottom(data);
}
//进行快速滑动调用的方法
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed) {
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
}
布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="40sp"
android:text="A\n B\nC\n D\nE\n F\nG\n H\nI\n J\nK\n M\nN\n O\nP\n Q\nR\n S\nA\n B\nC\n D\nE\n F\nG\n H\nI\n J\nK\n M\nN\n O\nP\n Q\nR\n S\n"/>
</android.support.v4.widget.NestedScrollView>
<View
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#afa"
app:layout_behavior=".ScrollBehavior"/>
</android.support.design.widget.CoordinatorLayout>
思考:第一种与第二种的实现机制是怎样的呢?
首先第一种,CoordinatorLayout初始化时会遍历他的子view,如果有时使用Behavior的就会通过反射建立相应Behavior,我粗略的翻看了源码(实乃菜鸟,太高深的也不懂...),其下有这样的方法:
而当我们所依赖的view发生变化时,CoordinatorLayout在其内部实现了ViewTreeObserver.OnPreDrawListener的接口,该接口是在即将进行绘图之前调用( ),也就是用来监听view发生位置变化,因为我们对依赖view的onTouch事件进行了重写并改变了控件位置系统会对视图进行重新绘制,然后就会调用*** onChildViewsChanged() 方法。
003.PNG
在 onChildViewsChanged() *** 方法中首先遍历所有的子view,检查每一个子view所依赖的view是否是其它的子view,如果是调用*** offsetChildToAnchor(child, layoutDirection); *** 而在该方法中会获取子view的behavior实例,最终调用Behavior的*** onDependentViewChanged() 方法。而后还有一层循环,通过 onChildViewsChanged() 方法传进来的type,进行判断操作,最后也是调用Bhavior的 onDependentViewChanged() ***方法,从而实现对内部视图的控制。 004.PNG 005.PNG 006.PNG