使用Android在视图剪辑范围之外进行绘制时:如何防止底层视图在自定义视图的顶部绘制? [英] When drawing outside the view clip bounds with Android: how do I prevent underling views from drawing on top of my custom view?

查看:71
本文介绍了使用Android在视图剪辑范围之外进行绘制时:如何防止底层视图在自定义视图的顶部绘制?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了一个自定义Android视图,该视图需要在其裁剪边界之外绘制.

I'm wrote a custom Android View that need to draw outside its clipping Bounds.

这就是我所拥有的:

当我单击一个按钮(例如右键)时,会发生以下情况:

This is what happens when I click a button, say the right button:

如何防止下面的视图在我的手柄"上绘制?

How do I prevent the View below to draw on top of my "handle"?

跟随我项目中的一些相关伪代码.

Some related pseudo-code from my project follow.

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

My custom view MyHandleView draw like this:

@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();
}

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

The layout is something like this (I simplified a little):

<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的底部包含两个按钮.

You can think of the fragment as a view containing two button at the bottom, in a LinearLayout.

我实际上是使用库中的视图来代替外部RelativeLayout:SlidingUpPanelLayout( https://github .com/umano/AndroidSlidingUpPanel ).但是我使用此RelativeLayout测试了行为:同一件事,这意味着库不相关.

In place of the external RelativeLayout I'm really using a view from a library: SlidingUpPanelLayout (https://github.com/umano/AndroidSlidingUpPanel). But I tested the behavior with this RelativeLayout: same thing, meaning the library is not related.

我说这只是为了让您知道我不能只是在其中放置FrameLayout并避免在剪切范围之外绘制.

I say this just to let you know that I can't just place there a FrameLayout and avoid drawing outside the clipping bounds.

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

I suspect this has something to do with the fact that when I touch the button it redraw itself but my other view (which is somewhere else in the hierarchy) doesn't get re-drawed (this is an optimization and I doubt it can be disabled).

每当任何其他近"(或任何)视图无效时,我希望能够使自定义视图无效,因此我需要某种侦听器来使视图无效,但是我不知道有任何监听器.

I'd like to be able to invalidate my custom view whenever any other "near" (or any) view get's invalidated, thus I need some kind of listener on view invalidation but I'm not aware of any.

有人可以帮忙吗?

推荐答案

我自己找到了解决方案,即使这对于性能而言并非最佳.

I found the solution myself, even if this is not optimal for performances.

只需添加:

android:clipChildren="false"

转到RelativeLayout(或您拥有的任何布局).

to the RelativeLayout (or whatever layout you have).

这有2种效果(可能更多,这是我感兴趣的两种): -ViewGroup不会裁剪他的孩子的画(很明显) -在考虑重画哪个子对象时,ViewGroup不会检查与脏区域的交集(无效)

This has 2 effects (may be more, this are the two that interested me): - the ViewGroup doesn't clip the drawing of his children (obvious) - the ViewGroup doesn't check for intersection with dirty regions (invalidated) when considering which children to redraw

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

I digged the View code about invalidating.

这个过程或多或少像这样:

The process goes, more or like, like this:

  1. 视图本身无效,它通常绘制的区域(矩形)成为要重绘的脏区域"
  2. 视图告诉其父级(某种ViewGroup)需要重绘自身
  3. 父母与根的父母一样
  4. 层次结构循环中的每个父级为每个子级,并检查脏区是否与某些子级相交
  5. 如果这样做也会重绘它们

在第4步中涉及裁剪:只有在clipChildren为true时,ViewGroup才检查其子项的视图边界:这意味着,如果将其设置为false,则在其任何子项无效时总是重新绘制其所有子项 >.

In step 4 clipping is involved: the ViewGroup check view bounds of his child only if clipChildren is true: meaning that if you place it to false it always redraw all its children when any of them is invalidated.

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

So, my View hierarchy was like this:

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)

在我的情况下,句柄"已绑定,通常在片段子树的某个元素所绘制的东西上.

In my case the "handle" draw ouf of it's bound, on top of something that is usually drawed by some element of the fragment subtree.

如果片段中的任何视图无效,它将在视图树中向上传递其脏区",每个视图组检查在该区域中是否还有其他子级需要重绘.

When any view inside the fragment is invalidated it pass its "dirty region" up in the view tree and each view group check if there are some other children to be redraw in that region.

如果未在其上设置 clipBounds ="false" ,则ViewGroup B会裁剪我在剪贴画边界之外绘制的内容.

ViewGroup B would clip what I draw outside the clip bounds if I do not set clipBounds="false" on it.

