'How can I disable a Preference Header inside a PreferenceActivity
I have an Activity that extends PreferenceActivity.
In onBuildHeaders I load the preference-headers that I defined in XML.
@Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.admin_preference_headers, target);
}
Here is the XML File itself.
<?xml version="1.0" encoding="utf-8"?>
<preference-headers
xmlns:android="http://schemas.android.com/apk/res/android">
<header android:fragment="..."
android:icon="@drawable/image"
android:title="title"
android:summary="summery" />
</preference-headers>
How do I disable on of these headers so they are grayed out, making them unclickable?
My guess would be to manipulate the ListView items of the PreferenceActivity but I'm not sure what to do exactly.
Solution 1:[1]
One possibility would be to override isEnabled(int position) in your adapter that extends ArrayAdapter<Header>. This would make the item not clickable by default, then you could make the text grey in the getView(int position, View convertView, ViewGroup parent) method.
Then in PreferenceActivity you would override setListAdapter(ListAdapter adapter) to call super with your custom adapter.
Solution 2:[2]
dodkoc's answer is on the right track, but not complete.
Lets say you disable the header based on a preference. In that case, if you simply override the setListAdapter() method and supply it with your custom adapter, your header list will not get updated when that preference is changed. This is because the PreferenceActivity calls the setListAdapter() only once during the onCreate() method. Calling invalidateHeaders() will not help either.
So here is how I went about this:
- Create a custom adapter. I stuck to adapter class in the source code of
PreferenceActivityand added a method which returns true when a header at a given position should be enabled.
HeaderAdapter
private static class HeaderAdapter extends ArrayAdapter<Header> {
private static class HeaderViewHolder {
ImageView icon;
TextView title;
TextView summary;
}
private LayoutInflater mInflater;
private int mLayoutResId;
private boolean mRemoveIconIfEmpty;
public HeaderAdapter(Context context, List<Header> objects, int layoutResId, boolean removeIconBehavior) {
super(context, 0, objects);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mLayoutResId = layoutResId;
mRemoveIconIfEmpty = removeIconBehavior;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder holder;
View view;
if (convertView == null) {
view = mInflater.inflate(mLayoutResId, parent, false);
holder = new HeaderViewHolder();
holder.icon = (ImageView) view.findViewById(R.id.preference_header_list_item_icon);
holder.title = (TextView) view.findViewById(R.id.preference_header_list_item_title);
holder.summary = (TextView) view.findViewById(R.id.preference_header_list_item_summary);
view.setTag(holder);
} else {
view = convertView;
holder = (HeaderViewHolder) view.getTag();
}
// All view fields must be updated every time, because the view may be recycled
Header header = getItem(position);
if (mRemoveIconIfEmpty) {
if (header.iconRes == 0) {
holder.icon.setVisibility(View.GONE);
} else {
holder.icon.setVisibility(View.VISIBLE);
holder.icon.setImageResource(header.iconRes);
}
} else {
holder.icon.setImageResource(header.iconRes);
}
boolean isEnabled = isHeaderEnabled(position);
holder.title.setText(header.getTitle(getContext().getResources()));
int enabledColor = getContext().getResources().getColor(R.color.text);
int disabledTitleColor = getContext().getResources().getColor(R.color.text_disabled);
holder.title.setTextColor(isEnabled ? enabledColor : disabledTitleColor);
CharSequence summary = header.getSummary(getContext().getResources());
if (!TextUtils.isEmpty(summary)) {
holder.summary.setVisibility(View.VISIBLE);
holder.summary.setText(summary);
int disabledSummaryColor = getContext().getResources().getColor(R.color.secondary_text_disabled);
holder.summary.setTextColor(isEnabled ? enabledColor : disabledSummaryColor);
} else {
holder.summary.setVisibility(View.GONE);
}
return view;
}
public boolean isHeaderEnabled(int position) {
boolean isEnabled = true;
if (position == 2) {
String prefValue = PreferenceManager.getDefaultSharedPreferences(getContext()).getString(PREF_KEY, null);
if (prefValue != null) {
if (//TODO Your condition to disable the header based on prefValue)
isEnabled = false;
}
}
return isEnabled;
}
}
NOTE: You do NOT want to override isEnabled() method in the ArrayAdapter to return false for items that you want to disable. This messes up the dividers in the ListView in Android 5.0 (Lollipop). If isEnabled() returns false for an item, dividers around that item disappear in Android 5.0. Thus I made another method called isHeaderEnabled() to check if the item should be enabled or not.
- I also copied the layout for header list item from the source code into my layout folder and renamed the ids
preference_header_list_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/activatedBackgroundIndicator"
android:gravity="center_vertical"
android:minHeight="48dp"
android:paddingRight="?android:attr/scrollbarSize">
<ImageView
android:id="@+id/preference_header_list_item_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="6dp"
android:layout_marginStart="6dp" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="6dp"
android:layout_marginTop="6dp"
android:layout_weight="1">
<TextView
android:id="@+id/preference_header_list_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/preference_header_list_item_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/preference_header_list_item_title"
android:layout_below="@+id/preference_header_list_item_title"
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
</LinearLayout>
- Override
onPostResume(),onBuildHeaders()andonListItemClick()methods in thePreferenceActivity. In theonPostResume()method I callinvalidateHeader()which recreates the header list. But this list is not passed on to the list adapter and thus the ListView will not be automatically updated. So I do this manually in theonBuildHeaders()method. Lastly, since I did not override theisEnabled()method in theArrayAdapterto return false for the disabled item, users can still click the disabled headers. So I override theonListItemClick()method and bypass calling super for the disabled headers.
SettingsActivity.java (only relevant methods shown)
@Override
protected void onPostResume() {
super.onPostResume();
invalidateHeaders();
}
@Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.pref_headers, target);
// Updating summary to indicate why the header is disabled as per the android design guideline
String prefValue = PreferenceManager.getDefaultSharedPreferences(this).getString(PREF_KEY, null);
if (prefValue != null) {
if (//TODO Your condition to disable the header based on prefValue) {
target.get(2).summary = "Not applicable because you have not enabled something";
} else {
target.get(2).summary = null;
}
}
// Creating a new adapter based on the new header list and passing it to the ListView
HeaderAdapter adapter = new HeaderAdapter(this, target, R.layout.preference_header_list_item, true);
setListAdapter(adapter);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
if (((HeaderAdapter) getListView().getAdapter()).isHeaderEnabled(position))
super.onListItemClick(l, v, position, id);
}
NOTE: If you are in the multi-pane mode you may need to call invalidateHeaders() in the OnPreferenceChangeListener.onPreferenceChange() method of the relevant preference as onResume() will not get called when you move between contents of different headers.
Solution 3:[3]
Seems to me the easiest way to adjust the existing layout of PreferenceActivity is to get the current adapter and make as little change as it is possible by decorating over it.
Such as
@Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(...);
final ListAdapter originalAdapter = getListAdapter();
final ArrayAdapter<Header> adapter = new ArrayAdapter<Header>(this, 0, target) {
@NonNull
@Override
public View getView(final int position, @Nullable final View convertView, @NonNull final ViewGroup parent) {
final View view = originalAdapter.getView(position, convertView, parent);
// alter some view based on its position
if (position == 2) {
((TextView) view.findViewById(android.R.id.title))
.setTextColor(ContextCompat.getColor(Activity.this, android.R.color.secondary_text_dark_nodisable));
}
return view;
}
};
setListAdapter(adapter);
}
This way you don't have to get into all kinds of UX inconsistencies when it comes to PreferencesActivity.
Aside note: don't ever use PreferenceActivity or any API that is related to it . This is pure evil!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | dodkoc |
| Solution 2 | |
| Solution 3 | foo |
