For posterity here is the java file that does what I want it to do. It references the pre-androidx code but works for androidx behavior. It also supports automatically setting the icon for the preference to the selected icon and updates the summary.
import android.app.AlertDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.RadioButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@SuppressWarnings("unused")
public class IconListPreference extends ListPreference {
private static final int DEFAULT_TINT = 0xFF;
private static final int DEFAULT_BACKGROUND_TINT = 0xFF;
private Context mContext;
private int mErrorResource;
private final List<Integer> mImages = new ArrayList<>();
private int mTintColor;
private int mBackgroundColor;
private boolean mUseCard;
private int mCustomItemLayout;
private String mInitialSummary;
private class iconListPreferenceAdapter extends ArrayAdapter<ImageListItem> {
private final List<ImageListItem> mItems;
private final int mLayoutResource;
iconListPreferenceAdapter(Context context, int layoutResource, List<ImageListItem> items) {
super(context, layoutResource, items);
mLayoutResource = layoutResource;
mItems = items;
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
try {
assert inflater != null;
convertView = inflater.inflate(mLayoutResource, parent, false);
holder = new ViewHolder();
holder.iconName = convertView.findViewById(R.id.iconlistpreference_text);
holder.iconImage = convertView.findViewById(R.id.iconlistpreference_image);
holder.radioButton = convertView.findViewById(R.id.iconlistpreference_radio);
convertView.setTag(holder);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
holder = (ViewHolder)convertView.getTag();
}
if (holder == null) {
return super.getView(position, convertView, parent);
}
ImageListItem item = mItems.get(position);
holder.iconName.setText(item.name);
if (item.resource != 0) {
holder.iconImage.setImageResource(item.resource);
} else {
holder.iconImage.setImageResource(mErrorResource);
}
if (mTintColor != 0) {
holder.iconImage.setColorFilter(mTintColor);
}
if (mBackgroundColor != 0) {
holder.iconImage.setBackgroundColor(mBackgroundColor);
}
holder.radioButton.setChecked(item.isChecked);
return convertView;
}
}
private static class ImageListItem {
private final int resource;
private final boolean isChecked;
private final String name;
ImageListItem(CharSequence name, int resource, boolean isChecked) {
this(name.toString(), resource, isChecked);
}
ImageListItem(String name, int resource, boolean isChecked) {
this.name = name;
this.resource = resource;
this.isChecked = isChecked;
}
}
private static class ViewHolder {
ImageView iconImage;
TextView iconName;
RadioButton radioButton;
}
public IconListPreference(Context context) {
super(context);
}
public IconListPreference(Context context, AttributeSet attrs) {
this(context, attrs, 0, 0);
}
public IconListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public IconListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs);
mContext = context;
mInitialSummary = Objects.requireNonNull(getSummary()).toString();
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.iconlistpreference, defStyleAttr, defStyleRes);
try {
int entryImagesArrayResource = array.getResourceId(R.styleable.iconlistpreference_ilp_entryImages, 0);
String tintKey = array.getNonResourceString(R.styleable.iconlistpreference_ilp_tintKey);
String backgroundKey = array.getNonResourceString(R.styleable.iconlistpreference_ilp_backgroundTint);
mTintColor = array.getColor(R.styleable.iconlistpreference_ilp_tint, DEFAULT_TINT);
mBackgroundColor = array.getColor(R.styleable.iconlistpreference_ilp_backgroundTint, DEFAULT_BACKGROUND_TINT);
mErrorResource = array.getResourceId(R.styleable.iconlistpreference_ilp_errorImage, 0);
mUseCard = array.getBoolean(R.styleable.iconlistpreference_ilp_useCard, false);
mCustomItemLayout = array.getResourceId(R.styleable.iconlistpreference_ilp_itemLayout, 0);
if (tintKey != null) {
mTintColor = sharedPreferences.getInt(tintKey, mTintColor);
}
if (backgroundKey != null) {
mBackgroundColor = sharedPreferences.getInt(backgroundKey, mBackgroundColor);
}
if (entryImagesArrayResource != 0) {
TypedArray images = context.getResources().obtainTypedArray(entryImagesArrayResource);
for (int i = 0; i < images.length(); i++) {
mImages.add(images.getResourceId(i, 0));
}
images.recycle();
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
array.recycle();
}
}
@Override
protected void onClick() {
//
// If an images array has been specified display the custom dialog otherwise
// display the default.
//
if (!mImages.isEmpty()) {
AlertDialog dialog = onPrepareDialog();
dialog.show();
} else {
super.onClick();
}
}
@Override
public void setValue(final String value) {
super.setValue(value);
int index = findIndexOfValue(value);
if (index != -1) {
//
// Update the icon for the list preference from the list of entryImages, if specified.
//
if (!mImages.isEmpty()) {
setIcon(mImages.get(index));
}
//
// Update the summary.
//
if (!mInitialSummary.isEmpty()) {
setSummary(getEntries()[index] + "\n" + mInitialSummary);
}
}
notifyChanged();
}
protected AlertDialog onPrepareDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
List<ImageListItem> items = new ArrayList<>();
int length = getEntries().length;
for (int i = 0; i < length; i++) {
int resource = 0;
if (mImages.size() > i) {
resource = mImages.get(i);
}
String currentValue = getValue();
items.add(new ImageListItem(getEntries()[i], resource, (getEntryValues()[i]).equals(currentValue)));
}
int layout = R.layout.iconlistpreference_item;
if (mUseCard) {
layout = R.layout.iconlistpreference_item_card;
}
if (mCustomItemLayout != 0) {
layout = mCustomItemLayout;
}
ListAdapter adapter = new iconListPreferenceAdapter(getContext(), layout, items);
//
// Setup the adapter to populate the alertDialog and Set the value that was selected.
//
builder.setAdapter(adapter, (dialog, which) -> setValue(getEntryValues()[which].toString()));
//
// Cancel the dialog if cancel is pressed.
//
builder.setNegativeButton(android.R.string.cancel, null);
builder.setTitle(getTitle());
return builder.create();
}
}
Only problem is that this code does not render in Android Studio when I try to view the preferences layout. Haven't figured out why yet.