如果片段子树中的任何内容无效,则ViewGroup A将看到ViewGroup B的脏区域没有与片段子树区域相交,并且将跳过对ViewGroup B的重绘.

If anything get's invalidated in the fragment subtree the ViewGroup A will see that ViewGroup B dirty region is not intersecting the fragment subtree region and will skip redrawing of ViewGroup B.

但是,如果我还告诉ViewGroup A不裁剪子级,它将仍然向ViewGroup B提供一个无效命令,这将导致重画我的句柄.

But if I also tell ViewGroup A to not clip children it will still give ViewGroup B an invalidate command which will then cause a redraw of my handle.

所以解决方案是确保设置

So the solution is to make sure to set

android:clipChildren="false"

在视图上方层次结构中任何超出其边界的ViewGroup上,内容可能会落在要绘制的边界范围外.

on any ViewGroup in the hierarchy above the View that draw out of it's bounds on which the content may fall "under" the out-of-bound region you are drawing.

这样做的明显副作用是,每当我使ViewGroup A内的任何视图无效时,无效调用都会连同无效区域一起转发到其中的所有视图.

The obvious side effect of this is that whenever I invalidate any of the view inside ViewGroup A an invalidate call will be forwarded, with the invalid region, to all the view in it.

但是,任何不与具有clipChildren ="true"(默认值)的ViewGroup内部的脏区相交的视图都将被跳过.

However any view that doesn't intersect the dirty region which is inside a ViewGroup with clipChildren="true" (default) will be skipped.

因此,为避免执行时出现性能问题,请确保带有clipChildren ="true"的视图组没有很多标准"直接子级.所谓标准",是指它们不会超出其视野范围.

So to avoid performance issues when doing this make sure your view groups with clipChildren="true" have not many "standard" direct children. And with "standard" I mean that they do not draw outside their view bounds.

因此,例如,如果在我的示例中ViewGroup B包含许多视图,请考虑使用clipChildren ="true"将所有视图包装到ViewGroup中,而只保留该视图组和在其区域之外绘制的一个视图作为ViewGroup B的直接子级. ViewGroup A也是如此.

So for example if in my example ViewGroup B contains many view consider wrapping all those in a ViewGroup with clipChildren="true" and only leave this view group and the one view that draw outside its region as direct children of ViewGroup B. The same goes for ViewGroup A.

这个简单的事实将确保如果其他View不在无效的脏区中,则其他任何View都不会获得重绘.

This simple fact will make sure no other View will get a redraw if they aren't in the invalidated dirty region minimizing the redraws needed.

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

I'm still open to hear any more consideration if someone has one ;)

因此,我将稍等一下,然后将其标记为可接受的答案.

So I'll wait a little bit before marking this as accepted answer.

许多设备在处理clipChildren ="false"方面做不同的事情.我发现我必须在自定义窗口小部件的所有父视图上设置clipChildren ="false",这些视图可能包含其层次结构中的元素,这些元素应绘制在窗口小部件的超出区域"范围内,否则您可能会看到自定义图形在另一个应该覆盖它的视图的顶部显示.例如,在我的布局中,我有一个导航抽屉,该抽屉应该覆盖我的手柄".如果我未在NavigationDrawer布局上设置clipChildren ="false",则有时可能会看到我的句柄在打开的抽屉前弹出.

Many devices do something different in handling clipChildren="false". I discovered that I had to set clipChildren="false" on all the parent views of my custom widget that may contains elements in their hierarchy which should draw over of the "out of bound region" of the widget or you may see your custom drawing showing ON TOP of another view that was supposed to cover it. For example in my layout I had a Navigation Drawer that was supposed to cover my "handle". If I didn't set clipChildren="false" on the NavigationDrawer layout I may sometimes see my handle pop up in front of the opened drawer.

我的自定义窗口小部件的高度为0,并在其自身的顶部绘制.在Nexus设备上运行良好,但其他许多设备都进行了一些优化",从而完全跳过了高度为0或宽度为0的视图的绘制.因此,如果要编写一个从其边界中抽出的组件,请注意这一点:您必须为其分配至少1个像素的高度/宽度.

My custom widget had 0 height and drawed "on top" of itself. Worked fine on Nexus devices but many of the others had some "optimization" in place that completely skip drawing of views that have 0 height or 0 width. So be aware of this if you want to write a component that draw out of it's bound: you have to assign it at least 1 pixel height / width.

这篇关于使用Android在视图剪辑范围之外进行绘制时:如何防止底层视图在自定义视图的顶部绘制?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