9anchor

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>
<think>嗯,用户现在在问关于“anchor indicator”在机器学习、目标检测和数据标注中的相关技术概念或实现方法。首先,我需要确认用户的问题意图,可能他们是在目标检测中遇到了与anchor相关的技术点,比如如何生成或调整anchor boxes,或者如何利用anchor indicator进行数据标注。 接下来,我需要回忆一下anchor在目标检测中的作用。通常,anchor boxes是预定义的框,用于检测不同形状和大小的目标。比如Faster R-CNN和YOLO系列都使用anchor机制。用户提到的“anchor indicator”可能指的是与这些anchor相关的指示器或生成方法,比如如何确定哪些anchor与真实框匹配,或者如何根据anchor生成标注数据。 然后,结合用户提供的引用内容,特别是引用[1]中提到目标分配器(target assigner)的作用,将3D目标与体素中的特征向量进行匹配,生成标注数据。这里可能涉及到类似2D目标检测中的anchor匹配机制。例如,在2D检测中,目标分配器会根据IoU(交并比)来分配anchor到真实框,正样本和负样本的划分就是通过这种匹配完成的。这可能就是用户所指的“anchor indicator”的一部分。 另外,引用[3]中提到了数据增强的方法,虽然不直接相关,但数据增强可能会影响anchor的设计,比如不同尺度或旋转后的图像需要调整anchor的大小或比例。不过用户的问题更偏向于anchor的生成和匹配,而不是数据增强。 还需要考虑用户提到的数据标注部分。在目标检测中,数据标注通常是边界框和类别标签,而anchor indicator可能涉及如何根据标注的真实框生成匹配的anchor,或者指示哪些anchor需要被激活。例如,在训练过程中,每个anchor会被分配一个二进制的标签(是或不是目标),这可以看作是一种indicator。 可能用户对anchor的生成、匹配策略以及如何在训练中使用这些anchor作为指标感兴趣。比如,anchor的尺寸、比例如何设置,正负样本的划分标准,以及如何处理anchor和真实框之间的回归问题。 此外,用户提供的引用[2]中提到了传统目标检测和深度学习方法的区别,其中Faster R-CNN等基于anchor的方法属于深度学习目标检测。这里可能需要解释anchor-based方法和anchor-free方法的区别,但用户明确提到了anchor indicator,所以应聚焦在anchor-based方法上。 总结来说,用户可能需要了解anchor在目标检测中的具体作用,如何生成和匹配anchor,以及这些过程在数据标注和模型训练中的实现方法。需要结合引用中提到的目标分配器、框编码器等概念,解释anchor indicator的可能含义和相关技术细节。</think>### Anchor Indicator 在目标检测中的技术解析 #### 1. **核心概念** 在目标检测中,**Anchor Indicator**(锚框指示器)是一种用于生成或筛选与真实目标框(Ground Truth)匹配的预定义锚框(Anchor Box)的机制。其核心作用是: - **定义候选区域**:通过预定义的锚框覆盖不同尺度和宽高比,作为目标位置的候选区域。 - **匹配真实框**:通过交并比(IoU)等指标,将锚框与真实目标框关联,用于训练时分类与回归任务的监督信号[^1][^3]。 - **指示正负样本**:根据匹配结果,标记哪些锚框属于正样本(包含目标)、负样本(背景)或忽略样本,从而优化模型训练效率[^3]。 #### 2. **技术实现方法** **(1) Anchor生成与匹配** - **锚框生成**: 在特征图上按网格生成锚框,每个位置对应多个尺度和宽高比的锚框。例如,Faster R-CNN中每个位置生成9个锚框(3种尺度 × 3种宽高比)[^2]。 $$ \text{锚框坐标} = (x_{\text{center}}, y_{\text{center}}, w, h) $$ - **匹配策略**: 通过计算锚框与真实框的IoU,选择最高IoU的锚框作为正样本,或设定阈值(如IoU > 0.7为正样本,<0.3为负样本)[^3]。 **(2) 目标分配与损失计算** - **分类任务**:正样本锚框的类别标签为真实目标类别,负样本标签为背景。 - **回归任务**:对正样本锚框计算偏移量,使其逼近真实框的位置和尺寸: $$ \Delta x = \frac{x_{\text{gt}} - x_{\text{anchor}}}{w_{\text{anchor}}}, \quad \Delta w = \log\left(\frac{w_{\text{gt}}}{w_{\text{anchor}}}\right) $$ 类似地定义$\Delta y$和$\Delta h$[^1][^3]。 **(3) Anchor优化技术** - **动态Anchor分配**:如ATSS(Adaptive Training Sample Selection),根据统计特性动态调整正负样本阈值[^3]。 - **Anchor-free替代方案**:某些方法(如CenterNet)摒弃锚框,直接预测目标中心点和尺寸,减少超参数依赖[^2]。 #### 3. **数据标注中的关联** - **标注格式**:真实框需包含类别和坐标信息(如COCO格式的`[x_min, y_min, width, height]`)。 - **半自动标注**:利用预训练模型生成锚框匹配建议,人工修正低置信度样本,提升标注效率[^1][^3]。 #### 4. **代码示例(Anchor匹配)** ```python import torch def match_anchors(anchors, gt_boxes, iou_threshold=0.5): # 计算锚框与真实框的IoU矩阵 iou_matrix = compute_iou(anchors, gt_boxes) # 为每个真实框选择最佳匹配的锚框 max_iou, max_indices = iou_matrix.max(dim=0) # 标记正样本(IoU > threshold) positive_mask = (max_iou >= iou_threshold) return positive_mask def compute_iou(boxes1, boxes2): # 实现IoU计算 # ... return iou_matrix ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值