* This article is meant for advanced UI customization of preferences. For basics, check out Android API guide1.
Settings2 or preferences are one of those semi-essential components that make our app feel more personal to users, by giving them choices to tailor their own experience. Preferences are especially popular in apps for ‘power’ users, where they are presented with a bloat of settings. They are also important in apps where users are opinionated in terms of what makes great experience, e.g. reading apps. Yet building a great settings section in Android has always been a source of pain, at least until recently.
Android SDK comes with 2 choices for developers who want to implement a settings screen, each has its own shortfalls:
PreferenceActivity
may break our inheritance chain, and sorry but no toolbar3PreferenceFragment
is only available from API 11
Many just give up on this and either go for a bare-bone settings screen with horrible experience, or go the long way of having their own implementation. Here comes preference-v7 to the rescue!
preference-v7
With the release of preference-v7, these have been adressed and there should be no excuses now for not implementing a good settings screen. As with other components of support library, preference-v7 provides the same set of implementation as Android SDK, with backward compatibility all the way back to API 7! This means that we get these components for free out of the box:
These components alone should be more than enough to create a decent settings experience. We can basically go with ListPreference
for anything with a list of choices, or the beautiful SwitchPreferenceCompat
for anything toggle.
The above screenshot shows an earlier version of settings in Materialistic. Check out the implementation here and here. All great, everything looks neat and material design! But as we add more preferences, each becomes harder to recognize in a long list of preferences. They all follow the same monotonous pattern. The default item layout is plain, and users are forced to go through a try-and-see cycle to get a taste of the change, which they will likely forget the next time.
This calls for a more visual, instant preview of preferences. For example, a theme preference should reflect what each theme looks like (background & text color). A font preference should list each font in its very own typography. Or a list of text sizes should show how big each of them is.
Like this:
Looking good? Making you feel excited just to look at each option now? If your answer is yes then read on!
TL;DR
What we need to do:
- add preference-v7 to
build.gradle
as project dependency (of course!) - set
preferenceTheme
in our theme invalues/styles.xml
, this is required. We can use the default@style/PreferenceThemeOverlay
as value for a start - extend
android.support.v7.preference.Preference
. This is the base class for all preference widgets - inflate custom layout using
setLayoutResource(int)
orsetWidgetLayoutResource(int)
via constructor - override
onBindViewHolder(PreferenceViewHolder)
with our view binding and click listener logic. We may need to disable the default click behavior if we want to click child view
A custom SpinnerPreference
Using uiautomatorviewer to have a quick peek into how preference-v7 layouts setttings screen, we can see that internally it inflates a RecyclerView
, where each preference is an item. And as with normal RecyclerView
implementation, we are to override some sort of ViewHolder
create and bind logic. In this case, it’s an instance of PreferenceViewHolder
.
So here goes! Let’s call our custom preference SpinnerPreference
, since a Spinner4 control allows us to display a list of choices, as well as selected value.
Our custom widget layout can be as simple as a single AppCompatSpinner
. We set this layout as our preference’s widget layout, which leaves the default title and summary for base class implementation.
The default implementation should take care of inflating our custom widget layout, creating a PreferenceViewHolder
, leaving us the task of binding it. Here we wire up the preference click logic to open Spinner
’s dropdown, and give it a set of items, which can be passed through custom attributes5 app:entries
and app:entryValues
, similar to android:entries
and android:entryValues
of ListPreference
. Clicking a spinner dropdown item will persist its corresponding value as string here, but it can be any of the supported types.
Subclasses to this abstract SpinnerPreference
should provide implementation to create and bind each dropdown item, which is where we do our magic to spice up the instant preview. Below is an example where each dropdown item has its own typeface, retrieved via a FontCache
, which is a map of name and typeface.
Of course don’t forget to set the persisted preference value to our Spinner
the next time users visit settings:
Now add this custom preference to our preferences config and we’re good to go!
Head over to Materialistic’s Github repo for complete implementation of this and other custom preferences:
- abstract
SpinnerPreference
FontPreference
FontSizePreference
ThemePreference
SettingsFragment
xml/preferences.xml
values/styles.xml