CoordinatorLayout效果展示
CoordinatorLayout工作原理
CoordinatorLayout,可译为协调者布局,用于协调多个子View进行滑动交互
CoordinatorLayout实现滑动交互逻辑的核心是Behavior类,Behavior类规定子View间如何进行交互
CoordinatorLayout实现了一个自己的LayoutParam
CoordinatorLayout会为每个子View的LayoutParam设置一个Behavior
子View具体使用哪个Behavior,可以在xml布局中,通过自定义属性layout_behavior来指定
CoordinatorLayout在处理onTouchEvent时,会将事件转交给Behavior去处理,具体如何处理事件,是由Behavior中的逻辑来决定的
CoordinatorLayout交互动画
具体如何交互取决于Behavior代码,这里主要是介绍我们的Demo的动画逻辑
CoordinatorLayout包含三个子View,Text + Image + Toolbar
其中Text被NestedScrollView包裹,可以滑动
Toolbar在Image上层叠加显示,二者通过大小和透明度变化,形成滑动动画效果
默认状态:
Image完全展示,Toolbar透明度为0,Text滑动到顶端
Text向上滑动:
Toolbar透明度逐渐增加到100,Image逐渐变小,当Image和Toolbar一样大小时,不再变小,Toolbar透明度仍然继续增加
Text向下滑动:
Toolbar透明度逐渐减小到0,当Toolbar透明度为0时,Image逐渐变大,直到最大为止
NestedScrollView和CoordinatorLayout的关联
NestedScrollView是一个可以滚动的容器,它继承自FrameLayout,在基类的基础上增加了滑动功能
CoordinatorLayout继承自ViewGroup,它不处理任何布局功能,只是把布局工作转交给Behavior去处理
NestedScrollView和CoordinatorLayout联系起来的纽带在于
NestedScrollView实现了NestedScrollingChild接口,CoordinatorLayout实现了NestedScrollingParent接口
当Child进行滑动时,它会通知Parent,仅此而已
通过以上分析,我们基本可以确定这样一个事实
NestedScrollView仅负责滑动,CoordinatorLayout负责将布局事件和滑动事件等全部工作转发给子View
Behavior才是决定,子View如何进行布局,如何进行滑动交互的关键类
CoordinatorLayout的简单替代方案
其实,上面的动画,我们就算不用CoordinatorLayout,也可以很轻松地手动实现
无非就是监听ScrollView的滑动距离变化,然后控制Image大小和Toolbar透明度,这么简单的事情而已
其实CoordinatorLayout最终的工作,也就是这么简单而已
CoordinatorLayout出现的真正意义在于,它抽象了一套关于滑动交互逻辑的接口
使用CoordinatorLayout的话,全部的工作最后是交给Behavior去处理的
当我们需要使用新的子View,新的交互动画时,只要替换Behavior的实现类即可
可以看出,CoordinatorLayout的真正意义在于解耦,将交互动画的逻辑和控件解耦了,无需修改控件
如果我们用最简单的方法去实现,那么只要子View类型换一下,布局换一下,交互动画换一下,就得重新写一套控件
核心代码
<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="centerCrop"
android:src="@mipmap/main"
app:behavior="com.dongnao.lsn_16_classdemo.design.ImageBehavior"
app:max_height="200dp"
app:min_height="60dp" />
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="60dp"
android:alpha="0"
android:background="#3F51B5"
app:behavior="com.dongnao.lsn_16_classdemo.design.ToolbarBehavior"
app:threshold="150dp" />
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/image">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/large_text" />
</android.support.v4.widget.NestedScrollView>
</CoordinatorLayout>
@SuppressWarnings("all")
public class CoordinatorLayout extends RelativeLayout implements NestedScrollingParent, ViewTreeObserver.OnGlobalLayoutListener {
float lastX;
float lastY;
public CoordinatorLayout(Context context) {
super(context);
}
public CoordinatorLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
LayoutParams lp = (LayoutParams) v.getLayoutParams();
if (lp.behavior != null)
lp.behavior.onSizeChanged(this, v, w, h, oldw, oldh);
}
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return true;
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
LayoutParams lp = (LayoutParams) v.getLayoutParams();
if (lp.behavior != null)
lp.behavior.onNestedScroll(target, v, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
}
@Override
public void onGlobalLayout() {
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
LayoutParams lp = (LayoutParams) v.getLayoutParams();
if (lp.behavior != null)
lp.behavior.onLayoutFinish(this, v);
}
}
public class LayoutParams extends RelativeLayout.LayoutParams {
protected Behavior behavior;
public LayoutParams(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.MyCoordinatorLayout);
String clazzName = a.getString(R.styleable.MyCoordinatorLayout_behavior);
behavior = parseBehaivor(context, attributeSet, clazzName);
a.recycle();
}
public LayoutParams(int w, int h) {
super(w, h);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
public LayoutParams(RelativeLayout.LayoutParams source) {
super(source);
}
protected Behavior parseBehaivor(Context context, AttributeSet attrs, String name) {
try {
Class<?> aClass = Class.forName(name, true, context.getClassLoader());
Constructor c = aClass.getConstructor(new Class[]{Context.class, AttributeSet.class});
c.setAccessible(true);
return (Behavior) c.newInstance(context, attrs);
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}
}
@SuppressWarnings("all")
public class Behavior {
public Behavior(Context context, AttributeSet attrs) {
}
public void onLayoutFinish(View parent, View child) {
}
public void onSizeChanged(View parent, View child, int w, int h, int oldw, int oldh) {
}
public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
return false;
}
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
return false;
}
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return true;
}
public void onStopNestedScroll(View child) {
}
public void onNestedScrollAccepted(View child, View target, int axes) {
}
public void onNestedScroll(View scrollView, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
}
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
}
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return false;
}
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
}
@SuppressWarnings("all")
public class ToolbarBehavior extends Behavior {
Float threshold;
public ToolbarBehavior(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
TypedArray attributes = context.obtainStyledAttributes(attributeSet, R.styleable.MyCoordinatorLayout);
threshold = attributes.getDimension(R.styleable.MyCoordinatorLayout_threshold, 0);
}
@Override
public void onNestedScroll(View scrollView, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
float alpha = scrollView.getScrollY() / threshold;
if (alpha > 1)
alpha = 1;
target.setAlpha(alpha);
Toolbar toolbar = (Toolbar) target;
toolbar.setTitleTextColor(Color.WHITE);
toolbar.setSubtitleTextColor(Color.WHITE);
toolbar.setTitle("CoordinatorLayout");
toolbar.setSubtitle("a demo for coordinator layout");
}
}
@SuppressWarnings("all")
public class ImageBehavior extends Behavior {
Float minHeight;
Float maxHeight;
public ImageBehavior(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
TypedArray attributes = context.obtainStyledAttributes(attributeSet, R.styleable.MyCoordinatorLayout);
minHeight = attributes.getDimension(R.styleable.MyCoordinatorLayout_min_height, 0);
maxHeight = attributes.getDimension(R.styleable.MyCoordinatorLayout_max_height, 0);
}
@Override
public void onNestedScroll(View scrollView, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
int scroll = scrollView.getScrollY();
if (scroll > 0) {
ViewGroup.LayoutParams layoutParams = target.getLayoutParams();
layoutParams.height = layoutParams.height - Math.abs(dyConsumed);
if (layoutParams.height < minHeight)
layoutParams.height = minHeight.intValue();
target.setLayoutParams(layoutParams);
}
if (scroll == 0) {
ViewGroup.LayoutParams layoutParams = target.getLayoutParams();
layoutParams.height = layoutParams.height + Math.abs(dyUnconsumed);
if (layoutParams.height >= maxHeight)
layoutParams.height = maxHeight.intValue();
target.setLayoutParams(layoutParams);
}
}
}
源码下载
CoordinatorLayout源码解析和手动实现.zip
|