編輯:關於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); //初始化數據 ListmMessages = 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); } }
源碼地址:點擊打開鏈接
通過前面幾篇博客,我們能夠自定義出一些比較簡單的自定義控件,但是這在實際應用中是遠遠不夠的,為了實現一些比較牛X的效果,比如側滑菜單、滑動卡片等等,我們還需要了解自定義V
發生滑動效果的原因Android坐標系獲取view在屏幕上的坐標(view左上角的坐標) View view = (View) findViewById(R.id.
SlidingPaneLayoutSlidingPaneLayout是Android在android-support-v4.jar中推出的一個可滑動面板的布局,我們提到水
1.關於坑 好吧,在此之前先來說一下,之前開的坑,恩,確實是坑,前面開的兩個android開發教程的坑,對不起,實在是沒什麼動力了,不過源碼都有的,大家可以參照githu