9anchor
来源:互联网 发布:钢结构公司起名 知乎 编辑:程序博客网 时间:2024/05/18 10:57
CoordinatorLayout还提供了一种布局方式叫anchor,看下边效果
对应xml
<android.support.design.widget.FloatingActionButton android:id="@+id/fab" app:layout_anchor="@id/appbar" app:layout_anchorGravity="bottom|right" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/fab_margin" android:src="@android:drawable/ic_dialog_email" app:layout_behavior="com.fish.behaviordemo.fab.MyBehavior" />
这里用了anchor把fab anchor到appbar上,anchor在appbar哪个位置呢?看layout_anchorGravity,这里是放在右下方。
布局
此时有2个问题,为什么fab会有一半在appbar内?写了layout_margin为什么只有右边生效了。
为什么一开始fab有一半在appbar内部,一般在appbar外部呢?因为fab没写layout_gravity,所以垂直方向是居中的,居中就会有一半在appbar内部(原因后边会讲),如果写个 android:layout_gravity=”bottom”,那就可以使得fab在appbar下方了,如下所示。
CoordinatorLayout.LayoutParams内部有4个成员是和anchor相关的,anchorGravity,mAnchorId,mAnchorView,mAnchorDirectChild。
mAnchorId是由app:layout_anchor指定的anchor对象的id。
mAnchorView是anchor的view,跟mAnchorId对应的,不一定是CoordinatorLayout的直接子view。
mAnchorDirectChild是CoordinatorLayout的直接子view,是mAnchorView本身或者祖先。
anchorGravity是anchor的方式,比如本文中就是bottom|right
//CoordinatorLayout.LayoutParams public static class LayoutParams extends ViewGroup.MarginLayoutParams { 。。。 /** * A {@link Gravity} value describing which edge of a child view's * {@link #getAnchorId() anchor} view the child should position itself relative to. */ public int anchorGravity = Gravity.NO_GRAVITY; int mAnchorId = View.NO_ID; View mAnchorView; View mAnchorDirectChild; }
app:layout_anchor=”@id/appbar”这句话会导致fab的LayoutParams内有mAnchorView指向appbar。再看CoordinatorLayout的布局代码,可以看到mAnchorView非空会调用layoutChildWithAnchor
//CoordinatorLayout public void onLayoutChild(View child, int layoutDirection) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp.checkAnchorChanged()) { throw new IllegalStateException("An anchor may not be changed after CoordinatorLayout" + " measurement begins before layout is complete."); } if (lp.mAnchorView != null) { layoutChildWithAnchor(child, lp.mAnchorView, layoutDirection); } else if (lp.keyline >= 0) { layoutChildWithKeyline(child, lp.keyline, layoutDirection); } else { layoutChild(child, layoutDirection); } }
再看layoutChildWithAnchor
//CoordinatorLayout private void layoutChildWithAnchor(View child, View anchor, int layoutDirection) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Rect anchorRect = mTempRect1; final Rect childRect = mTempRect2; getDescendantRect(anchor, anchorRect); getDesiredAnchoredChildRect(child, layoutDirection, anchorRect, childRect); child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom); }
而layoutChildWithAnchor内蛀要看getDesiredAnchoredChildRect。这里是根据anchorView的位置以及view的layout_anchorGravity、layout_gravity来敲定当前view的位置,layout_gravity 没写的话,L73可以看到会往上移动半个身位的高度,问题1解决。再看L84-L88,可以理解xml内的android:layout_margin限制的是离CoordinatorLayout上下左右的间距,大于等于这个值就可以了。
void getDesiredAnchoredChildRect(View child, int layoutDirection, Rect anchorRect, Rect out) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int absGravity = GravityCompat.getAbsoluteGravity( resolveAnchoredChildGravity(lp.gravity), layoutDirection); final int absAnchorGravity = GravityCompat.getAbsoluteGravity( resolveGravity(lp.anchorGravity), layoutDirection); final int hgrav = absGravity & Gravity.HORIZONTAL_GRAVITY_MASK; final int vgrav = absGravity & Gravity.VERTICAL_GRAVITY_MASK; final int anchorHgrav = absAnchorGravity & Gravity.HORIZONTAL_GRAVITY_MASK; final int anchorVgrav = absAnchorGravity & Gravity.VERTICAL_GRAVITY_MASK; final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); int left; int top; // Align to the anchor. This puts us in an assumed right/bottom child view gravity. // If this is not the case we will subtract out the appropriate portion of // the child size below. switch (anchorHgrav) { default: case Gravity.LEFT: left = anchorRect.left; break; case Gravity.RIGHT: left = anchorRect.right; break; case Gravity.CENTER_HORIZONTAL: left = anchorRect.left + anchorRect.width() / 2; break; } switch (anchorVgrav) { default: case Gravity.TOP: top = anchorRect.top; break; case Gravity.BOTTOM: top = anchorRect.bottom; break; case Gravity.CENTER_VERTICAL: top = anchorRect.top + anchorRect.height() / 2; break; } // Offset by the child view's gravity itself. The above assumed right/bottom gravity. switch (hgrav) { default: case Gravity.LEFT: left -= childWidth; break; case Gravity.RIGHT: // Do nothing, we're already in position. break; case Gravity.CENTER_HORIZONTAL: left -= childWidth / 2; break; } switch (vgrav) { default: case Gravity.TOP: top -= childHeight; break; case Gravity.BOTTOM: // Do nothing, we're already in position. break; case Gravity.CENTER_VERTICAL: //往上移一半高度 top -= childHeight / 2; break; } //注意这是CoordinatorLayout的宽高 final int width = getWidth(); final int height = getHeight(); // Obey margins and padding left = Math.max(getPaddingLeft() + lp.leftMargin, Math.min(left, width - getPaddingRight() - childWidth - lp.rightMargin)); top = Math.max(getPaddingTop() + lp.topMargin, Math.min(top, height - getPaddingBottom() - childHeight - lp.bottomMargin)); out.set(left, top, left + childWidth, top + childHeight); }
滑动
为什么fab会随着appbar的滑动而滑动呢?
anchor顾名思义是固定在某个view上的,这个view滑动,他也如影随形,这是怎么实现的?
首先当前view和anchorview直接建立依赖关系,依赖于anchorview. anchorview滑动的时候会触发dispatchOnDependentViewChanged,内部调用offsetChildToAnchor
//CoordinatorLayout void dispatchOnDependentViewChanged(final boolean fromNestedScroll) { 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(); // Check child views before for anchor for (int j = 0; j < i; j++) { final View checkChild = mDependencySortedChildren.get(j); if (lp.mAnchorDirectChild == checkChild) { //key code offsetChildToAnchor(child, layoutDirection); } } 。。。 }
关键代码offsetChildToAnchor,可以看到里面跟layoutChildWithAnchor的逻辑类似的,进行offsetLeftAndRight、offsetTopAndBottom来改变位置。
//CoordinatorLayout /** * Adjust the child left, top, right, bottom rect to the correct anchor view position, * respecting gravity and anchor gravity. * * Note that child translation properties are ignored in this process, allowing children * to be animated away from their anchor. However, if the anchor view is animated, * the child will be offset to match the anchor's translated position. */ void offsetChildToAnchor(View child, int layoutDirection) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp.mAnchorView != null) { final Rect anchorRect = mTempRect1; final Rect childRect = mTempRect2; final Rect desiredChildRect = mTempRect3; getDescendantRect(lp.mAnchorView, anchorRect); getChildRect(child, false, childRect); getDesiredAnchoredChildRect(child, layoutDirection, anchorRect, desiredChildRect); final int dx = desiredChildRect.left - childRect.left; final int dy = desiredChildRect.top - childRect.top; if (dx != 0) { child.offsetLeftAndRight(dx); } if (dy != 0) { child.offsetTopAndBottom(dy); } if (dx != 0 || dy != 0) { // If we have needed to move, make sure to notify the child's Behavior final Behavior b = lp.getBehavior(); if (b != null) { b.onDependentViewChanged(this, child, lp.mAnchorView); } } } }
fab默认的
我们去掉这行代码,看看会发生什么
app:layout_behavior=”com.fish.behaviordemo.fab.MyBehavior”
滑到顶的时候fab消失了,这是怎么做到的呢?
此时的behavior由注解决定,是FloatingActionButton.Behavior。相关代码如下,在onDependentViewChanged发现dependency是AppBarLayout就会调用updateFabVisibility
//FloatingActionButton.Behavior @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; }
再看updateFabVisibility,
//FloatingActionButton.Behavior private boolean updateFabVisibility(CoordinatorLayout parent, AppBarLayout appBarLayout, FloatingActionButton child) { final CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); 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; }
这里主要看L22,首先获取appBarLayout的可见区域rect,然后根据rect的bottom来判断是否够小了,够小了,就hide隐藏掉,否则就show显示。那隐藏的动画是怎么实现的呢?相关代码在design_fab_out.xml内部,如下所示,简单易懂。
<set xmlns:android="http://schemas.android.com/apk/res/android"> <alpha android:fromAlpha="1.0" android:toAlpha="0.0"/> <scale android:fromXScale="1.0" android:fromYScale="1.0" android:toXScale="0.0" android:toYScale="0.0" android:pivotX="50%" android:pivotY="50%"/></set>
- 9anchor
- Anchor
- anchor
- Anchor Point
- anchor对象
- css-anchor
- Anchor text 的重要性
- JScript方法-anchor方法
- IE中取anchor
- 自定义控件Anchor
- Anchor和Duck
- cocos2d anchor point 解析
- Extjs4----anchor布局
- Is Anchor magento
- what is anchor
- Table Elimination & Anchor modeling
- 什么是锚(anchor)
- HTML锚 Anchor
- Android 中的接口回调
- Oracle DBA日常工作手册
- 快速排序算法
- DataTable字符串类型的数字,按照数字类型排序
- C++ 中的 Lambda 表达式
- 9anchor
- 【基础知识思考整理】函数指针有什么用?
- Excel 插件开发
- 在 eclipse 中 配置 tomcat 服务器
- log4j2
- HDU 5889 Barricade 最短路最小割 -
- 泊松分布采样 (Poisson-Disk-Sample)代码及详细注释【OpenCV】
- 面向服务的体系结构与企业体系结构,第 2 部分: 相似点与不同处
- 模型构建问题