編輯:關於Android編程
先看一張效果圖,要做什麼就比較清晰了:

實現思路:
1.首先自定義一個View包括頭部和列表
2.給自定義View添加注解,也就是默認使用自定義的BehaviorLayoutBehavior
@CoordinatorLayout.DefaultBehavior(BehaviorLayoutBehavior.class)
3.自定義BehaviorLayoutBehavior
BehaviorLayoutBehavior extends CoordinatorLayout.Behavior
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;
}
onMeasureChild用來測量子布局
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;
}
滾動時執行順序是: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布局:
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 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;
}
}
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{ 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); } }
源碼地址:點擊打開鏈接
Android自定義ViewGroup(四、打造自己的布局容器)
通過前面幾篇博客,我們能夠自定義出一些比較簡單的自定義控件,但是這在實際應用中是遠遠不夠的,為了實現一些比較牛X的效果,比如側滑菜單、滑動卡片等等,我們還需要了解自定義V
Android群英傳第五章筆記·Android Scroll分析
發生滑動效果的原因Android坐標系獲取view在屏幕上的坐標(view左上角的坐標) View view = (View) findViewById(R.id.
Android開發筆記(一百二十)兩種側滑布局
SlidingPaneLayoutSlidingPaneLayout是Android在android-support-v4.jar中推出的一個可滑動面板的布局,我們提到水
[Android開發系列]IT博客應用
1.關於坑 好吧,在此之前先來說一下,之前開的坑,恩,確實是坑,前面開的兩個android開發教程的坑,對不起,實在是沒什麼動力了,不過源碼都有的,大家可以參照githu