Android 巢狀佈局簡析
前言
巢狀佈局是事件分發的衍生內容,理解了巢狀佈局的實現方式,Md的很多效果都是毛毛雨了
Uml 時序圖形
巢狀佈局實現方式,以及 5.0 中與巢狀佈局有關聯的元件
提起巢狀佈局,MD中的 CoordinatorLayout,SwipeRefreshLayout,RecyclerView 當然還有V4包下的NestedScrollView,都有直接關係,因為他們是 NestedScrollingParent(NP) 或者 NestedScrollingChild(NC) 直接或者間接子類。
那麼如何才能進行巢狀佈局自定義?
通過成為NP,NC子類進行巢狀佈局開發妥妥的。
下面是NP,NC 具體方法,呼叫時序檢視blog頭部的時序圖結合原始碼進行理解
NestedScrollingChildHelper // Child輔助類
NestedScrollingParentHelper // Parent輔助類
核心類 NestedScrollingParent
package android.support.v4.view;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
public interface NestedScrollingParent {
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
public void onStopNestedScroll(View target);
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed);
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
public boolean onNestedPreFling(View target, float velocityX, float velocityY);
public int getNestedScrollAxes();
}
核心類 NestedScrollingChild
package android.support.v4.view;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
public interface NestedScrollingChild {
public void setNestedScrollingEnabled(boolean enabled);
public boolean isNestedScrollingEnabled();
public boolean startNestedScroll(int axes);
public void stopNestedScroll();
public boolean hasNestedScrollingParent();
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
public boolean dispatchNestedPreFling(float velocityX, float velocityY);
}
簡單一下回調方法的介含義,以及需要在那裡處理事件:
onStartNestedScroll該方法,一定要按照自己的需求返回true,該方法決定了當前控制元件是否能接收到其內部View(非並非是直接子View)滑動時的引數;假設你只涉及到縱向滑動,這裡可以根據nestedScrollAxes這個引數,進行縱向判斷。
onNestedPreScroll該方法的會傳入內部View移動的dx,dy,如果你需要消耗一定的dx,dy,就通過最後一個引數consumed進行指定,例如我要消耗一半的dy,就可以寫consumed[1]=dy/2
onNestedFling你可以捕獲對內部View的fling事件,如果return true則表示攔截掉內部View的事件。
主要關注的就是這三個方法~
這裡內部View表示不一定非要是直接子View,只要是內部View即可。
下面看一下我們具體的實現:
public class StickyNavLayout extends LinearLayout implements NestedScrollingParent
{
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes)
{
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed)
{
boolean hiddenTop = dy > 0 && getScrollY() < mTopViewHeight;
boolean showTop = dy < 0 && getScrollY() > 0 && !ViewCompat.canScrollVertically(target, -1);
if (hiddenTop || showTop)
{
scrollBy(0, dy);
consumed[1] = dy;
}
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY)
{
if (getScrollY() >= mTopViewHeight) return false;
fling((int) velocityY);
return true;
}
}
onStartNestedScroll中,我們判斷了如果是縱向返回true,這個一般是需要內部的View去傳入的,你要是不確定,或者擔心內部View編寫的不規範,你可以直接return true;
onNestedPreScroll中,我們判斷,如果是上滑且頂部控制元件未完全隱藏,則消耗掉dy,即consumed[1]=dy;如果是下滑且內部View已經無法繼續下拉,則消耗掉dy,即consumed[1]=dy,消耗掉的意思,就是自己去執行scrollBy,實際上就是我們的StickNavLayout滑動。
此外,這裡還處理了fling,通過onNestedPreFling方法,這個可以根據自己需求定了,當頂部控制元件顯示時,fling可以讓頂部控制元件隱藏或者顯示。
Eg:當然有人會說用手勢 + 事件分發攔截同樣可以實現這個效果,的確可以實現,只是效果會打折扣
事件分發是這樣的:子View首先得到事件處理權,處理過程中,父View可以對其攔截,但是攔截了以後就無法再還給子View(本次手勢內)。
NestedScrolling機制是這樣的:內部View在滾動的時候,首先將dx,dy交給NestedScrollingParent,NestedScrollingParent可對其進行部分消耗,剩餘的部分還給內部View。
這裡針對實現了NetstedScrollingParent 的類繼續log輸出,直觀顯示滾動事件呼叫鏈。
不進行滑動
vlayout E/StickyNavLayout: onStartNestedScroll
05-22 14 E/StickyNavLayout: onNestedScrollAccepted
05-22 14 E/StickyNavLayout: onStopNestedScroll
進行滑動
05-22 14:46:11.655 27721-27721/: onStartNestedScroll
05-22 14:46:11.655 27721-27721/: onNestedScrollAccepted
05-22 14:46:11.875 27721-27721/: onNestedPreScroll
05-22 14:46:11.895 27721-27721/: onNestedPreScroll
05-22 14:46:11.915 27721-27721/: onNestedPreScroll
05-22 14:46:11.925 27721-27721/: onNestedPreScroll
05-22 14:46:11.945 27721-27721/: onNestedPreScroll
05-22 14:46:11.965 27721-27721/: onNestedPreScroll
05-22 14:46:11.975 27721-27721/: onNestedPreScroll
05-22 14:46:11.995 27721-27721/: onNestedPreScroll
05-22 14:46:12.015 27721-27721/: onNestedPreScroll
05-22 14:46:12.025 27721-27721/: onNestedPreScroll
05-22 14:46:12.045 27721-27721/: onStopNestedScroll
快速滑動
05-22 14:48:18.245 27721-27721/: onStartNestedScroll
05-22 14:48:18.245 27721-27721/: onNestedScrollAccepted
05-22 14:48:18.315 27721-27721/: onNestedPreScroll
05-22 14:48:18.315 27721-27721/: onNestedScroll
05-22 14:48:18.315 27721-27721/: onNestedPreFling
05-22 14:48:18.315 27721-27721/: onNestedFling
05-22 14:48:18.315 27721-27721/: onStopNestedScroll
拋,快速 滑動觸發Fling
05-22 14:49:51.875 27721-27721/: onNestedPreFling
05-22 14:49:51.875 27721-27721/: onNestedFling
1.巢狀佈局模式。依附於onTouchEvent() 進行處理。在down,move,up 中進行回撥。Helper 類中實現
- 固定大小的 int [2],集合。 作為形參,進行傳遞,在函式中可以通過形參進行賦值
3.scroller 和 後面 api 19 新新增對於over 邊界處理的 overscroller ,兩者80% api重複,後者多了幾個處理Over bound的 api
總結:
巢狀佈局滑動和事件分發之間的區別,進行自定義擴充套件巢狀佈局需要實現那些方法,實現那幾個類。巢狀佈局的延伸相容性等有個大體的瞭解。細節部分還請自行體驗