一 效果图
二 需求
- 组件: 标题+背景和滑动控件(显示内容)
- 当向上滑动时,标题渐渐不透明,图片逐渐变小,直到标题完全显示,图片不显示
- 当向下滑动时,显示背景图片并局域变大,标题变不透明,直到不能滑动,图片完全显示,标题完全不显示
三 CoordinatorLayout分析
CoordinatorLayout有两个主要的用法:
- 作为顶层应用的装饰或者chrome的布局;
- 作为一个能响应特定的一个或多个子视图交互的容器。
Behavior作为一个中间者,目的是协调 子控件之间的触摸事件,
NestedScrolling机制:
内部View在滚动的时候,首先将dx,dy交给NestedScrollingParent,NestedScrollingParent可对其进行部分消耗,剩余的部分还给内部View。
四 需求分析
- 自定义CoordinatorLayout处理事件,Behavior传递事件
- 标题为ToolBar,图片用ImageView,内容页用一个滑动控件NestedScrollView
- 分别定义不同的Behavior,接收事件,作用在ToolBar和ImageView上,实现渐变和缩小效果
public interface NestedScrollingParent {
onStartNestedScroll.当子view的调用NestedScrollingChild的方法startNestedScroll时,会调用该方法.
一定要按照自己的需求返回true,该方法决定了当前控件是否能接收到其内部View(非并非是直接子View)滑动时的参数
;假设你只涉及到纵向滑动,这里可以根据nestedScrollAxes这个参数,
进行纵向判断。
// 参数child:ViewParent包含触发嵌套滚动的view的对象
// 参数target:触发嵌套滚动的view (在这里如果不涉及多层嵌套的话,child和target)是相同的
// 参数nestedScrollAxes:就是嵌套滚动的滚动方向了
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
onNestedScrollAccepted.如果onStartNestedScroll方法返回的是true的话,那么紧接着就会调用该方法.
它是让嵌套滚动在开始滚动之前,让布局容器(viewGroup)或者它的父类执行一些配置的初始化的.
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
停止滚动了,当子view调用stopNestedScroll时会调用该方法.
public void onStopNestedScroll(View target);
当子view调用dispatchNestedPreScroll方法是,会调用该方法.
该方法的会传入内部View移动的dx,dy,如果你需要消耗一定的dx,dy,就通过最后一个参数
consumed进行指定,例如我要消耗一半的dy,就可以写consumed[1]=dy/2
// 参数target:同上
// 参数dxConsumed:表示target已经消费的x方向的距离
// 参数dyConsumed:表示target已经消费的x方向的距离
// 参数dxUnconsumed:表示x方向剩下的滑动距离
// 参数dyUnconsumed:表示y方向剩下的滑动距离
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed);
// 参数dx:表示target本次滚动产生的x方向的滚动总距离
// 参数dy:表示target本次滚动产生的y方向的滚动总距离
// 参数consumed:表示父布局要消费的滚动距离,consumed[0]和consumed[1]分别表示父布局在x和y方向上消费的距离.
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
onNestedFling你可以捕获对内部View的fling事件,如果return true则表示拦截掉内部View的事件
五 主要代码
1 自定义CoordinatorLayout和Behavior
BeaviorCoordinatorLayout
public class BehaviorCoordinatorLayout extends RelativeLayout implements ViewTreeObserver.OnGlobalLayoutListener , NestedScrollingParent {
public float lastX;
public float lastY;
public BehaviorCoordinatorLayout(Context context) {
super(context);
}
public BehaviorCoordinatorLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BehaviorCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.d("cici", "getHeight-->" + getHeight() + "h-->" + h + "oldf-->" + oldh);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams param = (LayoutParams) child.getLayoutParams();
if (param.getBehavior() != null) {
param.getBehavior().onSizeChanged(this, child, w, h, oldw, oldh);
}
}
}
/**
* 设置监听时一定要注意
* 必须 当前绘制完成onFinishInflate
* 设置监听
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
//layout_behavior 字符串 --》反射实例化
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(),attrs);
}
/**
* ----------------布局发生变换事件------------------------------
* @return
*/
@Override
public void onGlobalLayout() {
for(int i=0;i<getChildCount();i++) {
View child = getChildAt(i);
//真是类型是 重写了的LayoutParams
LayoutParams layoutParams= (LayoutParams) child.getLayoutParams();
if (layoutParams.getBehavior() != null) {
layoutParams.getBehavior().onLayoutFinish(this,child);
}
}
}
/**
* ----------------触摸事件------------------------------
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX=event.getRawX();
lastY=event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
onTouchMove(event);
break;
}
return super.onTouchEvent(event);
}
private void onTouchMove(MotionEvent event) {
float moveX=event.getRawX();
float moveY=event.getRawY();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams param = (LayoutParams) child.getLayoutParams();
if (param.getBehavior() != null) {
param.getBehavior().onTouchMove(this, child, event, moveX, moveY, lastX, lastY);
}
}
lastY = moveY;
lastX = moveX;
}
/**
* ----------------滚动事件------------------move------------
* move 肯定是拿不到
* 一定返回 true
* 实现了 NestedScrolling机制 的 滚动控件
* @return
*/
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return true;
}
@Override
public void onStopNestedScroll(View child) {
}
/**
* 最重要
// 参数target:同上
// 参数dxConsumed:表示target已经消费的x方向的距离
// 参数dyConsumed:表示target已经消费的x方向的距离
// 参数dxUnconsumed:表示x方向剩下的滑动距离
// 参数dyUnconsumed:表示y方向剩下的滑动距离
*/
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
LayoutParams param = (LayoutParams) child.getLayoutParams();
if (param.getBehavior() != null) {
param.getBehavior().onNestedScroll(target ,child, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
}
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
}
// 参数dx:表示target本次滚动产生的x方向的滚动总距离
// 参数dy:表示target本次滚动产生的y方向的滚动总距离
// 参数consumed:表示父布局要消费的滚动距离,consumed[0]和consumed[1]分别表示父布局在x和y方向上消费的距离.
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
}
// onNestedFling你可以捕获对内部View的fling事件,如果return true则表示拦截掉内部View的事件
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return false;
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
public static class LayoutParams extends RelativeLayout.LayoutParams{
private static final String TAG = "tuch";
private Behavior behavior;
public Behavior getBehavior() {
return behavior;
}
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
final TypedArray a = c.obtainStyledAttributes(attrs,
R.styleable.BeaviorCoordinatorLayout);
behavior = parseBehavior(c, attrs, a.getString(
R.styleable.BeaviorCoordinatorLayout_layout_behavior));
Log.i(TAG, "LayoutParams: 名字 "+ a.getString(
R.styleable.BeaviorCoordinatorLayout_layout_behavior));
a.recycle();
}
private Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
try {
final Class clazz = Class.forName(name, true,
context.getClassLoader());
Constructor c = clazz.getConstructor(new Class[]{Context.class,AttributeSet.class});
c.setAccessible(true);
return (Behavior) c.newInstance(context, attrs);
} catch (Exception e) {
throw new RuntimeException("Could not inflate Behavior subclass " + name, e);
}
}
public LayoutParams(int w, int h) {
super(w, h);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
}
Behavior
public class Behavior {
public Behavior(Context context, AttributeSet set) {
}
public void onTouchMove(View parent, View child, MotionEvent event, float x, float y, float oldx, float oldy) {
}
/**
* 布局绘制完成
* @param parent
* @param child
*/
public void onLayoutFinish(View parent, View child) {
}
//将所有的事件 类型kaolv齐全
public void onSizeChanged(View parent, View child, int w, int h, int oldw, int oldh){
}
public boolean onTouchEvent(BehaviorCoordinatorLayout parent, View child, MotionEvent ev) {
return false;
}
public boolean onLayoutChild(BehaviorCoordinatorLayout parent, View child, int layoutDirection) {
return false;
}
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
Log.d("cici","onStartNestedScroll");
return true;
}
public void onStopNestedScroll(View child) {
Log.d("cici","onStopNestedScroll");
}
public void onNestedScrollAccepted(View child, View target, int axes) {
Log.d("cici","onNestedScrollAccepted");
}
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;
}
}
2 实现Behavior
ImageBehavior
public class ImageBehavior extends Behavior {
private static final String TAG = "tuch";
private int maxHeight =100;
private int originHeight;
public ImageBehavior(Context context, AttributeSet set) {
super(context, set);
}
@Override
public void onLayoutFinish(View parent, View child) {
super.onLayoutFinish(parent, child);
if (originHeight == 0) {
originHeight = child.getHeight();
}
}
@Override
public void onNestedScroll(View scrollView, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
if (scrollView.getScrollY() > 0) {
ViewGroup.LayoutParams params = target.getLayoutParams();
params.height = params.height - Math.abs(scrollView.getScrollY());
if (params.height < maxHeight) {
params.height = maxHeight;
}
target.setLayoutParams(params);
} else if (scrollView.getScrollY() == 0) {
ViewGroup.LayoutParams params = target.getLayoutParams();
params.height = params.height+Math.abs(dyUnconsumed);
if(params.height >= originHeight){
params.height = originHeight;
}
target.setLayoutParams(params);
}
}
}
ToolBarBehavior
public class ImageBehavior extends Behavior {
private int maxHeight=500;
private int originHeight;
public ImageBehavior(Context context, AttributeSet set) {
super(context, set);
}
@Override
public void onLayoutFinish(View parent, View child) {
super.onLayoutFinish(parent, child);
if (originHeight == 0) {
originHeight=child.getHeight();
}
}
//滚动方法就 在这里进行缩放
@Override
public void onNestedScroll(View scrollView, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
if (scrollView.getScrollY() >0) {
ViewGroup.LayoutParams parmas=target.getLayoutParams();
parmas.height = parmas.height - Math.abs(dyConsumed);
if (parmas.height < originHeight) {
parmas.height=originHeight;
}
target.setLayoutParams(parmas);
} else if (scrollView.getScrollY()== 0){
ViewGroup.LayoutParams params = target.getLayoutParams();
params.height = params.height+ Math.abs(dyUnconsumed);
if(params.height>= maxHeight){
params.height =maxHeight;
}
target.setLayoutParams(params);
}
}
}
3 使用
<com.example.baopengjian.ray_seniorui.thirty_first.design.BehaviorCoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:src="@mipmap/main"
android:scaleType="centerCrop"
app:layout_behavior="com.example.baopengjian.ray_seniorui.thirty_first.ImageBehavior"
/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="50dp"
app:layout_behavior="com.example.baopengjian.ray_seniorui.thirty_first.ImageBehavior"
/>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/img"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/large_text" />
</android.support.v4.widget.NestedScrollView>
</com.example.baopengjian.ray_seniorui.thirty_first.design.BehaviorCoordinatorLayout>