- 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. fab 与 CoordinatorLayout 的交互
这块内容因为与 CoordinatorLayout
/ CoordinatorLayout#Behavior
有很大关联,如果不熟悉,请先 google 相关资料。本文假设读者对这块内容已经有一定理解。
fab 并不直接与 CoordinatorLayout
联系,而是通过 CoordinatorLayout#Behavior
作为桥梁。 CoordinatorLayout
类通过 CoordinatorLayout#Behavior
可以间接控制其直系子 View 的行为,能控制什么行为?View 测量、布局、touch 事件拦截、监听、NestedScroll 等等。是不是很屌。
fab 内部实现了 CoordinatorLayout#Behavior
抽象类。该抽象类有如下接口:
public static abstract class Behavior<V extends View> { ... public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) { return false; } public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) { return false; } ... /** * Determine whether the supplied child view has another specific sibling view as a * layout dependency. * * <p>This method will be called at least once in response to a layout request. If it * returns true for a given child and dependency view pair, the parent CoordinatorLayout * will:</p> * <ol> * <li>Always lay out this child after the dependent child is laid out, regardless * of child order.</li> * <li>Call {@link #onDependentViewChanged} when the dependency view's layout or * position changes.</li> * </ol> */ public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { return false; } /** * Respond to a change in a child's dependent view * * <p>This method is called whenever a dependent view changes in size or position outside * of the standard layout flow. A Behavior may use this method to appropriately update * the child view in response.</p> * * <p>A view's dependency is determined by * {@link #layoutDependsOn(CoordinatorLayout, android.view.View, android.view.View)} or * if {@code child} has set another view as it's anchor.</p> * * <p>Note that if a Behavior changes the layout of a child via this method, it should * also be able to reconstruct the correct position in * {@link #onLayoutChild(CoordinatorLayout, android.view.View, int) onLayoutChild}. * <code>onDependentViewChanged</code> will not be called during normal layout since * the layout of each child view will always happen in dependency order.</p> * * <p>If the Behavior changes the child view's size or position, it should return true. * The default implementation returns false.</p> * */ public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) { return false; } ... /** * Called when the parent CoordinatorLayout is about the lay out the given child view. * * <p>This method can be used to perform custom or modified layout of a child view * in place of the default child layout behavior. The Behavior's implementation can * delegate to the standard CoordinatorLayout measurement behavior by calling * {@link CoordinatorLayout#onLayoutChild(android.view.View, int) * parent.onLayoutChild}.</p> * * <p>If a Behavior implements * {@link #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)} * to change the position of a view in response to a dependent view changing, it * should also implement <code>onLayoutChild</code> in such a way that respects those * dependent views. <code>onLayoutChild</code> will always be called for a dependent view * <em>after</em> its dependency has been laid out.</p> * */ public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) { return false; } ... public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { // Do nothing } }
看到这个抽象类,有两点需要注意:
- 此抽象类并无抽象方法,也即子类可选择任何想复写的方法进行复写。
- 此抽象类接受一个泛型。该泛型需要是 View 的子类。
fab 实现此抽象类:
public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> {}
有选择性地实现了三个方法:
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency); public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency); public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child, int layoutDirection);
fab 为啥要实现 Behavior
?主要是为了配合其他控件完成一些复杂的交互,比较经典的像这个: fab 动画效果
fab 需要在 snackBar
弹出的时候自动向上平移,这就得知道 SnackBar 的状态了,实现 Behavior
让 fab 有机会监听到其他 CoordinatorLayout
子 View 的状态,并根据状态更新自己。
复写 layoutDependsOn
方法可以告诉 CoordinatorLayout
我对哪个 View 感兴趣,
这里当然是 SnackBar 了。(注意哦,SnackBar 最终展现的是 SnackbarLayout,SnackBar 本身并不是 View)
private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11; @Override public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) { // We're dependent on all SnackbarLayouts (if enabled) return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout; }
为什么 API LEVEL 要大于 11 呢?因为 google 偷懒想直接使用属性动画。
前面告诉了 CoordinatorLayout
fab 对 SnackBar
比较感兴趣,那么当 SnackBar 状态改变的时候, CoordinatorLayout
就会通过 onDependentViewChanged
回调通知 fab:
fab 就可以更新自己的 UI 拉(这里当然是平移喽):
@Override public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) { if (dependency instanceof Snackbar.SnackbarLayout) { updateFabTranslationForSnackbar(parent, child, dependency); } else if (dependency instanceof AppBarLayout) { // If we're depending on an AppBarLayout we will show/hide it automatically // if the FAB is anchored to the AppBarLayout updateFabVisibility(parent, (AppBarLayout) dependency, child); } return false; }
如果是 SnackBar 状态变化了,那么 fab 就会根据情况进行平移:
private void updateFabTranslationForSnackbar(CoordinatorLayout parent, final FloatingActionButton fab, View snackbar) { final float targetTransY = getFabTranslationYForSnackbar(parent, fab); if (mFabTranslationY == targetTransY) { // We're already at (or currently animating to) the target value, return... return; } final float currentTransY = ViewCompat.getTranslationY(fab); // Make sure that any current animation is cancelled if (mFabTranslationYAnimator != null && mFabTranslationYAnimator.isRunning()) { mFabTranslationYAnimator.cancel(); } if (fab.isShown() && Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) { // If the FAB will be travelling by more than 2/3 of it's height, let's animate // it instead if (mFabTranslationYAnimator == null) { mFabTranslationYAnimator = ViewUtils.createAnimator(); mFabTranslationYAnimator.setInterpolator( AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); mFabTranslationYAnimator.setUpdateListener( new ValueAnimatorCompat.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimatorCompat animator) { ViewCompat.setTranslationY(fab, animator.getAnimatedFloatValue()); } }); } mFabTranslationYAnimator.setFloatValues(currentTransY, targetTransY); mFabTranslationYAnimator.start(); } else { // Now update the translation Y ViewCompat.setTranslationY(fab, targetTransY); } mFabTranslationY = targetTransY; }
代码里的注释很多,我就不解释了。
前面说到 AppBarLayout 和 fab 一起使用可以完成另一个效果,即 AppBarLayout 伸缩时,fab 也可以以动画的形式显现、隐藏,其实现如下:
private boolean updateFabVisibility(CoordinatorLayout parent, AppBarLayout appBarLayout, FloatingActionButton child) { final CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); //注意到我们必须为 fab 指定 layout_anchor 为 appBarLayout if (lp.getAnchorId() != appBarLayout.getId()) { // The anchor ID doesn't match the dependency, so we won't automatically // show/hide the FAB return false; } if (child.getUserSetVisibility() != VISIBLE) { // The view isn't set to be visible so skip changing it's visibility return false; } if (mTmpRect == null) { mTmpRect = new Rect(); } // First, let's get the visible rect of the dependency final Rect rect = mTmpRect; ViewGroupUtils.getDescendantRect(parent, appBarLayout, rect); if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) { // If the anchor's bottom is below the seam, we'll animate our FAB out child.hide(null, false); } else { // Else, we'll animate our FAB back in child.show(null, false); } return true; }
除此之外, fab#Behavior
还实现了 onLayoutChild
,主要是为了根据 AppBarLayout 的当前状态来判断自己是否需要隐藏。
@Override public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child, int layoutDirection) { // First, lets make sure that the visibility of the FAB is consistent final List<View> dependencies = parent.getDependencies(child); for (int i = 0, count = dependencies.size(); i < count; i++) { final View dependency = dependencies.get(i); if (dependency instanceof AppBarLayout && updateFabVisibility(parent, (AppBarLayout) dependency, child)) { break; } } // Now let the CoordinatorLayout lay out the FAB parent.onLayoutChild(child, layoutDirection); // Now offset it if needed offsetIfNeeded(parent, child); return true; }
此方法会在 CoordinatorLayout
对孩子布局的时候进行调用(即 CoordinatorLayout#onLayout
), CoordinatorLayout
会检查所有的直系孩子,是否设置了 Behavior,如果设置了,那么就执行其 onLayoutChild
方法:
CoordinatorLayout#onLayout
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int layoutDirection = ViewCompat.getLayoutDirection(this); final int childCount = mDependencySortedChildren.size(); for (int i = 0; i < childCount; i++) { final View child = mDependencySortedChildren.get(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Behavior behavior = lp.getBehavior(); if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) { onLayoutChild(child, layoutDirection); } } }
如果该 Behavior 实现了 OnLayoutChild,并且返回了 true,那么将不会执行 CoordinatorLayout #onLayoutChild
,否则执行默认的布局方案。 最后一点,这里的 Behavior 如何生效的呢?通过注解:
@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class) public class FloatingActionButton extends VisibilityAwareImageButton {
CoordinatorLayout
在解析孩子的 LayoutParams
时,会 check 有无注解:
LayoutParams getResolvedLayoutParams(View child) { final LayoutParams result = (LayoutParams) child.getLayoutParams(); if (!result.mBehaviorResolved) { Class<?> childClass = child.getClass(); DefaultBehavior defaultBehavior = null; while (childClass != null && (defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) { childClass = childClass.getSuperclass(); } if (defaultBehavior != null) { try { result.setBehavior(defaultBehavior.value().newInstance()); } catch (Exception e) { Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName() + " could not be instantiated. Did you forget a default constructor?", e); } } result.mBehaviorResolved = true; } return result; }
至此 fab
解析完毕,谢谢观看!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论