使用 Android 在视图剪辑边界之外绘制时:如何防止下层视图绘制在自定义视图之上?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25044085/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
When drawing outside the view clip bounds with Android: how do I prevent underling views from drawing on top of my custom view?
提问by Daniele Segato
I'm wrote a custom Android View that need to draw outside its clipping Bounds.
我写了一个自定义的 Android 视图,需要在其裁剪边界之外进行绘制。
This is what I have:
这就是我所拥有的:
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.
我的项目中的一些相关伪代码如下。
My custom view MyHandleViewdraw like this:
我的自定义视图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();
}
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>
You can think of the fragment as a view containing two button at the bottom, in a LinearLayout.
您可以将片段视为一个在 LinearLayout 底部包含两个按钮的视图。
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.
代替外部 RelativeLayout 我真的使用库中的视图:SlidingUpPanelLayout ( https://github.com/umano/AndroidSlidingUpPanel)。但是我用这个 RelativeLayout 测试了行为:同样的事情,这意味着库不相关。
I say this just to let you know that I can't just place there a FrameLayout and avoid drawing outside the clipping bounds.
我这么说只是为了让你知道我不能只是在那里放置一个 FrameLayout 并避免在裁剪边界之外绘制。
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.
我希望能够在任何其他“接近”(或任何)视图失效时使我的自定义视图失效,因此我需要某种关于视图失效的侦听器,但我不知道任何。
Can someone help?
有人可以帮忙吗?
回答by Daniele Segato
I found the solution myself, even if this is not optimal for performances.
我自己找到了解决方案,即使这对表演来说不是最佳选择。
Just add:
只需添加:
android:clipChildren="false"
to the RelativeLayout (or whatever layout you have).
到RelativeLayout(或您拥有的任何布局)。
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
这有 2 个效果(可能更多,这是我感兴趣的两个): - ViewGroup 不剪裁他孩子的绘图(很明显) - ViewGroup 在考虑时不检查与脏区域(无效)的交集哪些孩子要重画
I digged the View code about invalidating.
我挖掘了关于无效的查看代码。
The process goes, more or like, like this:
这个过程大概是这样的:
- a View invalidate itself, the region it usually draw (a rectangular) become a "dirty region" to be redrawed
- the View tell its parent (a ViewGroup of some kind) it need to redraw itself
- the parents do the same with it's parent to the root
- each parent in the hierarchy loop for every children and check if the dirty region intersect some of them
- if it does it also redraw them
- 视图使自身失效,它通常绘制的区域(矩形)成为要重绘的“脏区域”
- View 告诉它的父级(某种 ViewGroup)它需要重绘自己
- 父母对根的父母做同样的事情
- 每个孩子的层次结构循环中的每个父母并检查脏区域是否与其中一些相交
- 如果这样做,它也会重绘它们
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.
在第 4 步中涉及裁剪:只有当 clipChildren 为 true 时,ViewGroup 才会检查他的孩子的视图边界:这意味着如果您将其设置为 false,它总是在其中任何一个无效时重绘其所有孩子。
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.
当片段内的任何视图无效时,它会在视图树中向上传递其“脏区域”,每个视图组检查该区域中是否还有其他一些子项要重绘。
ViewGroup B would clip what I draw outside the clip bounds if I do not set clipBounds="false"on it.
如果我没有在它上面设置clipBounds="false",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 脏区域未与片段子树区域相交,并将跳过 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.
但是,如果我还告诉 ViewGroup A 不要剪裁子项,它仍然会给 ViewGroup B 一个无效命令,这将导致我的句柄重绘。
So the solution is to make sure to set
所以解决方案是确保设置
android:clipChildren="false"
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.
在 View 之上的层次结构中的任何 ViewGroup 上绘制超出其边界的内容可能会落在您正在绘制的越界区域“下方”。
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.
这样做的明显副作用是,每当我使 ViewGroup A 中的任何视图无效时,都会将无效调用与无效区域一起转发到其中的所有视图。
However any view that doesn't intersect the dirty region which is inside a ViewGroup with clipChildren="true" (default) will be skipped.
但是,任何不与带有 clipChildren="true"(默认)的 ViewGroup 内的脏区域相交的视图都将被跳过。
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.
因此,为避免执行此操作时出现性能问题,请确保带有 clipChildren="true" 的视图组没有很多“标准”直接子级。对于“标准”,我的意思是他们不会在视图范围之外进行绘制。
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.
因此,例如,如果在我的示例中 ViewGroup B 包含许多视图,请考虑将所有视图包装在一个带有 clipChildren="true" 的 ViewGroup 中,并且只保留此视图组和在其区域外绘制的一个视图作为 ViewGroup B 的直接子项。同样的对于视图组 A。
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.
因此,我会稍等片刻,然后将其标记为已接受的答案。
EDIT: 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.
EDIT2: 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.
编辑:许多设备在处理 clipChildren="false" 时会做一些不同的事情。我发现我必须在我的自定义小部件的所有父视图上设置 clipChildren="false" ,这些父视图可能包含其层次结构中的元素,这些元素应该绘制小部件的“越界区域”,否则您可能会看到您的自定义绘图显示应该覆盖它的另一个视图的顶部。例如,在我的布局中,我有一个导航抽屉,它应该覆盖我的“手柄”。如果我没有在 NavigationDrawer 布局上设置 clipChildren="false",我有时可能会看到我的手柄在打开的抽屉前面弹出。
EDIT2:我的自定义小部件的高度为 0,并在其自身“顶部”绘制。在 Nexus 设备上运行良好,但其他许多设备都进行了一些“优化”,完全跳过了高度为 0 或宽度为 0 的视图的绘制。因此,如果您想编写一个超出其边界的组件,请注意这一点:您必须为其分配至少 1 个像素的高度/宽度。