Performance bits and pieces, part II - use fewer Views
One of +Romain Guy
's hobbies is to file bugs on people to use fewer Views. But you didn't hear it from me. Anyhow...
The screenshot below shows a full-width banner row with image that cross-fades into solid-color background, with title and (optional) subtitle to its right. In the previous release of the Play Store app we used six child views. Now we use three. What did we remove?
The first one to go was a View
that spanned the whole width and height of the parent minus the paddings. That view had the main background fill color set on it. Now that background color is set on the parent view, with one minor caveat. If you call View.setBackgroundColor
, it will set the color on the full bounds including the padding. Instead, what you want to do is to use an InsetDrawable
and wrap it around a PaintDrawable
initialized with your color. I mentioned this before, and I'll mention it here. Do not
as a bug on older OS versions will cause it to ignore the insets.
The next one was used to create the cross-fade between the right edge of the image and the solid fill. One option is to tweak the bits of the bitmap itself after you load it from network or local cache. Any kind of device-side image processing (in general so YMMV) is expensive in terms of both memory and CPU, so we opted out for overlaying the right part of the ImageView
with a View
that had a GradientDrawable
set as its background. That drawable was initialized with two end points - full-opacity fill color on the right, and the same fill color with 0x00FFFFFF mask applied to it on the left. You usually don't want a "random" transparent color used on such a gradient, as the intermediate pixels will not look right across a variety of solid colors. The gradient drawable should be created once in the constructor and then have its setBounds
method called from within the onLayout
of your custom view group (so that it is properly positioned on its view).
In the latest release we're achieving the same visual effect using fading edges. Here, you extend the ImageView
class and do the following:
* call setHorizontalFadingEdgeEnabled(true)
* call setFadingEdgeLength
passing the width of the fade area in pixels
* override getLeftFadingEdgeStrength()
to return 0.0f (in our case)
* override getRightFadingEdgeStrength()
to return 1.0f (in our case)
* override getSolidColor()
to return the ARGB value of the matching solid fill to be used in the fading edge part
* override hasOverlappingRendering()
to return true
and onSetAlpha(int alpha)
to return false
The last two are needed to indicate that the fading edge should be respected during image view fade-in sequence.
Finally, the last view to go was a full-span child that provided light blue highlight visuals for pressed/focused state. Instead, we override the draw
method to paint those states explicitly. If isPressed()
, we call setBounds
on the press Drawable
and call its draw(Canvas)
method. Otherwise, if isFocused()
, we do the same for the focus Drawable
Note that none of this results in improving the overdraw. We're touching all the pixels the same number of times we used to. However, we're spending slightly less time inflating the view itself, as well as on measuring and laying out the child views.