In the Android preferences you can put different types of controls: check box, edit box, list,…, but none of these displays an icon (see Settings).
Usually a preference consists of two lines, the title and the summary, and after you have clicked you get a dialog box where you can select the chosen item, as you can see in the two pictures below.
Note that there are no icons.
In this post I write an example to create a preference where a icon is shown and chosen from the user, you can download the whole project here.
How it works
- the layouts: there are four layouts
- activity_main.xml the layout for the Activity containing the TextView “Hello world!” only
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> </RelativeLayout>
- icon_item_preference.xml the layout for the preferences, in this example it consists of only one item containing a title, a summary and an icon
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="6dp" android:paddingLeft="6dp" android:paddingRight="6dp" android:paddingTop="6dp" > <!-- android:paddingLeft="72dp" --> <TextView android:id="@+android:id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+android:id/summary" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@android:id/title" android:layout_below="@android:id/title" android:textAppearance="?android:attr/textAppearanceSmall" android:text="@string/default_summary" /> <ImageView android:id="@+id/iconSelected" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" /> </RelativeLayout>
the padding values of the RelativeLayout are for the version 4.x of Android, for earlier versions the paddingLeft should be set to 72dp
- item_picker.xml the layout that handles the selection of the icon from the user, it contains the summary (the name of the icon), the icon and a radio button
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10dp" > <TextView android:id="@+id/iconName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" /> <RadioButton android:id="@+id/iconRadio" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_below="@+id/iconName" android:focusable="false" android:clickable="false" /> <ImageView android:id="@+id/iconImage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/iconName" android:layout_below="@+id/iconName" /> </RelativeLayout>
- preferences.xml this layout is not in the directory “res/layout” unlike previous ones but in the directory “res/xml”, the class IconPickerPreference is bound to this file which has the file icon_item_preference.xml (line 10) as layout
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom="http://schemas.android.com/apk/res/eu.lucazanini.custompreference" > <PreferenceCategory android:title="Icon Picker" > <eu.lucazanini.custompreference.IconPickerPreference android:entries="@array/iconName" android:entryValues="@array/iconFile" android:key="@string/custom_icon_key" android:layout="@layout/icon_item_preference" android:title="@string/icon_label" android:summary="Select an icon" custom:iconFile="@string/icon_default" /> </PreferenceCategory> </PreferenceScreen>
- activity_main.xml the layout for the Activity containing the TextView “Hello world!” only
- the files in the directory “values”:
- strings.xml contains the strings used by the app and among these:
- icon_default the file without extension of the default icon
- custom_icon_key the key of the selected icon
- icon_label the title
- default_summary the initial value of the summary
- iconName strings array with the names of the icons
- iconFile strings array with the names of the files without extension of the icons, one of these is equal to custom_icon_key
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Custom Preference</string> <string name="action_settings">Settings</string> <string name="hello_world">Hello world!</string> <string name="icon_default">ic_green</string> <string name="custom_icon_key">customIconKey</string> <string name="icon_label">Icon</string> <string name="default_summary">Select an icon</string> <string-array name="iconName"> <item>Green</item> <item>Yellow</item> <item>Red</item> </string-array> <string-array name="iconFile"> <item>ic_green</item> <item>ic_yellow</item> <item>ic_red</item> </string-array> </resources>
- attrs_icon.xml defines the custom attribute iconFile used in preferences.xml at the line 13
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="attrs_icon"> <attr name="iconFile" format="string" /> </declare-styleable> </resources>
- strings.xml contains the strings used by the app and among these:
- the java classes:
- MainActivity.java the starting activity, it is only to launch the preferences, lines 27-30
package eu.lucazanini.custompreference; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_settings: Intent intent = new Intent(this, PreferencesActivity.class); startActivity(intent); return true; default: return super.onOptionsItemSelected(item); } } }
- PreferencesActivity.java the activity of the preferences, it contains an instance of IconPickerFragment
package eu.lucazanini.custompreference; import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.os.Bundle; public class PreferencesActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Fragment iconPickerFragment = new IconPickerFragment(); FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); ft.replace(android.R.id.content, iconPickerFragment); ft.commit(); } }
- IconPickerFragment.java, it has the file preferences.xml as layout
package eu.lucazanini.custompreference; import android.os.Bundle; import android.preference.PreferenceFragment; public class IconPickerFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); } }
- IconPickerPreference.java, the key class of the app, it handles the preference set in preferences.xml and the dialog box where the user chooses the icon
- line 130: the constructor IconPickerPreference initializes context, resources, preferences and defaultIconFile defined in preferences.xml
- line 155: the method onBindView displays the icon in the preferences
- line 197: the method onPrepareDialogBuilder is called before the opening of the dialog box, it defines the adapter for the bulder (dialog box)
- line 170: the method onDialogClosed is called after the dialog box is closed and it saves the choice of the user in the preferences
- line 27: the class CustomListPreferenceAdapter handles the dialog box
package eu.lucazanini.custompreference; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import android.app.AlertDialog.Builder; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.res.Resources; import android.content.res.TypedArray; import android.preference.ListPreference; import android.preference.PreferenceManager; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.RadioButton; import android.widget.TextView; public class IconPickerPreference extends ListPreference { private class CustomListPreferenceAdapter extends ArrayAdapter<IconItem> { private Context context; private List<IconItem> icons; private int resource; public CustomListPreferenceAdapter(Context context, int resource, List<IconItem> objects) { super(context, resource, objects); this.context = context; this.resource = resource; this.icons = objects; } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(resource, parent, false); holder = new ViewHolder(); holder.iconName = (TextView) convertView .findViewById(R.id.iconName); holder.iconImage = (ImageView) convertView .findViewById(R.id.iconImage); holder.radioButton = (RadioButton) convertView .findViewById(R.id.iconRadio); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.iconName.setText(icons.get(position).name); int identifier = context.getResources().getIdentifier( icons.get(position).file, "drawable", context.getPackageName()); holder.iconImage.setImageResource(identifier); holder.radioButton.setChecked(icons.get(position).isChecked); convertView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ViewHolder holder = (ViewHolder) v.getTag(); for (int i = 0; i < icons.size(); i++) { if (i == position) icons.get(i).isChecked = true; else icons.get(i).isChecked = false; } getDialog().dismiss(); } }); return convertView; } } private static class IconItem { private String file; private boolean isChecked; private String name; public IconItem(CharSequence name, CharSequence file, boolean isChecked) { this(name.toString(), file.toString(), isChecked); } public IconItem(String name, String file, boolean isChecked) { this.name = name; this.file = file; this.isChecked = isChecked; } } private static class ViewHolder { protected ImageView iconImage; protected TextView iconName; protected RadioButton radioButton; } private Context context; private ImageView icon; private CharSequence[] iconFile; private CharSequence[] iconName; private List<IconItem> icons; private SharedPreferences preferences; private Resources resources; private String selectedIconFile, defaultIconFile; private TextView summary; public IconPickerPreference(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; resources = context.getResources(); preferences = PreferenceManager.getDefaultSharedPreferences(context); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.attrs_icon, 0, 0); try { defaultIconFile = a.getString(R.styleable.attrs_icon_iconFile); } finally { a.recycle(); } } private String getEntry(String value) { String[] entries = resources.getStringArray(R.array.iconName); String[] values = resources.getStringArray(R.array.iconFile); int index = Arrays.asList(values).indexOf(value); return entries[index]; } @Override protected void onBindView(View view) { super.onBindView(view); selectedIconFile = preferences.getString( resources.getString(R.string.custom_icon_key), defaultIconFile); icon = (ImageView) view.findViewById(R.id.iconSelected); updateIcon(); summary = (TextView) view.findViewById(android.R.id.summary); summary.setText(getEntry(selectedIconFile)); } @Override protected void onDialogClosed(boolean positiveResult) { super.onDialogClosed(positiveResult); if (icons != null) { for (int i = 0; i < iconName.length; i++) { IconItem item = icons.get(i); if (item.isChecked) { Editor editor = preferences.edit(); editor.putString( resources.getString(R.string.custom_icon_key), item.file); editor.commit(); selectedIconFile = item.file; updateIcon(); summary.setText(item.name); break; } } } } @Override protected void onPrepareDialogBuilder(Builder builder) { builder.setNegativeButton("Cancel", null); builder.setPositiveButton(null, null); iconName = getEntries(); iconFile = getEntryValues(); if (iconName == null || iconFile == null || iconName.length != iconFile.length) { throw new IllegalStateException( "ListPreference requires an entries array " + "and an entryValues array which are both the same length"); } String selectedIcon = preferences.getString( resources.getString(R.string.custom_icon_key), resources.getString(R.string.icon_default)); icons = new ArrayList<IconItem>(); for (int i = 0; i < iconName.length; i++) { boolean isSelected = selectedIcon.equals(iconFile[i]) ? true : false; IconItem item = new IconItem(iconName[i], iconFile[i], isSelected); icons.add(item); } CustomListPreferenceAdapter customListPreferenceAdapter = new CustomListPreferenceAdapter( context, R.layout.item_picker, icons); builder.setAdapter(customListPreferenceAdapter, null); } private void updateIcon() { int identifier = resources.getIdentifier(selectedIconFile, "drawable", context.getPackageName()); icon.setImageResource(identifier); icon.setTag(selectedIconFile); } }
- MainActivity.java the starting activity, it is only to launch the preferences, lines 27-30
The following pictures show the app:
Leave a Reply