- CompoundButton 源码分析
- LinearLayout 源码分析
- SearchView 源码解析
- LruCache 源码解析
- ViewDragHelper 源码解析
- BottomSheets 源码解析
- Media Player 源码分析
- NavigationView 源码解析
- Service 源码解析
- Binder 源码分析
- Android 应用 Preference 相关及源码浅析 SharePreferences 篇
- ScrollView 源码解析
- Handler 源码解析
- NestedScrollView 源码解析
- SQLiteOpenHelper/SQLiteDatabase/Cursor 源码解析
- Bundle 源码解析
- LocalBroadcastManager 源码解析
- Toast 源码解析
- TextInputLayout
- LayoutInflater 和 LayoutInflaterCompat 源码解析
- TextView 源码解析
- NestedScrolling 事件机制源码解析
- ViewGroup 源码解析
- StaticLayout 源码分析
- AtomicFile 源码解析
- AtomicFile 源码解析
- Spannable 源码分析
- Notification 之 Android 5.0 实现原理
- CoordinatorLayout 源码分析
- Scroller 源码解析
- SwipeRefreshLayout 源码分析
- FloatingActionButton 源码解析
- AsyncTask 源码分析
- TabLayout 源码解析
4. NestedScrollView 之 Nested
还记得前面我们跟到了 mChildHelper.startNestedScroll 函数么,那个函数的主要工作就是要找到一个支持 nested 功能的 mNestedScrollingParent。哦,其实应该是 ancestorView。明眼人掐指一算,这个支持 nested 功能的 view 不就是我们熟悉的 CoordinatorLayout 么?此处,我们先建立一个大前提,layout 文件中我们让 NestedScrollView 支持视差,使用 CoordinatorLayout 和 AppbarLayout,xml 如下:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="6dp"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:navigationIcon="?attr/homeAsUpIndicator" />
<Button
android:layout_width="fill_parent"
android:layout_height="60dp"
android:background="#111111"
android:gravity="center"
android:textColor="#ffffff"
android:text="这个是悬停按钮" />
</android.support.design.widget.AppBarLayout>
<nested.stone.com.nestedscrolldemo.CoordinatorNestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!--xxxxxxxxxxxxxxx 若干子 Viewxxxxxxxxxxxxxxxx-->
</LinearLayout>
</nested.stone.com.nestedscrolldemo.CoordinatorNestedScrollView>
</android.support.design.widget.CoordinatorLayout>接下来我们回归 java 代码,mChildHelper.startNestedScroll 可以找到支持 nested 功能的 parentView,那一段代码是这样的:
if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
mNestedScrollingParent = p;
ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
return true;
}其中 ViewParentCompat.onStartNestedScroll 就是判断 mView 的某一个祖先视图 p 是否支持 nested 操作的。我们继续跟进去 ViewParentCompat 看看:
static final ViewParentCompatImpl IMPL;
static {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
IMPL = new ViewParentCompatLollipopImpl();
} else if (version >= 19) {
IMPL = new ViewParentCompatKitKatImpl();
} else if (version >= 14) {
IMPL = new ViewParentCompatICSImpl();
} else {
IMPL = new ViewParentCompatStubImpl();
}
}
public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
}由于操作系统版本的不同,IMPL 有不同的实现类。我们看一下支持安卓 5.0 以后的这个兼容类:ViewParentCompatLollipopImpl
public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
try {
return parent.onStartNestedScroll(child, target, nestedScrollAxes);
} catch (AbstractMethodError e) {
Log.e(TAG, "ViewParent " + parent + " does not implement interface " +
"method onStartNestedScroll", e);
return false;
}
}在上面的函数中,parent 是 NestedScrollView 的某一个祖先视图。由于我们在 xml 中定义了 CoordinatorLayout,所以这个 parent 就应该是 CoordinatorLayout。然后呢,调用了这个 parent.onStartNestedScroll 函数,我们看一下 CoordinatorLayout 有没有这个函数。
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
boolean handled = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
// 遍历每一个子 View
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
nestedScrollAxes);
handled |= accepted;
lp.acceptNestedScroll(accepted);
} else {
lp.acceptNestedScroll(false);
}
}
return handled;
}你看到了么,CoordinatorLayout 有这个函数。在这个函数中,它遍历了 CoordinatorLayout 的所有子 view,并找到了子 View 中对应的 LayoutParams.Behavior。所以,AppbarLayout 的 Behavior 就能排上用场了。它接下来会跳转到 AppbarLayout.Behavior.onStartNestedScroll 了。接下来我们就不跟了吧,这个只是 nested 功能开始的一个回调了。
接下来我们关心视差吧,回到 NestedScrollView.onTouchEvent 里面的 ACTION_MOVE 代码段:
if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
mLastMotionY -= mScrollOffset[1];
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
}之所以挑出来这一段,是因为 dispatchNestedScroll 比较眼熟。我们跟进去 dispatchNestedScroll 瞅瞅:
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
offsetInWindow);
}又是 mChildHelper,我之前说过了它很强大,现在信了吧。此处我们跟进去这个 mChildHelper.dispatchNestedScroll 函数看看:
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
int startX = 0;
int startY = 0;
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
startX = offsetInWindow[0];
startY = offsetInWindow[1];
}
ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,
dyConsumed, dxUnconsumed, dyUnconsumed);
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
offsetInWindow[0] -= startX;
offsetInWindow[1] -= startY;
}
return true;
} else if (offsetInWindow != null) {
// No motion, no dispatch. Keep offsetInWindow up to date.
offsetInWindow[0] = 0;
offsetInWindow[1] = 0;
}
}
return false;
}在这个函数中,支持 nested 功能的 parentView 如果愿意消费 MotionEvent,就返回 true,如果不愿意就返回 false。此外还有两个参数 dyUnconsumed 和 offsetInWindow,既是入参也是出参,意为子 View 愿意消费多少 dy。ViewParentCompat.onNestedScroll 就是真正把愿意消费的视差信息传递给 CoordinatorLayout 和 Behavior 了。后续的逻辑跟踪,你应该也大致看的懂了。
读到这份源代码,我一直忽略了坐标值细节的一针一眼的计算。因为比较费时间,费精力。有兴趣的朋友私下去感受就好。
当然,滑动时候,跟 CoordinatorLayout 和 Behavior 打交道还有其它一些函数,比如 stopNestedScroll 和 dispatchNestedFling 等。主要的一个逻辑,都是类似的,就是通过 mChildHelper 找到 mNestedScrollingParent,然后再由 mNestedScrollingParent 找到对应子 View 的 Behavior。限于篇幅问题,此处不再赘述了。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论