CoordinatorLayout的简单实用,其中behavior做了一个简单的自定义,原理不说太多,因为还在摸索中,避免误导别人,有兴趣的可以google,下面是代码,代码中有比较详细的注释:
首先,布局的代码:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff"
android:fitsSystemWindows="false"
tools:context="com.chenh.coordinaforlayout.CoordinatorLayoutActivity">
<ImageView
android:id="@+id/scrolling_header"
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="centerCrop"
android:src="@mipmap/test"/>
<LinearLayout
android:id="@+id/edit_search"
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal"
android:visibility="visible"
app:layout_behavior="@string/test_search_behavior"
android:background="#fff">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:text="搜索关键字"
android:textColor="#90000000"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/test_behavior"
android:background="#fff"/>
</android.support.design.widget.CoordinatorLayout>
接着是两个自定义的behavior:
1、搜索框的behavior
package com.chenh.coordinaforlayout;
import android.content.Context;
import android.content.res.Resources;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.LinearLayout;
import com.example.jay.viewoflist.R;
/**
* 搜索框的Behavior
*
* @author chenh
*/
public class TestSearchBehavior extends CoordinatorLayout.Behavior<LinearLayout> {
//定义子View所依赖的View
private View dependencyView;
//定义上下文对象
private Context mContext;
private int default_dp_50;
private int default_dp_70;
private int default_dp_12;
public TestSearchBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
default_dp_50 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, mContext
.getResources().getDisplayMetrics());
default_dp_70 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, mContext
.getResources().getDisplayMetrics());
default_dp_12 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12, mContext
.getResources().getDisplayMetrics());
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, LinearLayout child, View dependency) {
if (dependency != null && dependency.getId() == R.id.scrolling_header) {
dependencyView = dependency;
return true;
}
return false;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child, View
dependency) {
//计算剩下的滑动距离的比例
float progress = 1.f - Math.abs(dependencyView.getTranslationY() / (dependencyView
.getHeight() - default_dp_50));
//获取需要移动的距离
float translateY = 20 + (dependencyView.getHeight() - default_dp_70) * progress;
child.setTranslationY(translateY);
//获取子View的margin
int margin = default_dp_12;
//获取LayoutParam对象
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child
.getLayoutParams();
//设置margin
lp.setMargins((int) (margin * progress), 0, (int) (margin * progress), 0);
child.setLayoutParams(lp);
return true;
}
}
2、这个就是RecyclerView的behavior
package com.chenh.coordinaforlayout;
import android.content.Context;
import android.os.Handler;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.Scroller;
import com.example.jay.viewoflist.R;
/**
* RecyclerView的BeHavior
*
* @author chenh
*/
public class TestBehavior extends CoordinatorLayout.Behavior<RecyclerView> {
//子View所依赖的View
private View dependencyView;
//定义上下文对象
private Context mContext;
//定义Scroller,用于滑动处理
private Scroller mScroller;
//定义标识符,用于判断是否已经进行了滑动处理
private boolean isScroll = false;
//Handler,用于更新UI
private Handler mHandler;
//默认的偏移量
private int defaut_dp_50;
/**
* 构造器
*
* @param context
* @param attrs
*/
public TestBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mScroller = new Scroller(mContext);
mHandler = new Handler();
defaut_dp_50 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, mContext
.getResources().getDisplayMetrics());
}
/**
* 主要用于获取子View所依赖的View
*
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, RecyclerView child, View dependency) {
if (dependency != null && dependency.getId() == R.id.scrolling_header) {
dependencyView = dependency;
return true;
}
return false;
}
/**
* 布局子View,也就是在布局中设置了app:layout_behavior属性的View
*
* @param parent
* @param child
* @param layoutDirection
* @return
*/
@Override
public boolean onLayoutChild(CoordinatorLayout parent, RecyclerView child, int
layoutDirection) {
//获取依赖的View的高度
int dependencyHeight = dependencyView.getHeight();
//布局子View在父布局中的位置,在这里的bottom属性中的parent.getHeight() +
//dependencyHeight - defaut_offset是为了将子View延伸到屏幕外面,防止在向上滑动之后底部留下空白
child.layout(0, dependencyHeight, parent.getWidth(), (int) (parent.getHeight() +
dependencyHeight - defaut_dp_50));
return true;
}
/**
* 询问父布局是否要处理这次滑动事件,true表示处理,反之不处理
*
* @param coordinatorLayout
* @param child
* @param directTargetChild
* @param target
* @param nestedScrollAxes
* @return
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child,
View directTargetChild, View target, int nestedScrollAxes) {
//如果是垂直滑动,则返回true
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
/**
* 当父布局要处理此次滑动事件时回调
*
* @param coordinatorLayout
* @param child
* @param directTargetChild
* @param target
* @param nestedScrollAxes
*/
@Override
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, RecyclerView child,
View directTargetChild, View target, int nestedScrollAxes) {
//停止动画
mScroller.abortAnimation();
//不在进行滑动处理
isScroll = false;
super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target,
nestedScrollAxes);
}
/**
* 子View即将被滑动时调用
*
* @param coordinatorLayout
* @param child
* @param target
* @param dx x轴方向滑动的滑动距离
* @param dy x轴方向滑动的距离距离
* @param consumed 代表消耗的距离,数组下标0代表x轴,1代表y轴
*/
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View
target, int dx, int dy, int[] consumed) {
//这个判断的主要作用在于防止子View所依赖的View已经滑动回到原来的位置的时候依然可以滑动
if ((dependencyView.getTranslationY() - dy) < 0) {
//在依赖的View的范围内上下滑动,都是允许的
if (Math.abs(dependencyView.getTranslationY()) < dependencyView.getHeight() -
defaut_dp_50) {
dependencyView.setTranslationY(dependencyView.getTranslationY() - dy);
consumed[1] = dy;
}
}
}
/**
* 依赖的View发生改变时回调
*
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, RecyclerView child, View
dependency) {
//子View随着所依赖的View的偏移进行移动
child.setTranslationY(dependency.getTranslationY());
//计算剩下的滑动距离的比例
float progress = 1.f - Math.abs(dependency.getTranslationY() / dependency.getHeight());
//计算所依赖的View的缩放比例,这个可以根据个人喜好来设置
float scale = 1.f + (1.f - progress);
//设置缩放
dependency.setScaleX(scale);
dependency.setScaleY(scale);
//设置透明度
dependency.setAlpha(progress);
return true;
}
/**
* NSC处理剩下的距离
* 比如上面还剩下10px,这里 NSC 滚动 2px 后发现已经到头了,于是 NSC 结束其滚动,调用该方法,
* 并将 NSC 处理剩下的像素数作为参数(dxUnconsumed、dyUnconsumed)传过来,这里传过来的就是 8px。
* 参数中还会有 NSC 处理过的像素数(dxConsumed、dyConsumed)。这个方法主要处理一些越界后的滚动。
*
* @param coordinatorLayout
* @param child
* @param target
* @param dxConsumed
* @param dyConsumed
* @param dxUnconsumed
* @param dyUnconsumed
*/
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View
target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
//通常情况是不会发生这个状况
if (dyUnconsumed > 0) {
return;
}
//判断是否已经到达了底部,因为如果依赖的View已经回到了默认状态,那么这个值就会大于0,因为dyUnconsumed这个值一般为负数
//当getTranslationY()为0就代表已经回到默认状态,那么这个判断就会为false
if ((dependencyView.getTranslationY() - dyUnconsumed) < 0) {
dependencyView.setTranslationY(dependencyView.getTranslationY() - dyUnconsumed);
}
}
/**
* 这个方法经过测试,回调的时机应该是滑动的时候有一定的速度才会进行回调
*
* @param coordinatorLayout
* @param child
* @param target
* @param velocityX
* @param velocityY
* @param consumed
* @return
*/
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, RecyclerView child, View
target, float velocityX, float velocityY, boolean consumed) {
return updateScroll();
}
/**
* 这个方法,在手指抬起了之后就会回调,所以,这里是一个手势结束的随后一道关卡
*
* @param coordinatorLayout
* @param child
* @param target
*/
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View
target) {
if (!isScroll) {
updateScroll();
}
}
/**
* 用于处理滑动
*/
public boolean updateScroll() {
//获取依赖的View的高度
int dependencyHeight = dependencyView.getHeight() - defaut_dp_50;
//依赖的View的偏移量
float dependencyScrollY = dependencyView.getTranslationY();
//滑动的距离大于一半
if (Math.abs(dependencyScrollY) > dependencyHeight / 2) {
//大于一般的时候,就让他滑动剩下的距离
mScroller.startScroll(0, (int) dependencyScrollY, 0, (int) (-dependencyHeight -
dependencyScrollY));
mHandler.post(finalRunnable);
} else {
mScroller.startScroll(0, (int) dependencyScrollY, 0, (int) (0 - dependencyScrollY));
mHandler.post(finalRunnable);
}
isScroll = true;
return true;
}
/**
* 执行滑动处理
*/
private Runnable finalRunnable = new Runnable() {
@Override
public void run() {
if (mScroller.computeScrollOffset()) {
dependencyView.setTranslationY(mScroller.getCurrY());
mHandler.post(this);
} else {
isScroll = false;
}
}
};
}
接下来就是主界面了
package com.chenh.coordinaforlayout;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
import android.widget.TextView;
import com.example.jay.viewoflist.R;
import java.util.ArrayList;
import java.util.List;
public class CoordinatorLayoutActivity extends Activity {
//定义RecycleView组件
private RecyclerView mRecycleView;
private List<String> mList = new ArrayList<>(50);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_coordinator_layout);
initView();
}
/**
* 初始化
*/
private void initView() {
mRecycleView = (RecyclerView) findViewById(R.id.recycle_view);
for (int i = 0; i < 49; i++) {
mList.add("recycle view item : " + i);
}
mRecycleView.setLayoutManager(new LinearLayoutManager(this));
mRecycleView.setAdapter(new RecyClerViewAdapter());
}
private class RecyClerViewAdapter extends RecyclerView.Adapter<RecyClerViewAdapter
.MyViewHolder> {
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = View.inflate(CoordinatorLayoutActivity.this, R.layout.item, null);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.mTxtView.setText(mList.get(position));
}
@Override
public int getItemCount() {
return mList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
private TextView mTxtView;
public MyViewHolder(View itemView) {
super(itemView);
mTxtView = (TextView) itemView.findViewById(R.id.txt_item);
}
}
}
}
另外的就是,自定义的behavior在strings.xml中设置好全路径,在布局中引用就好
<string name="test_behavior">com.chenh.coordinaforlayout.TestBehavior</string>
<string name="test_search_behavior">com.chenh.coordinaforlayout.TestSearchBehavior</string>
以上就是CoordinatorLayout的简单使用