在Android视图剪辑边界外绘图时:如何防止底层视图在我的自定义视图上绘制?

时间:2014-07-30 18:17:43

标签: android android-layout android-custom-view

我写了一个自定义的Android视图,需要在其剪切边界之外绘制。

这就是我所拥有的:

enter image description here

这是当我点击按钮时发生的情况,例如右键:

enter image description here

如何阻止下面的视图在我的“句柄”之上绘制?

我项目中的一些相关伪代码如下。

我的自定义视图 MyHandleView 的绘制方式如下:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Path p = mPath;
    int handleWidth = mHandleWidth;
    int handleHeight = mHandleHeight;
    int left = (getWidth() >> 1) - handleWidth;

    canvas.save();

    // allow drawing out of bounds vertically
    Rect clipBounds = canvas.getClipBounds();
    clipBounds.inset(0, -handleHeight);
    canvas.clipRect(clipBounds, Region.Op.REPLACE);

    // translate up to draw the handle
    canvas.translate(left, -handleHeight);

    // draw my background shape
    canvas.drawPath(p, mPaint);

    canvas.restore();
}

布局是这样的(我简化了一点):

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Main content of the SlidingUpPanel -->
    <fragment
        android:above=@+id/panel"
        class="com.neosperience.projects.percassi.android.home.HomeFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="?android:attr/actionBarSize"
        tools:layout="@layout/fragment_home" />

    <!-- The Sliding Panel -->
    <LinearLayout
        android:id="@id/panel"
        android:layout_width="match_parent"
        android:layout_height="@dimen/myFixedSize"
        android:alignParentBottom="true"
        android:orientation="vertical"
        android:clipChildren="false">

        <MyHandleView  xmlns:custom="http://schemas.android.com/apk/res-auto.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            custom:handleHeight="@dimen/card_panel_handle_height"
            custom:handleWidthRatio="@dimen/card_panel_handle_width_ratio"
            custom:handleBackgroundColor="#000"/>

        <TextView
            android:id="@+id/loyaltyCardPanelTitle"
            android:layout_width="match_parent"
            android:layout_height="@dimen/card_panel_height"
            android:background="#000"
            android:gravity="center"
            android:textStyle="bold"
            android:textSize="24sp"
            android:textColor="#fff"
            android:text="My TEXT"/>
    </LinearLayout>

</RelativeLayout>

您可以将片段视为包含底部两个按钮的视图,位于LinearLayout中。

代替外部RelativeLayout,我实际上正在使用库中的视图:SlidingUpPanelLayout(https://github.com/umano/AndroidSlidingUpPanel)。但是我用这个RelativeLayout来测试行为:同样的事情,这意味着库没有相关性。

我这样说只是为了让你知道我不能只放置一个FrameLayout并避免在剪裁范围之外绘图

我怀疑这与以下事实有关:当我触摸按钮时它重绘自己,但我的另一个视图(在层次结构中的其他位置)不会被重新绘制(这是一个优化,我怀疑它可以被禁用)。

当任何其他“近”(或任何)视图失效时,我希望能够使我的自定义视图无效,因此我需要一些关于视图失效的监听器,但我不知道任何。

有人可以帮忙吗?

1 个答案:

答案 0 :(得分:84)

我自己找到了解决方案,即使这不是表演的最佳选择。

只需添加:

android:clipChildren="false"

到RelativeLayout(或你拥有的任何布局)。

这有2个效果(可能更多,这是我感兴趣的两个): - ViewGroup没有剪辑他的孩子的画(显而易见) - 在考虑要重绘的子项时,ViewGroup不检查与脏区域的交集(无效)

我挖掘了有关无效的View代码。

这个过程更像或更喜欢这样:

  1. 一个视图使自身无效,它通常绘制的区域(一个矩形)成为一个&#34;脏区域&#34;要重新绘制
  2. 视图告诉其父级(某种类型的ViewGroup)需要重绘自己
  3. 父母对其根本
  4. 的父母做同样的事
  5. 每个子节点的层次结构中的每个父节点并检查脏区域是否与其中一些相交
  6. 如果它也重绘它们
  7. 在第4步中涉及裁剪:仅当clipChildren为true时,ViewGroup检查其子项的视图边界:意味着如果将其置为false,则在其中任何一个无效时始终重绘其所有子项

    所以,我的View层次结构是这样的:

    ViewGroup A
    |
    |----- fragment subtree (containing buttons, map,
    |       whatever element that I don't want to draw
    |       on top of my handle)
    |
    |----- ViewGroup B
           |
           |---- my handle (which draw outside its clip bounds)
    

    在我的情况下,&#34;句柄&#34;在一些通常由片段子树的某个元素绘制的东西之上绘制它的束缚。

    当片段中的任何视图无效时,它会通过其#34;脏区域&#34;在视图树中,每个视图组检查是否有其他子项要在该区域中重绘。

    如果我没有在其上设置 clipBounds =&#34; false&#34; ,ViewGroup B会剪切我在剪辑边界外绘制的内容。

    如果片段子树中的任何内容失效,ViewGroup A将看到ViewGroup B脏区域未与片段子树区域相交,并将跳过重绘ViewGroup B。

    但如果我还告诉ViewGroup A不剪辑子节点,它仍会给ViewGroup B一个无效命令,这将导致重绘我的句柄。

    所以解决方案是确保设置

    android:clipChildren="false"
    

    在View上方的层次结构中的任何ViewGroup上绘制出内容可能落在&#34;&#34;下的#34;你正在绘制的越界地区。

    明显的副作用是每当我使ViewGroup A中的任何视图无效时,无效区域将被转发到其中的所有视图。

    然而,任何与viewGroup内的脏区域相交的视图与clipChildren =&#34; true&#34; (默认)将被跳过。

    因此,为避免性能问题,请确保您的视图组使用clipChildren =&#34; true&#34;没有多少&#34;标准&#34;直接的孩子。用&#34;标准&#34;我的意思是他们不会超出他们的视角。

    因此,例如,如果在我的示例中,ViewGroup B包含许多视图,请考虑使用clipChildren =&#34; true&#34;将所有这些视图包装在ViewGroup中。并且只保留此视图组以及在其区域之外绘制的一个视图作为ViewGroup B的直接子项。对于ViewGroup A也是如此。

    这个简单的事实将确保没有其他视图会在无效的脏区域中进行重绘,从而最大限度地减少所需的重绘。

    如果有人有更多的考虑,我仍然愿意听取更多的考虑;)

    所以我等一下,然后将其标记为已接受的答案。

      

    编辑:许多设备在处理clipChildren =&#34; false&#34;时做了不同的事情。我发现我必须设置clipChildren =&#34; false&#34;在我的自定义窗口小部件的所有父视图上可能包含其层次结构中的元素,这些元素应该覆盖&#34;超出范围的区域&#34;的小部件或您可能会看到您的自定义绘图显示应该覆盖它的另一个视图的ON TOP。例如,在我的布局中,我有一个导航抽屉,应该覆盖我的&#34;手柄&#34;。如果我没有设置clipChildren =&#34; false&#34;在NavigationDrawer布局上,我有时可能会看到我的手柄弹出打开的抽屉前面。

         

    EDIT2:我的自定义小部件的高度为0,并在顶部&#34;上绘制了#34;本身。在Nexus设备上工作得很好,但其他许多设备都有一些优化&#34;完全跳过具有0高度或0宽度的视图的绘图。因此,如果你想要编写一个从它开始绘制的组件,请注意这一点:你必须为它分配至少1个像素的高度/宽度。