TextView:onDraw仅被调用一次

问题描述:

我遇到一种情况,我想调试对TextView.onDraw()的调用,所以我将其子类化为这样:

I have a situation where I want to debug calls to TextView.onDraw() so I subclassed it like this:

public class MyTextView extends AppCompatTextView {

    View parent;

    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void storeParent(View parent) {
        this.parent = parent;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }
}

并将其放在这样的层次结构中:(请参阅MyTextView)

and put it up inside a hierarchy like this:(see MyTextView)

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.scrollviewtest1.MainActivity">

    <LinearLayout
        android:id="@+id/scroll_container"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.example.scrollviewtest1.MyTextView
            android:id="@+id/text2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/large_text" />

        <TextView
            android:id="@+id/text1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!" />

    </LinearLayout>


</ScrollView>

最后,我为此TextView放置了一个很大的文本,以便使其可滚动.

Lastly, I put a very large piece of text for this TextView so that it becomes scrollable.

现在,如果我在onDraw()方法中放置一个断点,则只会得到一个调用.根据我的理解,当我上下滚动时,应该会调用它.为什么会这样?

Now, if I put a breakpoint inside the onDraw() method, I only get one call to it. As per my understanding, I should be getting call to it as I scroll up and down. Why is this happening?

旁注::我已经尝试设置setWillNotDraw(false),但结果没有变化.

Side note: I have already tried setting setWillNotDraw(false) with no change in the outcome.

Credit转到@pskink,建议为此关闭硬件加速.有效.这是文档对此的看法:>

Credit goes to @pskink for suggesting turning off H/W acceleration for this; it worked. Here is what docs say about this:

硬件加速绘图模型

Android系统仍然使用invalidate()和draw()来请求 屏幕更新并渲染视图,但可以处理实际图形 不一样.而不是立即执行绘图命令, Android系统将它们记录在显示列表中,其中包含 视图层次结构的绘图代码的输出.另一个优化 是Android系统只需要记录和更新显示 由invalidate()调用标记为脏的视图的列表.具有的视图 未失效的可以通过重新签发 先前记录的显示列表.新的绘图模型包含三个 阶段:

Hardware accelerated drawing model

The Android system still uses invalidate() and draw() to request screen updates and to render views, but handles the actual drawing differently. Instead of executing the drawing commands immediately, the Android system records them inside display lists, which contain the output of the view hierarchy’s drawing code. Another optimization is that the Android system only needs to record and update display lists for views marked dirty by an invalidate() call. Views that have not been invalidated can be redrawn simply by re-issuing the previously recorded display list. The new drawing model contains three stages:

Invalidate the hierarchy
Record and update display lists
Draw the display lists

使用此模型,您不能依靠与脏点相交的视图 区域执行其draw()方法.确保Android 系统记录视图的显示列表,您必须调用invalidate(). 忘记这样做,即使视图已经 已更改.

With this model, you cannot rely on a view intersecting the dirty region to have its draw() method executed. To ensure that the Android system records a view’s display list, you must call invalidate(). Forgetting to do so causes a view to look the same even after it has been changed.

使用显示列表还可以提高动画效果,因为 设置特定的属性(例如alpha或旋转)不会 需要使目标视图无效(它是自动完成的). 此优化也适用于具有显示列表的视图(任何视图 当您的应用程序通过硬件加速时.)例如,假设 有一个LinearLayout,它在Button上方包含一个ListView.这 LinearLayout的显示列表如下:

Using display lists also benefits animation performance because setting specific properties, such as alpha or rotation, does not require invalidating the targeted view (it is done automatically). This optimization also applies to views with display lists (any view when your application is hardware accelerated.) For example, assume there is a LinearLayout that contains a ListView above a Button. The display list for the LinearLayout looks like this:

DrawDisplayList(ListView)
DrawDisplayList(Button)

现在假设您要更改ListView的不透明度.后 在ListView上调用setAlpha(0.5f),显示列表现在包含 这个:

Assume now that you want to change the ListView's opacity. After invoking setAlpha(0.5f) on the ListView, the display list now contains this:

SaveLayerAlpha(0.5)
DrawDisplayList(ListView)
Restore
DrawDisplayList(Button)

未执行ListView的复杂绘图代码.相反, 系统仅更新了更为简单的LinearLayout的显示列表. 在未启用硬件加速的应用程序中,工程图 列表及其父代的代码都将再次执行.

The complex drawing code of ListView was not executed. Instead, the system only updated the display list of the much simpler LinearLayout. In an application without hardware acceleration enabled, the drawing code of both the list and its parent are executed again.

VS

基于软件的绘图模型

在软件绘图模型中,使用以下两个视图绘制视图 步骤:

Software-based drawing model

In the software drawing model, views are drawn with the following two steps:

Invalidate the hierarchy
Draw the hierarchy

每当应用程序需要更新其UI的一部分时,它都会调用 在任何已更改的视图上的invalidate()(或其变体之一) 内容.无效消息将一直传播到 视图层次结构以计算需要在屏幕上显示的区域 重绘(脏区).然后,Android系统会在 与脏区相交的层次结构.很遗憾, 此绘图模型有两个缺点:

Whenever an application needs to update a part of its UI, it invokes invalidate() (or one of its variants) on any view that has changed content. The invalidation messages are propagated all the way up the view hierarchy to compute the regions of the screen that need to be redrawn (the dirty region). The Android system then draws any view in the hierarchy that intersects with the dirty region. Unfortunately, there are two drawbacks to this drawing model:

First, this model requires execution of a lot of code on every draw pass. For example, if your application calls invalidate() on a

按钮,该按钮位于另一个视图(Android系统)的顶部 重绘视图,即使它没有更改. 第二个问题是,绘图模型可以隐藏应用程序中的错误.由于Android系统会在视图重新绘制视图时 与脏区相交,您更改了内容的视图可能是 重绘,即使未调用invalidate()也是如此.当这个 发生,您是依靠另一个视图无效来获取 正确的行为.每次您修改时,此行为都会更改 你的申请.因此,您应该始终调用invalidate() 在您的自定义视图上,只要您修改会影响数据或状态的数据 视图的绘图代码.

button and that button sits on top of another view, the Android system redraws the view even though it hasn't changed. The second issue is that the drawing model can hide bugs in your application. Since the Android system redraws views when they intersect the dirty region, a view whose content you changed might be redrawn even though invalidate() was not called on it. When this happens, you are relying on another view being invalidated to obtain the proper behavior. This behavior can change every time you modify your application. Because of this, you should always call invalidate() on your custom views whenever you modify data or state that affects the view’s drawing code.

注意:当Android视图的属性更改时,例如背景颜色或文本中的文本,它们会自动调用invalidate() TextView.

Note: Android views automatically call invalidate() when their properties change, such as the background color or the text in a TextView.