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);
}
}
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>
Lastly, I put a very large piece of text for this TextView
so that it becomes scrollable.
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?
Side note: I have already tried setting setWillNotDraw(false)
with no change in the outcome.
Credit goes to @pskink for suggesting turning off H/W acceleration for this; it worked. Here is what docs say about this:
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
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.
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)
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)
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
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
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.
Note: Android views automatically call invalidate() when their properties change, such as the background color or the text in a
TextView.