相关文章: CoordinatorLayout自定义behavior(仿20CM动画效果) 定义自己的CoorDinatorLayout(NestedScrollingParent)
先看一张效果图,要做什么就比较清晰了:
实现思路:
1.首先自定义一个View包括头部和列表
2.给自定义View添加注解,也就是默认使用自定义的BehaviorLayoutBehavior
@CoordinatorLayout.DefaultBehavior(BehaviorLayoutBehavior.class)
3.自定义BehaviorLayoutBehavior
BehaviorLayoutBehavior extends CoordinatorLayout.Behavior<BehaviorLayout>
4.关键的两个方法是:
onMeasureChild, onLayoutChild
public boolean onMeasureChild(CoordinatorLayout parent, V child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
return false;
}
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
return false;
}
onLayoutChild用来摆放子布局
@Override
public boolean onMeasureChild(CoordinatorLayout parent, BehaviorLayout child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
Log.i(TAG, "onMeasureChild: ");
//测绘子控件
//获取子控件的偏移量
int offset = getChildMeasureOffset(parent, child);
//计算子控件的最大高度
int measureHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec) - offset - heightUsed;
//生成MeasureSpec
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(measureHeight, View.MeasureSpec.EXACTLY);
//测量
child.measure(parentWidthMeasureSpec, heightMeasureSpec);
return true;
}
//获取子控件y方向偏移量
private int getChildMeasureOffset(CoordinatorLayout parent, BehaviorLayout child) {
int offset = 0;
for(int i = 0; i < parent.getChildCount(); i++){
View view = parent.getChildAt(i);
if(view != child && view instanceof BehaviorLayout){
offset += ((BehaviorLayout) view).getHeaderHeight();
}
}
return offset;
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, BehaviorLayout child, int layoutDirection) {
Log.i(TAG, "onLayoutChild: ");
//先按照默认摆放,然后自己控制
parent.onLayoutChild(child, layoutDirection);
BehaviorLayout preView = getPreViewChild(parent, child);
int offset = 0;
if(preView != null){
offset = preView.getTop() + preView.getHeaderHeight();
}
//控制View距离顶部的距离
child.offsetTopAndBottom(offset);
//每个child都会离顶部有一个初始高度
mInitialOffset = child.getTop();
return true;
}
//获取前面的view
private BehaviorLayout getPreViewChild(CoordinatorLayout parent, BehaviorLayout child) {
int childIndex = parent.indexOfChild(child);
for(int i = childIndex - 1; i >= 0; i--){
View view = parent.getChildAt(i);
if(view instanceof BehaviorLayout){
return (BehaviorLayout) view;
}
}
return null;
}
//获取后面的view
private BehaviorLayout getNextChild(CoordinatorLayout parent, View child) {
int cardIndex = parent.indexOfChild(child);
for (int i = cardIndex + 1; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
if(view instanceof BehaviorLayout){
return (BehaviorLayout) view;
}
}
return null;
}
5.测量摆放好了以后,接下来就是滑动事件的处理
滚动时执行顺序是:onStartNestedScroll->onNestedPreScroll->onNestedScroll可以参考文章开始推荐的两篇文章。
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, BehaviorLayout child, View directTargetChild, View target, int nestedScrollAxes) {
Log.i(TAG, "onStartNestedScroll: ");
//是否需要监听滑动(水平,垂直)
boolean isVertical = ((nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0);
return isVertical && child == directTargetChild;
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, BehaviorLayout child, View target, int dx, int dy, int[] consumed) {
Log.i(TAG, "onNestedPreScroll: " + dy);
boolean show = dy < 0 && !ViewCompat.canScrollVertically(target, -1);
boolean hide = dy > 0;
if(child.getTop() >= mInitialOffset){
if(hide || show) {//如果向上滚动,或者(向下滚动并且列表不能滑动)
scroll(child, dy, mInitialOffset, mInitialOffset + child.getHeight() - child.getHeaderHeight());
//下面获取前面还是后面,为了保持联动效果
BehaviorLayout preView = getPreViewChild(coordinatorLayout, child);
if (preView != null && preView.getTop() + child.getHeaderHeight() > child.getTop()) {
preView.offsetTopAndBottom(child.getTop() - preView.getHeaderHeight() - preView.getTop());
}
BehaviorLayout nextChild = getNextChild(coordinatorLayout, child);
if (nextChild != null && nextChild.getTop() - child.getHeaderHeight() < child.getTop()) {
nextChild.offsetTopAndBottom(child.getTop() + child.getHeaderHeight() - nextChild.getTop());
}
}
}
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, BehaviorLayout child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
Log.i(TAG, "onNestedScroll: ");
}
/**
* 得到child滑动距离
* @param child
* @param dyConsumed
* @param minOffset 最小偏移量
* @param maxOffset 最大偏移量
* @return
*/
private int scroll(BehaviorLayout child, int dyConsumed, int minOffset, int maxOffset) {
int initialOffset = child.getTop();
//a在此范围[minOffset, maxOffset], delta为移动距离
int delta = clamp(initialOffset - dyConsumed, minOffset, maxOffset) - initialOffset;
child.offsetTopAndBottom(delta);
return delta;
}
//边界检查
private int clamp(int i, int minOffset, int maxOffset) {
if(i > maxOffset){
return maxOffset;
}
if(i < minOffset){
return minOffset;
}
return i;
}
源码:
1.layout布局:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/mCoordinatorLayout"
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"
>
<com.test.git.coordinatorlayout.View.BehaviorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.test.git.coordinatorlayout.View.BehaviorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.design.widget.CoordinatorLayout>
2.BehaviorLayout
package com.test.git.coordinatorlayout.View;
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import com.test.git.coordinatorlayout.Adapter.TestAdapter;
import com.test.git.coordinatorlayout.Behavior.BehaviorLayoutBehavior;
import com.test.git.coordinatorlayout.R;
import java.util.ArrayList;
import java.util.List;
/**
* Created by lk on 16/9/7.
*/
@CoordinatorLayout.DefaultBehavior(BehaviorLayoutBehavior.class)
public class BehaviorLayout extends FrameLayout {
private final View tv_header;
private int mHeaderViewHeight;
public BehaviorLayout(Context context) {
this(context, null);
}
public BehaviorLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BehaviorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
View view = LayoutInflater.from(context).inflate(R.layout.item_header_recycler, null);
tv_header = view.findViewById(R.id.tv_header);
RecyclerView mRecyclerView = (RecyclerView) view.findViewById(R.id.mRecyclerView);
//添加布局管理器
LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(context);
mRecyclerView.setLayoutManager(mLinearLayoutManager);
//初始化数据
List<String> mMessages = new ArrayList<>();
for(int i = 0; i < 20; i ++){
mMessages.add("" + i);
}
TestAdapter mAdapter = new TestAdapter(context, mMessages, R.layout.item_text);
mRecyclerView.setAdapter(mAdapter);
addView(view);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(h != oldh || w != oldw) {
mHeaderViewHeight = tv_header.getMeasuredHeight();
}
}
//获取头部高度
public int getHeaderHeight(){
return mHeaderViewHeight;
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_header"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#00ff00"
android:text="头部"
android:gravity="center"
/>
<android.support.v7.widget.RecyclerView
android:id="@+id/mRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0000ff"
>
</android.support.v7.widget.RecyclerView>
</LinearLayout>
3.BehaviorLayoutBehavior
package com.test.git.coordinatorlayout.Behavior;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.util.Log;
import android.view.View;
import com.test.git.coordinatorlayout.View.BehaviorLayout;
/**
* Created by lk on 16/9/7.
*/
public class BehaviorLayoutBehavior extends CoordinatorLayout.Behavior<BehaviorLayout> {
private int mInitialOffset;
private static final String TAG = "BehaviorLayoutBehavior";
@Override
public boolean onMeasureChild(CoordinatorLayout parent, BehaviorLayout child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
Log.i(TAG, "onMeasureChild: ");
//测绘子控件
//获取子控件的偏移量
int offset = getChildMeasureOffset(parent, child);
//计算子控件的最大高度
int measureHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec) - offset - heightUsed;
//生成MeasureSpec
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(measureHeight, View.MeasureSpec.EXACTLY);
//测量
child.measure(parentWidthMeasureSpec, heightMeasureSpec);
return true;
}
//获取子控件y方向偏移量
private int getChildMeasureOffset(CoordinatorLayout parent, BehaviorLayout child) {
int offset = 0;
for(int i = 0; i < parent.getChildCount(); i++){
View view = parent.getChildAt(i);
if(view != child && view instanceof BehaviorLayout){
offset += ((BehaviorLayout) view).getHeaderHeight();
}
}
return offset;
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, BehaviorLayout child, int layoutDirection) {
Log.i(TAG, "onLayoutChild: ");
//先按照默认摆放,然后自己控制
parent.onLayoutChild(child, layoutDirection);
BehaviorLayout preView = getPreViewChild(parent, child);
int offset = 0;
if(preView != null){
offset = preView.getTop() + preView.getHeaderHeight();
}
//控制View距离顶部的距离
child.offsetTopAndBottom(offset);
//每个child都会离顶部有一个初始高度
mInitialOffset = child.getTop();
return true;
}
//获取前面的view
private BehaviorLayout getPreViewChild(CoordinatorLayout parent, BehaviorLayout child) {
int childIndex = parent.indexOfChild(child);
for(int i = childIndex - 1; i >= 0; i--){
View view = parent.getChildAt(i);
if(view instanceof BehaviorLayout){
return (BehaviorLayout) view;
}
}
return null;
}
//获取后面的view
private BehaviorLayout getNextChild(CoordinatorLayout parent, View child) {
int cardIndex = parent.indexOfChild(child);
for (int i = cardIndex + 1; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
if(view instanceof BehaviorLayout){
return (BehaviorLayout) view;
}
}
return null;
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, BehaviorLayout child, View directTargetChild, View target, int nestedScrollAxes) {
Log.i(TAG, "onStartNestedScroll: ");
//是否需要监听滑动(水平,垂直)
boolean isVertical = ((nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0);
return isVertical && child == directTargetChild;
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, BehaviorLayout child, View target, int dx, int dy, int[] consumed) {
Log.i(TAG, "onNestedPreScroll: " + dy);
boolean show = dy < 0 && !ViewCompat.canScrollVertically(target, -1);
boolean hide = dy > 0;
if(child.getTop() >= mInitialOffset){
if(hide || show) {//如果向上滚动,或者(向下滚动并且列表不能滑动)
scroll(child, dy, mInitialOffset, mInitialOffset + child.getHeight() - child.getHeaderHeight());
//下面获取前面还是后面,为了保持联动效果
BehaviorLayout preView = getPreViewChild(coordinatorLayout, child);
if (preView != null && preView.getTop() + child.getHeaderHeight() > child.getTop()) {
preView.offsetTopAndBottom(child.getTop() - preView.getHeaderHeight() - preView.getTop());
}
BehaviorLayout nextChild = getNextChild(coordinatorLayout, child);
if (nextChild != null && nextChild.getTop() - child.getHeaderHeight() < child.getTop()) {
nextChild.offsetTopAndBottom(child.getTop() + child.getHeaderHeight() - nextChild.getTop());
}
}
}
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, BehaviorLayout child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
Log.i(TAG, "onNestedScroll: ");
}
/**
* 得到child滑动距离
* @param child
* @param dyConsumed
* @param minOffset 最小偏移量
* @param maxOffset 最大偏移量
* @return
*/
private int scroll(BehaviorLayout child, int dyConsumed, int minOffset, int maxOffset) {
int initialOffset = child.getTop();
//a在此范围[minOffset, maxOffset], delta为移动距离
int delta = clamp(initialOffset - dyConsumed, minOffset, maxOffset) - initialOffset;
child.offsetTopAndBottom(delta);
return delta;
}
//边界检查
private int clamp(int i, int minOffset, int maxOffset) {
if(i > maxOffset){
return maxOffset;
}
if(i < minOffset){
return minOffset;
}
return i;
}
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, BehaviorLayout child, View target, float velocityX, float velocityY) {
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, BehaviorLayout child, View target, float velocityX, float velocityY, boolean consumed) {
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
}
源码地址:点击打开链接