android, attributes, compound, compound drawables, custom attributes, custom menu, custom state, custom-ui, drawable state, duplicateParentState, menu, mozilla, optimization, optimize, performance, state, textview
“So! Many! Views!” — that’s exactly what I felt when we started the process of optimizing Firefox for Android. Compound Drawables was pure magic. It reduced a lot of views in the home page. But we wanted to see where all it could be used. The custom menu was a good choice. To stand out from other menus, but still feel like Android menu, we added the images to the menu. This required re-writing the entire menu management ourselves. Also, some menu items are checkable and some others can have a sub menu. We added an indicator on the right for the same. The final layout had no less than 8 views to manage the image, text and the indicator along with the variable padding in between them.
How do we reduce the views in this menu? This menu is going to exist for the entire life time of the app (at this point of writing, even though they use a list view, the individual rows aren’t cleared every time the menu is closed). To start optimizing, let’s look at how the original menu layout was.
<LinearLayout> <!-- left-most padding --> <View android:layout_width="10dp"/> <!-- icon --> <ImageView /> <!-- menu item name --> <TextView /> <!-- right Indicator --> <FrameLayout> <!-- tick --> <CheckBox /> <!-- more indicator --> <ImageView /> </FrameLayout> <!-- right-most padding --> <View android:layout_width="10dp"/> </LinearLayout>
The low hanging fruit here is to combine the
ImageView for the icon with the
TextView. That reduces one of the two views. The left and right most padding are interesting cases. The
TextView by itself cannot have a standard margin. This would push the text more inside, if the icon or the right indicator is present, but will look right without them. But, that’s not the intended design. The parent
LinearLayout can have a padding. However, this layout file actually had a
<merge/> tag as the parent, as the layout could be inflated into any
ViewGroup on demand. Now, since the left image is now made a compound drawable, the padding can be merged with the TextView. We are left with the right indicator (3 views) and an empty view for padding.
The interesting thing with compound drawables is that they support states. The state of the
TextView is duplicated to the compound drawables too. If there is a way to combine the
CheckBox and the
ImageView as a single drawable, the entire right indicator could be made a compound drawable. How do we do that? The easiest way is to have custom states. A typical state list for the right indicator would look like:
<selector> <!-- more indicator --> <item app:state_more="true" android:drawable="@drawable/more_indicator"/> <!-- checked state --> <item app:state_more="false" android:state_checkable="true" android:state_checked="true" android:drawable="@drawable/check_mark"/> <!-- unchecked state --> <item app:state_more="false" android:state_checkable="true" android:state_checked="false" android:drawable="@drawable/check_box"/> <!-- default state --> <item android:drawable="@android:color/transparent"/> </selector>
The right side of the
TextView is shrunk to a single image with multiple states. This can be the
android:drawableRight of the text. So, how many views do we have for the menu item?
<TextView android:drawableLeft="@drawable/_assigned_by_code_" android:drawableRight="@drawable/_right_indicator_selector_" android:paddingLeft="10dp" android:paddingRight="10dp"/>
And that’s how we optimize Firefox on Android! One view at a time! 😉
Nice trick with the custom state, thanks for sharing!
Alex Lockwood said:
Thanks for the article! A Space could be used instead of an empty View to optimize this case further (probably not significantly or anything, but still): https://developer.android.com/reference/android/support/v7/widget/Space.html