Android NestedScrolling機制
NestedScrolling機制現在在App的作用越來越重要,許多很漂亮的互動都是基於NestedScrolling機制進行完成的。
NestedScrolling機制主要是能夠讓父View和子View在滾動時互相協調配合。其中有兩個重要的類,分別是:
介面類
NestedScrollingParent(最新:NestedScrollingParent2)
NestedScrollingChild(最新:NestedScrollingChild2)
幫助類
NestedScrollingChildHelper
NestedScrollingParentHelper
父類繼承NestedScrollingParent介面,而子類繼承NestedScrollingChild介面,同時讓父類包含子類,而不是自接父子關係,就搭起了NestedScrollingParent機制的基本骨架。
其主要流程是:
- 子類滑動,把滑動產生的事件和引數傳給父類
- 父類根據子類傳過來的引數進行各種互動操作,如變大縮小之類的
而NestedScrollingChildHelper和NestedScrollingParentHelper是兩個幫助類,在實現NestedScrollingChild和NestedScrollingParent介面時,使用這兩個幫助類可以簡化我們的工作。
NestedScrollingChild 介面類
public interface NestedScrollingChild {
/**
* 設定巢狀滑動是否能用
*/
@Override
public void setNestedScrollingEnabled(boolean enabled);
/**
* 判斷巢狀滑動是否可用
*/
@Override
public boolean isNestedScrollingEnabled();
/**
* 開始巢狀滑動
*
* @param axes 表示方向軸,有橫向和豎向
*/
@Override
public boolean startNestedScroll(int axes);
/**
* 停止巢狀滑動
*/
@Override
public void stopNestedScroll();
/**
* 判斷是否有父View 支援巢狀滑動
*/
@Override
public boolean hasNestedScrollingParent() ;
/**
* 滑行時呼叫
* @param velocityX x 軸上的滑動速率
* @param velocityY y 軸上的滑動速率
* @param consumed 是否被消費
* @return true if the nested scrolling parent consumed or otherwise reacted to the fling
*/
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) ;
/**
* 進行滑行前呼叫
* @param velocityX x 軸上的滑動速率
* @param velocityY y 軸上的滑動速率
* @return true if a nested scrolling parent consumed the fling
*/
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) ;
/**
* 子view處理scroll後呼叫
* @param dxConsumed x軸上被消費的距離(橫向)
* @param dyConsumed y軸上被消費的距離(豎向)
* @param dxUnconsumed x軸上未被消費的距離
* @param dyUnconsumed y軸上未被消費的距離
* @param offsetInWindow 子View的窗體偏移量
* @return true if the event was dispatched, false if it could not be dispatched.
*/
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) ;
/**
* 在子View的onInterceptTouchEvent或者onTouch中,呼叫該方法通知父View滑動的距離
* @param dx x軸上滑動的距離
* @param dy y軸上滑動的距離
* @param consumed 父view消費掉的scroll長度
* @param offsetInWindow 子View的窗體偏移量
* @return 支援的巢狀的父View 是否處理了 滑動事件
*/
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow);
}
上面這個方法方法和代表的意思我都已經貼出來, 然後是隻是一個介面類上面的方法要怎麼實現呢,這時候就要用到上面的幫助類NestedScrollingChildHelper,一個完整的實現模板如下:
public class MyNestedScrollingChild extends LinearLayout implements NestedScrollingChild {
private NestedScrollingChildHelper mNestedScrollingChildHelper;
public MyNestedScrollingChild(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
mNestedScrollingChildHelper.setNestedScrollingEnabled(true);
}
@Override
public void setNestedScrollingEnabled(boolean enabled) {
mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return mNestedScrollingChildHelper.isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return mNestedScrollingChildHelper.startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
mNestedScrollingChildHelper.stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return mNestedScrollingChildHelper.hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mNestedScrollingChildHelper.dispatchNestedFling(velocityX,velocityY,consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX,velocityY);
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) {
return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed,dyConsumed,dxUnconsumed,dyUnconsumed,offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) {
return dispatchNestedPreScroll(dx,dy,consumed,offsetInWindow);
}
}
NestedScrollingParent 介面
public interface NestedScrollingParent {
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
@Override
public void onStopNestedScroll(View child);
@Override
public void onNestedScrollAccepted(View child, View target, int axes);
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed);
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY);
@Override
public int getNestedScrollAxes();
}
從上面的程式碼可以看出NestedScrollingChild的方法執行之後就會回撥父View的各個方法,從方法名也知道作用和NestedScrollingChild的用作大同小異。當子View執行startNestedScroll時,就會回撥父View的onStartNestedScroll、onNestedScrollAccepted方法,當子View執行dispatchNestedPreScroll方法時,就會回撥父View的onNestedPreScroll,當子View執行dispatchNestedScroll方法時,就會回撥父View的onNestedScroll方法,由此類推,dispatchNestedPreFling回撥父View的onNestedPreFling方法,dispatchNestedFling回撥父View的onNestedFling方法,等。
同時也有幾個介面是需要幫助類進行實現的,模板程式碼如下:
public class MyNestedScrollingParent extends LinearLayout implements NestedScrollingParent {
private NestedScrollingParentHelper mNestedScrollingParentHelper;
public MyNestedScrollingParent(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return super.onStartNestedScroll(child, target, nestedScrollAxes);
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(target, dx, dy, consumed);
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return super.onNestedFling(target, velocityX, velocityY, consumed);
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return super.onNestedPreFling(target, velocityX, velocityY);
}
@Override
public void onStopNestedScroll(View child) {
mNestedScrollingParentHelper.onStopNestedScroll(child);
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
mNestedScrollingParentHelper.onNestedScrollAccepted(child,target,axes);
}
@Override
public int getNestedScrollAxes() {
return mNestedScrollingParentHelper.getNestedScrollAxes();
}
}
最後總結,子View通過startNestedScroll()發起巢狀滑動,同時父View也會回撥自己的onStartNestedScroll()方法,接著子View每次在滾動前都會呼叫dispatchNestedPreScroll()方法,父View的onNestedPreScroll()也會操作,父View決定是否熬滑動,然後才是子View自己滑動,之後子View也可以呼叫上面的其它方法做相應的處理,最後呼叫stopNestedScroll()結束。
最後舉一個例項吧
public class MyNestedScrollChild extends LinearLayout implements NestedScrollingChild2 {
private NestedScrollingChildHelper mNestedScrollingChildHelper;
private int[] offset=new int[2];
private int[] consumed=new int[2];
private TextView scrollText;
private int showHeight;
private int lastY;
private boolean srcollTop=false;
public MyNestedScrollChild(Context context) {
super(context);
}
public MyNestedScrollChild(Context context, AttributeSet attrs) {
super(context, attrs);
setBackgroundColor(context.getResources().getColor(R.color.colorffffff));
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
scrollText=(TextView)getChildAt(0);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
showHeight = getMeasuredHeight();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public boolean canChildScrollUp() {
return srcollTop;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
lastY=(int)event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int y=(int)(event.getRawY());
int dy=y-lastY;
lastY=y;
if(startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)
&&dispatchNestedPreScroll(0,dy,consumed,offset)){
int remain = dy - consumed[1];
if (remain != 0) {
scrollBy(0, -remain);
}
}else{
scrollBy(0, -dy);
}
break;
}
return true;
}
//限制滾動範圍
@Override
public void scrollTo(int x, int y) {
int maxY = getMeasuredHeight()- showHeight;
if (y > maxY) {
y = maxY;
srcollTop=false;
}else if (y < 0) {
y = 0;
srcollTop=true;
}else{
srcollTop=false;
}
super.scrollTo(x, y);
}
public NestedScrollingChildHelper getNestedScrollingChildHelper(){
if(mNestedScrollingChildHelper==null){
mNestedScrollingChildHelper=new NestedScrollingChildHelper(this);
mNestedScrollingChildHelper.setNestedScrollingEnabled(true);
}
return mNestedScrollingChildHelper;
}
@Override
public boolean startNestedScroll(int axes, int type) {
return getNestedScrollingChildHelper().startNestedScroll(axes,type);
}
@Override
public void stopNestedScroll(int type) {
getNestedScrollingChildHelper().stopNestedScroll(type);
}
@Override
public boolean hasNestedScrollingParent(int type) {
return getNestedScrollingChildHelper().hasNestedScrollingParent(type);
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, @Nullable int[] offsetInWindow, int type) {
return getNestedScrollingChildHelper().dispatchNestedScroll(dxConsumed,dyConsumed,
dxUnconsumed,dyUnconsumed,offsetInWindow,type);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, int type) {
return getNestedScrollingChildHelper().dispatchNestedPreScroll(dx,dy,consumed,offsetInWindow,type);
}
@Override
public void setNestedScrollingEnabled(boolean enabled) {
getNestedScrollingChildHelper().setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return getNestedScrollingChildHelper().isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return getNestedScrollingChildHelper().startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
getNestedScrollingChildHelper().stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return getNestedScrollingChildHelper().hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
@Nullable int[] offsetInWindow) {
return getNestedScrollingChildHelper().dispatchNestedScroll(dxConsumed,dyConsumed,dxUnconsumed,dyUnconsumed,offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) {
return getNestedScrollingChildHelper().dispatchNestedPreScroll(dx,dy,consumed,offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return getNestedScrollingChildHelper().dispatchNestedFling(velocityX,velocityY,consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return getNestedScrollingChildHelper().dispatchNestedPreFling(velocityX,velocityY);
}
}
首先給出NestedScrollingChild子View,重點看一下onTouchEvent()方法,當MotionEvent.ACTION_MOVE時,不斷的呼叫startNestedScroll()和dispatchNestedPreScroll()向父View傳送直接,然後滾動通過scrollBy()滾動觸發事件的View,這就是最核心的程式碼了,接著看父View程式碼如下:
public class MyNestedScrollParent extends LinearLayout implements NestedScrollingParent2 {
private NestedScrollingParentHelper mNestedScrollingParentHelper;
private MyNestedScrollChild scrollChildView;
private ImageView foodIV;
private TextView titleTV;
private int imageHeight;
private int titleHeight;
private int imageMargin;
private int scrollY;
public MyNestedScrollParent(Context context) {
this(context,null);
}
public MyNestedScrollParent(Context context, AttributeSet attrs) {
super(context, attrs);
mNestedScrollingParentHelper=new NestedScrollingParentHelper(this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
FrameLayout frameLayout=(FrameLayout) getChildAt(0);
scrollChildView=(MyNestedScrollChild) getChildAt(1);
foodIV=frameLayout.findViewById(R.id.foodIV);
titleTV=frameLayout.findViewById(R.id.titleTV);
foodIV.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
imageHeight=foodIV.getHeight();
}
});
titleTV.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
titleHeight=titleTV.getHeight();
}
});
}
@Override
public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, int type) {
if(target instanceof MyNestedScrollChild) {
return true;
}
return false;
}
@Override
public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) {
mNestedScrollingParentHelper.onNestedScrollAccepted(child,target,axes,type);
}
@Override
public void onStopNestedScroll(@NonNull View target, int type) {
mNestedScrollingParentHelper.onStopNestedScroll(target,type);
}
@Override
public int getNestedScrollAxes() {
return mNestedScrollingParentHelper.getNestedScrollAxes();
}
@Override
public void onStopNestedScroll(@NonNull View target) {
mNestedScrollingParentHelper.onStopNestedScroll(target);
}
@Override
public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int type) {
}
@Override
public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
imageMargin=titleHeight-imageHeight;
scrollY+=dy;
if(scrollY<=imageMargin){
scrollY=imageMargin;
scrollChildView.setTranslationY(scrollY);
}else{
if(dy<0){
//上滑
consumed[1]=dy;
scrollChildView.setTranslationY(scrollY);
}else{
//下滑
if(!scrollChildView.canChildScrollUp()){
scrollY-=dy;
}
if(scrollY>=0){
scrollY=0;
}
scrollChildView.setTranslationY(scrollY);
}
}
}
@Override
public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes) {
return false;
}
}
前面的子View的dispatchNestedPreScroll()對應這個父View的onNestedScroll()如上面的程式碼,通過View.setTranslationY()來滑動整個子View,consumed[1]=dy;表示子View和滑動的View一起滑。最後看一下佈局檔案:
<?xml version="1.0" encoding="utf-8"?>
<com.jack.meituangoodsdetails.view.MyNestedScrollParent
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/foodIV"
android:layout_width="match_parent"
android:layout_height="170dp"
android:src="@mipmap/food_bg"
android:scaleType="fitXY"/>
<TextView
android:id="@+id/titleTV"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:textColor="@android:color/white"
android:background="@color/color2e8b57"
android:text="MyTitle"/>
</FrameLayout>
<com.jack.meituangoodsdetails.view.MyNestedScrollChild
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/scrollText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="123\n456\n789\n111\n222\n333\n444\n555\n666\n777\n888\n999\n14\n12\n13\n44\n55\n66\n77\n88\n99\n11\n22\n33\n44\n55\n66\n77\n88\n99\n77\n88\n88\n8\n88\n88\n" />
</com.jack.meituangoodsdetails.view.MyNestedScrollChild>
</com.jack.meituangoodsdetails.view.MyNestedScrollParent>
父View包裹子View,以達成依賴關係。
講了NestedScrolling,就有必要講解CoordinatorLayout.Behavior,下回講吧,最後奉上原始碼吧https://github.com/jack921/MeiTuanGoodsDetails
相關文章
- 【透鏡系列】看穿 > NestedScrolling 機制 >
- Android NestedScrolling 實戰Android
- NestedScrolling機制解析,自定義巢狀滑動你也可以巢狀
- android 巢狀問題(NestedScrolling)Android巢狀
- 乾貨:五分鐘帶你看懂NestedScrolling巢狀滑動機制巢狀
- 自定義View事件之進階篇(一)-NestedScrolling(巢狀滑動)機制View事件巢狀
- Android混淆機制Android
- Android 事件機制Android事件
- Android 安全機制Android
- android中反射機制Android反射
- Android Classloader機制Android
- Android IPC 機制分析Android
- Android包管理機制Android
- Android簽名機制Android
- Android Framework : Alarm 機制AndroidFramework
- Android Handler機制理解Android
- android: 廣播機制Android
- 理解Android安全機制Android
- android之 Android訊息機制Android
- Android10_原理機制系列_事件傳遞機制Android事件
- android訊息機制—HandlerAndroid
- 理解 Android 訊息機制Android
- Android系統安全機制Android
- Android重修課 -- Parcel機制Android
- Android訊息機制HandlerAndroid
- Android事件分發機制Android事件
- Android類裝載機制Android
- Android 之訊息機制Android
- Android Handler機制詳解Android
- Android Binder機制淺析Android
- Android中的IPC機制Android
- Android的訊息機制Android
- Android事件傳遞機制Android事件
- Android的IPC機制BinderAndroid
- android restart reason機制AndroidREST
- Android動畫機制全解析Android動畫
- android下的callback機制Android
- 全面理解 Android 安全機制(Android Permission)Android