在上一篇文章中琳猫,我使用NestedScrollingParent
和NestedScrollingChild
并結(jié)合輔助類NestedScrollingParentHelper
和NestedScrollingChildHelper
實現(xiàn)了以下效果:
UI界面由三個部分組成,分別是圖片、標題箕戳、主體撩银,但是一般在現(xiàn)實需求中淳地,標題默認是最上面的著拭,那么,有沒有辦法讓界面默認顯示標題和主體衡怀,當下滑時才會顯示圖片棍矛,圖片作為頭部,順便添加下尾部抛杨。
答案是必須有够委,下面開始分步驟說明。
首先看一下布局實現(xiàn)
<?xml version="1.0" encoding="utf-8"?>
<com.zyc.hezuo.aaademo.MyCustomNestedScrollingParent
android:id="@+id/nestedparent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:textSize="20sp"
android:gravity="center"
android:background="#E9A8E9"
android:text="我是標題"/>
<com.zyc.hezuo.aaademo.MyCustomNestedScrollingChild
android:id="@+id/nestedchild"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:text="意內(nèi)容我是任容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容意內(nèi)容我是任容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容我是任意內(nèi)容"
android:textSize="26sp"/>
</com.zyc.hezuo.aaademo.MyCustomNestedScrollingChild>
</com.zyc.hezuo.aaademo.MyCustomNestedScrollingParent>
使用自定義NestedScrollingParent和自定義NestedScrollingChild完成嵌套布局怖现,這兩個類的完整代碼稍后貼出茁帽。
該布局的效果如下:
然后貼出自定義NestedScrollingChild的實現(xiàn)代碼
public class MyCustomNestedScrollingChild extends LinearLayout implements NestedScrollingChild {
private NestedScrollingChildHelper mNestedScrollingChildHelper;
private final int[] offset = new int[2]; //偏移量
private final int[] consumed = new int[2]; //消費
private int lastY;
public MyCustomNestedScrollingChild(Context context) {
super(context);
}
public MyCustomNestedScrollingChild(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyCustomNestedScrollingChild(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//記錄觸摸時的Y軸方向
lastY = (int) event.getRawY();
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
break;
case MotionEvent.ACTION_MOVE:
int y = (int) (event.getRawY());
int dy = y - lastY;//dy為屏幕上滑動的偏移量
lastY = y;
dispatchNestedPreScroll(0, dy, consumed, offset);
break;
}
return true;
}
//初始化helper對象
private NestedScrollingChildHelper getScrollingChildHelper() {
if (mNestedScrollingChildHelper == null) {
mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
mNestedScrollingChildHelper.setNestedScrollingEnabled(true);
}
return mNestedScrollingChildHelper;
}
@Override
public void setNestedScrollingEnabled(boolean enabled) { //設(shè)置滾動事件可用性
getScrollingChildHelper().setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {//是否可以滾動
return getScrollingChildHelper().isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {//開始滾動
Log.d("yunchong", "child ---- startNestedScroll");
return getScrollingChildHelper().startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {//停止?jié)L動,清空滾動狀態(tài)
getScrollingChildHelper().stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {//判斷是否含有對應(yīng)的NestedScrollingParent
return getScrollingChildHelper().hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
//在子view進行滾動之后調(diào)用此方法屈嗤,詢問父view是否還要進行余下(unconsumed)的滾動脐雪。
//前四個參數(shù)為輸入?yún)?shù),用于告訴父view已經(jīng)消費和尚未消費的距離恢共,最后一個參數(shù)為輸出參數(shù),用于子view獲取父view位置的偏移量璧亚。
//如果父view接收了它的滾動參數(shù)讨韭,進行了部分消費,則這個函數(shù)返回true癣蟋,否則為false
return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
//在子view自己進行滾動之前調(diào)用此方法透硝,詢問父view是否要在子view之前進行滾動。
//此方法的前兩個參數(shù)用于告訴父View此次要滾動的距離疯搅;而第三第四個參數(shù)用于子view獲取父view消費掉的距離和父view位置的偏移量濒生。
return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
}
}
這段代碼的核心都在onTouchEvent這個方法里,dispatchNestedPreScroll
方法將滑動偏移量傳遞給父view幔欧,startNestedScroll
確定父view是否會配合子view實現(xiàn)滑動罪治。
最后丽声,開始來自定義NestedScrollingParent,代碼如下:
public class MyCustomNestedScrollingParent extends LinearLayout implements NestedScrollingParent {
//頭部圖片最大高度
private static final int MAX_HEIGHT = 500;
//頭
private ImageView headerView;
//尾
private View footerView;
//目標view
private View childView;
public MyCustomNestedScrollingParent(Context context) {
this(context, null);
}
public MyCustomNestedScrollingParent(Context context, AttributeSet attrs) {
super(context, attrs);
//默認方向為垂直
setOrientation(LinearLayout.VERTICAL);
//頭
headerView = new ImageView(context);
headerView.setScaleType(ImageView.ScaleType.CENTER_CROP);
headerView.setImageDrawable(getResources().getDrawable(R.mipmap.demo));
//尾
footerView = LayoutInflater.from(getContext()).inflate(R.layout.footer_layout, null, false);
}
//當前視圖完全加載完畢觉义,顯示在屏幕上后執(zhí)行
@Override
protected void onFinishInflate() {
super.onFinishInflate();
//找出目標view
for(int index = 0;index < getChildCount();index ++){
if(getChildAt(index) instanceof MyCustomNestedScrollingChild){
childView = getChildAt(index);
}
}
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, MAX_HEIGHT);
//添加頭
addView(headerView, 0, layoutParams);
//添加尾
addView(footerView, getChildCount());
// 上移雁社,即隱藏header
scrollBy(0, MAX_HEIGHT);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ViewGroup.LayoutParams params = childView.getLayoutParams();
//標題高度
int titleHeight = (int) (50 * getContext().getResources().getDisplayMetrics().density + 0.5);
params.height = getMeasuredHeight() - titleHeight;
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
//如果當前觸摸的view是MyCustomNestedScrollingChild則返回true
if (target instanceof MyCustomNestedScrollingChild) {
return true;
}
return false;
}
@Override
public void onStopNestedScroll(View target) {
}
//先于child滾動
//前3個為輸入?yún)?shù),最后一個是輸出參數(shù)
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
// 如果在自定義ViewGroup之上還有父View交給我來處理
getParent().requestDisallowInterceptTouchEvent(true);
//滑動父view
scrollBy(0, -dy);
consumed[1] = dy;
}
//后于child滾動
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
}
@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;
}
/**
* 限制滑動 移動y軸不能超出最大范圍
*/
@Override
public void scrollTo(int x, int y) {
footerView.getLayoutParams().height = (int) (100 * getContext().getResources().getDisplayMetrics().density + 0.5);
if (y < 0) {
y = 0;
} else if (y > MAX_HEIGHT + footerView.getHeight()) {
y = MAX_HEIGHT + footerView.getHeight();
}
super.scrollTo(x, y);
}
}
最終效果如下:
MyCustomNestedScrollingParent的實現(xiàn)我大致說明一下:
(1)當屏幕中的MyCustomNestedScrollingParent加載完成時(包括所有的子視圖)晒骇,會執(zhí)行onFinishInflate方法霉撵,所以,在這個方法里動態(tài)添加頭和尾最為合適洪囤,計算頭布局的高度(假設(shè)為y)徒坡,最后將MyCustomNestedScrollingParent向上滑動距離為y的偏移量。代碼如下:
//當前視圖完全加載完畢瘤缩,顯示在屏幕上后執(zhí)行
@Override
protected void onFinishInflate() {
super.onFinishInflate();
//找出目標view
for(int index = 0;index < getChildCount();index ++){
if(getChildAt(index) instanceof MyCustomNestedScrollingChild){
childView = getChildAt(index);
}
}
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, MAX_HEIGHT);
//添加頭
addView(headerView, 0, layoutParams);
//添加尾
addView(footerView, getChildCount());
// 上移喇完,即隱藏header
scrollBy(0, MAX_HEIGHT);
}
但是,scrollBy之后款咖,主題布局的高度不會變化何暮,所以,必須要在scrollBy之前將提前修改主體布局的高度铐殃。在添加頭和尾時海洼,使用了addView方法,查看源碼之后發(fā)現(xiàn)富腊,一旦執(zhí)行這個方法之后布局就會重新測量坏逢,執(zhí)行onMeasure
方法,所以赘被,完全可以在這個方法里調(diào)整主體布局的高度是整,如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ViewGroup.LayoutParams params = childView.getLayoutParams();
//標題高度
int titleHeight = (int) (50 * getContext().getResources().getDisplayMetrics().density + 0.5);
params.height = getMeasuredHeight() - titleHeight;
}
在onStartNestedScroll方法中給出返回值,只有當目標view是MyCustomNestedScrollingChild才允許被滑動民假,代碼如下:
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
//如果當前觸摸的view是MyCustomNestedScrollingChild則返回true
if (target instanceof MyCustomNestedScrollingChild) {
return true;
}
return false;
}
當手指滑動主題布局時浮入,父view也會跟著一起滑動,此時羊异,自定義MyCustomNestedScrollingParent的onNestedPreScroll方法會不斷被執(zhí)行事秀,代碼如下:
//前3個為輸入?yún)?shù),最后一個是輸出參數(shù)
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
// 如果在自定義ViewGroup之上還有父View交給我來處理
getParent().requestDisallowInterceptTouchEvent(true);
//滑動父view
scrollBy(0, -dy);
consumed[1] = dy;
}
最后野舶,為了控制滑動范圍易迹,需要重寫scrollTo方法,具體實現(xiàn)如下:
/**
* 限制滑動 移動y軸不能超出最大范圍
*/
@Override
public void scrollTo(int x, int y) {
footerView.getLayoutParams().height = (int) (100 * getContext().getResources().getDisplayMetrics().density + 0.5);
if (y < 0) {
y = 0;
} else if (y > MAX_HEIGHT + footerView.getHeight()) {
y = MAX_HEIGHT + footerView.getHeight();
}
super.scrollTo(x, y);
}
下篇預(yù)知
以上案例中并沒有發(fā)生Fling事件平道,因為在子view中并沒有將Fling的速度值傳遞給父view睹欲,下一篇將實現(xiàn)Fling事件。
[本章完...]