In Part II, the PreferenceFragment and the PreferenceScreen were discussed along with the fact that there is a limited number of built-in widgets available for configuring the settings. The one missing that I thought would be most useful for multi-language support was a SpinnerPreference. Language support in an app can vary over time and the spinner lends itself well to a list that can change without requiring any modifications to the user interface.
A goal in creating the SpinnerPreference was to make it’s interface as similar as possible to the built-in widgets, such as the CheckBoxPreference. Because of this all the behind the scenes layout in the SpinnerPreference is done in java and not xml. In addition, the xml preference settings needed by the CheckBoxPreference will also be used:
<com.cdevtech.preference.SpinnerPreference
android:key="pref_language"
android:title="@string/pref_change_language_title"
android:summary="@string/pref_change_language_summary"
android:entries="@array/language_entries"
android:entryValues="@array/language_values"
android:defaultValue="en"/>
<string-array name="language_entries">
<item>English</item>
<item>Spanish</item>
</string-array>
<string-array name="language_values">
<item>en</item>
<item>es</item>
</string-array>
SpinnerPreference extends DialogPreference which has a number of methods that need to be overridden in the inherited class. In the constructor, the text for the positive and negative dialog buttons is set along with the retrieval of some of the parameters set in the preferences.xml file.
public SpinnerPreference(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
setPositiveButtonText(context.getString(
android.R.string.ok));
setNegativeButtonText(context.getString(
android.R.string.cancel));
int entryValuesId = 0;
for (int i = 0; i < attrs.getAttributeCount(); i++) {
if (attrs.getAttributeName(i).
equalsIgnoreCase("entries")) {
entriesId = Integer.parseInt(
attrs.getAttributeValue(i).substring(1));
}
if (attrs.getAttributeName(i).
equalsIgnoreCase("entryValues")) {
entryValuesId = Integer.parseInt(
attrs.getAttributeValue(i).substring(1));
}
}
if (entryValuesId != 0) {
String[] entryValues = context.getResources().
getStringArray(entryValuesId);
// Convert String Array To ArrayList for indexOf capability
entryValuesList = Arrays.asList(entryValues);
}
}
As mentioned, instead of using setDialogLayoutResource in the constructor to specify the layout, onCreateDialogView is overrriden and the View to display in the dialog is generated there using the values context, entriesId which were set in the constructor.
@override
protected View onCreateDialogView() {
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));
layout.setPadding(10, 10, 10, 10);
spinner = new AppCompatSpinner(context);
spinner.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));
spinner.setPadding(10, 10, 10, 10);
if (entriesId != 0) {
ArrayAdapter<String> spinnerArrayAdapter =
new ArrayAdapter<String>(
context,
android.R.layout.simple_spinner_dropdown_item,
context.getResources().getStringArray(entriesId));
spinner.setAdapter(spinnerArrayAdapter);
}
layout.addView(spinner);
return layout;
}
Override onBindDialogView to bind data to the content views:
@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
// Set default/current/selected value if set
if (value != null) {
spinner.setSelection(value);
}
}
Override onDialogClosed which is called when the dialog is closed. If the positive button was clicked then persist the data (save in SharedPreferences by calling persistString:
@Override
protected void onDialogClosed(boolean positiveResult) {
try {
if (positiveResult) {
value = spinner.getSelectedItemPosition();
String entryValue = entryValuesList.get(value);
if (callChangeListener(entryValue)) {
persistString(entryValue);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Override onSetInitialValue to set the initial value of the preference. It is called when the preference object is added to the screen. If restorePersistedValue is true, the preference value should be restored from the SharedPreferences else the preference value should be set to the defaultValue passed and it should also be persisted (saved).
restorePersistedValue will generally be false when you’ve specified android:defaultValue that calls onGetDefaultValue (check below) and that in turn returns a value which is passed as the defaultValue to onSetInitialValue.
@Override
protected void onSetInitialValue(boolean restorePersistedValue,
Object defaultValue) {
try {
if (restorePersistedValue) {
if (defaultValue == null) {
value = entryValuesList.indexOf(
getPersistedString(entryValuesList.get(0)));
} else {
value = entryValuesList.indexOf(
getPersistedString((String) defaultValue));
}
} else {
value = entryValuesList.indexOf((String) defaultValue);
persistString((String) defaultValue);
}
} catch (Exception e) {
e.printStackTrace();
}
}
Override onGetDefaultValue which is called when you set android:defaultValue . Just in case the value is undefined, you can return DEFAULT_VALUE so that it gets passed to onSetInitialValue that gets saved in SharedPreferences.
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getString(index);
}