日期:
来源:鸿洋收集编辑:layz4android
本文作者
作者:layz4android
链接:
https://juejin.cn/post/7191748934610452538
本文由作者授权发布。
public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent2,
NestedScrollingParent3
(2)子控件之间的嵌套滑动;
(3)子控件宽高的测量;
(4)子控件事件拦截与响应。
1.1 CoordinatorLayout的依赖交互原理
1.2 CoordinatorLayout的嵌套滑动原理
剩下的两个功能就比较简单了,同样也是在Behavior中进行处理,就不做介绍了。
2.1 CoordinatorLayout的依赖交互实现
class DependentView @JvmOverloads constructor(
val mContext: Context,
val attributeSet: AttributeSet? = null,
val flag: Int = 0
) : View(mContext, attributeSet, flag) {
private var paint: Paint
private var mStartX = 0
private var mStartY = 0
init {
paint = Paint()
paint.color = Color.parseColor("#000000")
paint.style = Paint.Style.FILL
isClickable = true
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.let {
it.drawRect(
Rect().apply {
left = 200
top = 200
right = 400
bottom = 400
},
paint
)
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
Log.e("TAG","ACTION_DOWN")
mStartX = event.rawX.toInt()
mStartY = event.rawY.toInt()
}
MotionEvent.ACTION_MOVE -> {
Log.e("TAG","ACTION_MOVE")
val endX = event.rawX.toInt()
val endY = event.rawY.toInt()
val dx = endX - mStartX
val dy = endY - mStartY
ViewCompat.offsetTopAndBottom(this, dy)
ViewCompat.offsetLeftAndRight(this, dx)
postInvalidate()
mStartX = endX
mStartY = endY
}
}
return super.onTouchEvent(event)
}
}
class DependBehavior @JvmOverloads constructor(context: Context, attributeSet: AttributeSet) :
CoordinatorLayout.Behavior<View>(context, attributeSet) {
override fun layoutDependsOn(
parent: CoordinatorLayout,
child: View,
dependency: View
): Boolean {
return dependency is DependentView
}
override fun onDependentViewChanged(
parent: CoordinatorLayout,
child: View,
dependency: View
): Boolean {
//获取dependency的位置
child.x = dependency.x
child.y = dependency.bottom.toFloat()
return true
}
}
2.2 CoordinatorLayout交互依赖的源码分析
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.lay.learn.asm.DependentView
android:layout_width="200dp"
android:layout_height="200dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是跟随者"
app:layout_behavior="com.lay.learn.asm.behavior.DependBehavior"
android:textStyle="bold"
android:textColor="#000000"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
resetTouchBehaviors(false);
if (mNeedsPreDrawListener) {
if (mOnPreDrawListener == null) {
mOnPreDrawListener = new OnPreDrawListener();
}
final ViewTreeObserver vto = getViewTreeObserver();
vto.addOnPreDrawListener(mOnPreDrawListener);
}
if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) {
// We're set to fitSystemWindows but we haven't had any insets yet...
// We should request a new dispatch of window insets
ViewCompat.requestApplyInsets(this);
}
mIsAttachedToWindow = true;
}
class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
@Override
public boolean onPreDraw() {
onChildViewsChanged(EVENT_PRE_DRAW);
return true;
}
}
final void onChildViewsChanged(@DispatchChangeEvent final int type) {
final int layoutDirection = ViewCompat.getLayoutDirection(this);
final int childCount = mDependencySortedChildren.size();
final Rect inset = acquireTempRect();
final Rect drawRect = acquireTempRect();
final Rect lastDrawRect = acquireTempRect();
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
// Do not try to update GONE child views in pre draw updates.
continue;
}
// Update any behavior-dependent views for the change
for (int j = i + 1; j < childCount; j++) {
final View checkChild = mDependencySortedChildren.get(j);
final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
final Behavior b = checkLp.getBehavior();
if (b != null && b.layoutDependsOn(this, checkChild, child)) {
if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
// If this is from a pre-draw and we have already been changed
// from a nested scroll, skip the dispatch and reset the flag
checkLp.resetChangedAfterNestedScroll();
continue;
}
final boolean handled;
switch (type) {
case EVENT_VIEW_REMOVED:
// EVENT_VIEW_REMOVED means that we need to dispatch
// onDependentViewRemoved() instead
b.onDependentViewRemoved(this, checkChild, child);
handled = true;
break;
default:
// Otherwise we dispatch onDependentViewChanged()
handled = b.onDependentViewChanged(this, checkChild, child);
break;
}
if (type == EVENT_NESTED_SCROLL) {
// If this is from a nested scroll, set the flag so that we may skip
// any resulting onPreDraw dispatch (if needed)
checkLp.setChangedAfterNestedScroll(handled);
}
}
}
}
releaseTempRect(inset);
releaseTempRect(drawRect);
releaseTempRect(lastDrawRect);
}
for (int j = i + 1; j < childCount; j++) {
final View checkChild = mDependencySortedChildren.get(j);
final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
final Behavior b = checkLp.getBehavior();
if (b != null && b.layoutDependsOn(this, checkChild, child)) {
if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
// If this is from a pre-draw and we have already been changed
// from a nested scroll, skip the dispatch and reset the flag
checkLp.resetChangedAfterNestedScroll();
continue;
}
final boolean handled;
switch (type) {
case EVENT_VIEW_REMOVED:
// EVENT_VIEW_REMOVED means that we need to dispatch
// onDependentViewRemoved() instead
b.onDependentViewRemoved(this, checkChild, child);
handled = true;
break;
default:
// Otherwise we dispatch onDependentViewChanged()
handled = b.onDependentViewChanged(this, checkChild, child);
break;
}
if (type == EVENT_NESTED_SCROLL) {
// If this is from a nested scroll, set the flag so that we may skip
// any resulting onPreDraw dispatch (if needed)
checkLp.setChangedAfterNestedScroll(handled);
}
}
}
if (mBehaviorResolved) {
mBehavior = parseBehavior(context, attrs, a.getString(
R.styleable.CoordinatorLayout_Layout_layout_behavior));
}
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
final String fullName;
if (name.startsWith(".")) {
// Relative to the app package. Prepend the app package name.
fullName = context.getPackageName() + name;
} else if (name.indexOf('.') >= 0) {
// Fully qualified package name.
fullName = name;
} else {
// Assume stock behavior in this package (if we have one)
fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
? (WIDGET_PACKAGE_NAME + '.' + name)
: name;
}
try {
Map<String, Constructor<Behavior>> constructors = sConstructors.get();
if (constructors == null) {
constructors = new HashMap<>();
sConstructors.set(constructors);
}
Constructor<Behavior> c = constructors.get(fullName);
if (c == null) {
final Class<Behavior> clazz =
(Class<Behavior>) Class.forName(fullName, false, context.getClassLoader());
c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
c.setAccessible(true);
constructors.put(fullName, c);
}
return c.newInstance(context, attrs);
} catch (Exception e) {
throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
}
}
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
Context.class,
AttributeSet.class
};
Could not inflate Behavior subclass com.lay.learn.asm.behavior.DependBehavior
2.3 CoordinatorLayout子控件拦截事件源码分析
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
// Make sure we reset in case we had missed a previous important event.
if (action == MotionEvent.ACTION_DOWN) {
resetTouchBehaviors(true);
}
final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
resetTouchBehaviors(true);
}
return intercepted;
}
private boolean performIntercept(MotionEvent ev, final int type) {
boolean intercepted = false;
boolean newBlock = false;
MotionEvent cancelEvent = null;
final int action = ev.getActionMasked();
final List<View> topmostChildList = mTempList1;
getTopSortedChildren(topmostChildList);
// Let topmost child views inspect first
final int childCount = topmostChildList.size();
for (int i = 0; i < childCount; i++) {
final View child = topmostChildList.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior b = lp.getBehavior();
if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
// Cancel all behaviors beneath the one that intercepted.
// If the event is "down" then we don't have anything to cancel yet.
if (b != null) {
if (cancelEvent == null) {
final long now = SystemClock.uptimeMillis();
cancelEvent = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
}
switch (type) {
case TYPE_ON_INTERCEPT:
b.onInterceptTouchEvent(this, child, cancelEvent);
break;
case TYPE_ON_TOUCH:
b.onTouchEvent(this, child, cancelEvent);
break;
}
}
continue;
}
if (!intercepted && b != null) {
switch (type) {
case TYPE_ON_INTERCEPT:
intercepted = b.onInterceptTouchEvent(this, child, ev);
break;
case TYPE_ON_TOUCH:
intercepted = b.onTouchEvent(this, child, ev);
break;
}
if (intercepted) {
mBehaviorTouchView = child;
}
}
// Don't keep going if we're not allowing interaction below this.
// Setting newBlock will make sure we cancel the rest of the behaviors.
final boolean wasBlocking = lp.didBlockInteraction();
final boolean isBlocking = lp.isBlockingInteractionBelow(this, child);
newBlock = isBlocking && !wasBlocking;
if (isBlocking && !newBlock) {
// Stop here since we don't have anything more to cancel - we already did
// when the behavior first started blocking things below this point.
break;
}
}
topmostChildList.clear();
return intercepted;
}
2.4 CoordinatorLayout嵌套滑动原理分析
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) {
int xConsumed = 0;
int yConsumed = 0;
boolean accepted = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
if (view.getVisibility() == GONE) {
// If the child is GONE, skip...
continue;
}
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted(type)) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
mBehaviorConsumed[0] = 0;
mBehaviorConsumed[1] = 0;
viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mBehaviorConsumed, type);
xConsumed = dx > 0 ? Math.max(xConsumed, mBehaviorConsumed[0])
: Math.min(xConsumed, mBehaviorConsumed[0]);
yConsumed = dy > 0 ? Math.max(yConsumed, mBehaviorConsumed[1])
: Math.min(yConsumed, mBehaviorConsumed[1]);
accepted = true;
}
}
consumed[0] = xConsumed;
consumed[1] = yConsumed;
if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
}
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="#2196F3"
android:text="这是顶部TextView"
android:gravity="center"
android:textColor="#FFFFFF"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
class ScrollBehavior @JvmOverloads constructor(
val mContext: Context,
val attributeSet: AttributeSet
) : CoordinatorLayout.Behavior<TextView>(mContext, attributeSet) {
//相对于y轴滑动的距离
private var mScrollY = 0
//总共滑动的距离
private var totalScroll = 0
override fun onLayoutChild(
parent: CoordinatorLayout,
child: TextView,
layoutDirection: Int
): Boolean {
Log.e("TAG", "onLayoutChild----")
//实时测量
parent.onLayoutChild(child, layoutDirection)
return true
}
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: TextView,
directTargetChild: View,
target: View,
axes: Int,
type: Int
): Boolean {
//目的为了dispatch成功
return true
}
override fun onNestedPreScroll(
coordinatorLayout: CoordinatorLayout,
child: TextView,
target: View,
dx: Int,
dy: Int,
consumed: IntArray,
type: Int
) {
//边界处理
var cosumedy = dy
Log.e("TAG","onNestedPreScroll $totalScroll dy $dy")
var scroll = totalScroll + dy
if (abs(scroll) > getMaxScroll(child)) {
cosumedy = getMaxScroll(child) - abs(totalScroll)
} else if (scroll < 0) {
cosumedy = 0
}
//在这里进行事件消费,我们只需要关心竖向滑动
ViewCompat.offsetTopAndBottom(child, -cosumedy)
//重新赋值
totalScroll += cosumedy
consumed[1] = cosumedy
}
private fun getMaxScroll(child: TextView): Int {
return child.height
}
}
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="#2196F3"
android:text="这是顶部TextView"
android:gravity="center"
android:textColor="#FFFFFF"
app:layout_behavior=".behavior.ScrollBehavior"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
class RecyclerViewBehavior @JvmOverloads constructor(
val context: Context,
val attributeSet: AttributeSet
) : CoordinatorLayout.Behavior<RecyclerView>(context, attributeSet) {
override fun layoutDependsOn(
parent: CoordinatorLayout,
child: RecyclerView,
dependency: View
): Boolean {
return dependency is TextView
}
override fun onDependentViewChanged(
parent: CoordinatorLayout,
child: RecyclerView,
dependency: View
): Boolean {
Log.e("TAG","onDependentViewChanged ${dependency.bottom} ${child.top}")
ViewCompat.offsetTopAndBottom(child,(dependency.bottom - child.top))
return true
}
}
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="#2196F3"
android:text="这是顶部TextView"
android:gravity="center"
android:textColor="#FFFFFF"
app:layout_behavior=".behavior.ScrollBehavior"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
app:layout_behavior=".behavior.RecyclerViewBehavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
点击 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!