Last active
April 6, 2023 09:07
-
-
Save cbeyls/7475726 to your computer and use it in GitHub Desktop.
A PreferenceFragment for the Android support library. Based on the platform's code with some removed features and a basic ListView layout.It uses reflection but works with every device I've tested so far.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package android.support.v4.preference; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Method; | |
import android.annotation.SuppressLint; | |
import android.app.Activity; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.os.Build; | |
import android.os.Bundle; | |
import android.os.Handler; | |
import android.os.Message; | |
import android.preference.Preference; | |
import android.preference.PreferenceManager; | |
import android.preference.PreferenceScreen; | |
import android.support.v4.app.Fragment; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.widget.ListView; | |
/** | |
* A PreferenceFragment for the support library. Based on the platform's code with some removed features and a basic ListView layout. | |
* | |
* @author Christophe Beyls | |
*/ | |
public abstract class PreferenceFragment extends Fragment { | |
private static final int FIRST_REQUEST_CODE = 100; | |
static final int MSG_BIND_PREFERENCES = 1; | |
static final int MSG_REQUEST_FOCUS = 2; | |
private static final String PREFERENCES_TAG = "android:preferences"; | |
private static final float HC_HORIZONTAL_PADDING = 16f; | |
@SuppressLint("HandlerLeak") | |
private final Handler mHandler = new Handler() { | |
@Override | |
public void handleMessage(Message msg) { | |
switch (msg.what) { | |
case MSG_BIND_PREFERENCES: | |
bindPreferences(); | |
break; | |
case MSG_REQUEST_FOCUS: | |
mList.focusableViewAvailable(mList); | |
break; | |
} | |
} | |
}; | |
private boolean mHavePrefs; | |
private boolean mInitDone; | |
ListView mList; | |
private PreferenceManager mPreferenceManager; | |
@Override | |
public void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
try { | |
Constructor<PreferenceManager> c = PreferenceManager.class.getDeclaredConstructor(Activity.class, int.class); | |
c.setAccessible(true); | |
mPreferenceManager = c.newInstance(this.getActivity(), FIRST_REQUEST_CODE); | |
} catch (Exception ignored) { | |
} | |
} | |
@Override | |
public View onCreateView(LayoutInflater layoutInflater, ViewGroup viewGroup, Bundle savedInstanceState) { | |
ListView listView = new ListView(getActivity()); | |
listView.setId(android.R.id.list); | |
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) && (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)) { | |
final int horizontalPadding = (int) (HC_HORIZONTAL_PADDING * getResources().getDisplayMetrics().density + 0.5f); | |
listView.setPadding(horizontalPadding, 0, horizontalPadding, 0); | |
listView.setClipToPadding(false); | |
listView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); | |
} | |
return listView; | |
} | |
@Override | |
public void onActivityCreated(Bundle savedInstanceState) { | |
super.onActivityCreated(savedInstanceState); | |
if (mHavePrefs) { | |
bindPreferences(); | |
} | |
mInitDone = true; | |
if (savedInstanceState != null) { | |
Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG); | |
if (container != null) { | |
final PreferenceScreen preferenceScreen = getPreferenceScreen(); | |
if (preferenceScreen != null) { | |
preferenceScreen.restoreHierarchyState(container); | |
} | |
} | |
} | |
} | |
public void onStop() { | |
super.onStop(); | |
try { | |
Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityStop"); | |
m.setAccessible(true); | |
m.invoke(mPreferenceManager); | |
} catch (Exception ignored) { | |
} | |
} | |
public void onDestroyView() { | |
mList = null; | |
mHandler.removeCallbacksAndMessages(null); | |
super.onDestroyView(); | |
} | |
public void onDestroy() { | |
super.onDestroy(); | |
try { | |
Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityDestroy"); | |
m.setAccessible(true); | |
m.invoke(mPreferenceManager); | |
} catch (Exception ignored) { | |
} | |
} | |
public void onSaveInstanceState(Bundle outState) { | |
super.onSaveInstanceState(outState); | |
PreferenceScreen preferenceScreen = getPreferenceScreen(); | |
if (preferenceScreen != null) { | |
Bundle container = new Bundle(); | |
preferenceScreen.saveHierarchyState(container); | |
outState.putBundle(PREFERENCES_TAG, container); | |
} | |
} | |
public void onActivityResult(int requestCode, int resultCode, Intent data) { | |
super.onActivityResult(requestCode, resultCode, data); | |
try { | |
Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityResult", int.class, int.class, Intent.class); | |
m.setAccessible(true); | |
m.invoke(mPreferenceManager, requestCode, resultCode, data); | |
} catch (Exception ignored) { | |
} | |
} | |
public PreferenceManager getPreferenceManager() { | |
return mPreferenceManager; | |
} | |
public void setPreferenceScreen(PreferenceScreen screen) { | |
try { | |
Method m = PreferenceManager.class.getDeclaredMethod("setPreferences", PreferenceScreen.class); | |
m.setAccessible(true); | |
boolean result = (Boolean) m.invoke(mPreferenceManager, screen); | |
if (result && (screen != null)) { | |
mHavePrefs = true; | |
if (mInitDone) { | |
postBindPreferences(); | |
} | |
} | |
} catch (Exception ignored) { | |
} | |
} | |
public PreferenceScreen getPreferenceScreen() { | |
try { | |
Method m = PreferenceManager.class.getDeclaredMethod("getPreferenceScreen"); | |
m.setAccessible(true); | |
return (PreferenceScreen) m.invoke(mPreferenceManager); | |
} catch (Exception e) { | |
return null; | |
} | |
} | |
public void addPreferencesFromIntent(Intent intent) { | |
requirePreferenceManager(); | |
try { | |
Method m = PreferenceManager.class.getDeclaredMethod("inflateFromIntent", Intent.class, PreferenceScreen.class); | |
m.setAccessible(true); | |
PreferenceScreen screen = (PreferenceScreen) m.invoke(mPreferenceManager, intent, getPreferenceScreen()); | |
setPreferenceScreen(screen); | |
} catch (Exception ignored) { | |
} | |
} | |
public void addPreferencesFromResource(int resId) { | |
requirePreferenceManager(); | |
try { | |
Method m = PreferenceManager.class.getDeclaredMethod("inflateFromResource", Context.class, int.class, PreferenceScreen.class); | |
m.setAccessible(true); | |
PreferenceScreen screen = (PreferenceScreen) m.invoke(mPreferenceManager, getActivity(), resId, getPreferenceScreen()); | |
setPreferenceScreen(screen); | |
} catch (Exception ignored) { | |
} | |
} | |
public Preference findPreference(CharSequence key) { | |
if (mPreferenceManager == null) { | |
return null; | |
} | |
return mPreferenceManager.findPreference(key); | |
} | |
private void requirePreferenceManager() { | |
if (this.mPreferenceManager == null) { | |
throw new RuntimeException("This should be called after super.onCreate."); | |
} | |
} | |
private void postBindPreferences() { | |
if (!mHandler.hasMessages(MSG_BIND_PREFERENCES)) { | |
mHandler.sendEmptyMessage(MSG_BIND_PREFERENCES); | |
} | |
} | |
void bindPreferences() { | |
final PreferenceScreen preferenceScreen = getPreferenceScreen(); | |
if (preferenceScreen != null) { | |
preferenceScreen.bind(getListView()); | |
} | |
} | |
public ListView getListView() { | |
ensureList(); | |
return mList; | |
} | |
private void ensureList() { | |
if (mList != null) { | |
return; | |
} | |
View root = getView(); | |
if (root == null) { | |
throw new IllegalStateException("Content view not yet created"); | |
} | |
View rawListView = root.findViewById(android.R.id.list); | |
if (rawListView == null) { | |
throw new RuntimeException("Your content must have a ListView whose id attribute is 'android.R.id.list'"); | |
} | |
if (!(rawListView instanceof ListView)) { | |
throw new RuntimeException("Content has view with id attribute 'android.R.id.list' that is not a ListView class"); | |
} | |
mList = (ListView) rawListView; | |
mHandler.sendEmptyMessage(MSG_REQUEST_FOCUS); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Since AppCompat 23, you can now also use the Preferences Support Library. However, preferences tend to look bad with that library (even on modern devices) and a lot of hacks are required to make it work decently, not to mention all the added code to your apk file. For now I'm still using AppCompatPreferenceActivity since my preference screens are quite simple.