Java tutorial
/*####################################################### * * Maintained by Gregor Santner, 2017- * https://gsantner.net/ * * License: Apache 2.0 * https://github.com/gsantner/opoc/#licensing * https://www.apache.org/licenses/LICENSE-2.0 * #########################################################*/ /* * Add dependencies: implementation "com.android.support:preference-v7:${version_library_appcompat}" implementation "com.android.support:preference-v14:${version_library_appcompat}" * Apply to activity using setTheme(), add to styles.xml/theme: <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item> * OR <style name="AppTheme" ... <item name="preferenceTheme">@style/AppTheme.PreferenceTheme</item> </style> <style name="AppTheme.PreferenceTheme" parent="PreferenceThemeOverlay.v14.Material"> <item name="preferenceCategoryStyle">@style/AppTheme.PreferenceTheme.CategoryStyle</item> </style> <style name="AppTheme.PreferenceTheme.CategoryStyle" parent="Preference.Category"> <item name="android:layout">@layout/pref_category_text</item> </style> * Layout file: <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/title" style="?android:attr/listSeparatorTextViewStyle" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAllCaps="false" android:textColor="@color/colorAccent" /> */ package net.gsantner.opoc.preference; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Bundle; import android.support.annotation.ColorInt; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.annotation.XmlRes; import android.support.v4.app.Fragment; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceFragmentCompat; import android.support.v7.preference.PreferenceGroup; import android.support.v7.preference.PreferenceScreen; import android.text.TextUtils; import android.view.View; import net.gsantner.opoc.util.Callback; import net.gsantner.opoc.util.ContextUtils; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Baseclass to use as preference fragment (with support libraries) */ @SuppressWarnings({ "WeakerAccess", "unused" }) public abstract class GsPreferenceFragmentCompat<AS extends SharedPreferencesPropertyBackend> extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener, PreferenceFragmentCompat.OnPreferenceStartScreenCallback { private static final int DEFAULT_ICON_TINT_DELAY = 200; // // Abstract // @XmlRes public abstract int getPreferenceResourceForInflation(); public abstract String getFragmentTag(); protected abstract AS getAppSettings(Context context); // // Virtual // public Boolean onPreferenceClicked(Preference preference) { return null; } public String getSharedPreferencesName() { return "app"; } protected void afterOnCreate(Bundle savedInstances, Context context) { } public synchronized void doUpdatePreferences() { } protected void onPreferenceScreenChanged(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) { } public Integer getIconTintColor() { return _defaultIconTintColor; } public String getTitle() { return null; } // // // private final Set<String> _registeredPrefs = new HashSet<>(); private final List<PreferenceScreen> _prefScreenBackstack = new ArrayList<>(); protected AS _appSettings; protected int _defaultIconTintColor; protected ContextUtils _cu; @Override @Deprecated public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { Activity activity = getActivity(); _appSettings = getAppSettings(activity); _cu = new ContextUtils(activity); getPreferenceManager().setSharedPreferencesName(getSharedPreferencesName()); addPreferencesFromResource(getPreferenceResourceForInflation()); if (activity != null && activity.getTheme() != null) { TypedArray array = activity.getTheme() .obtainStyledAttributes(new int[] { android.R.attr.colorBackground }); int bgcolor = array.getColor(0, 0xFFFFFFFF); _defaultIconTintColor = _cu.shouldColorOnTopBeLight(bgcolor) ? Color.WHITE : Color.BLACK; } // on bottom afterOnCreate(savedInstanceState, activity); } public final Callback.a1<PreferenceFragmentCompat> updatePreferenceIcons = (frag) -> { try { View view = frag.getView(); final Integer color = getIconTintColor(); if (view != null && color != null) { Runnable r = () -> tintAllPrefIcons(frag, color); for (long delayFactor : new int[] { 1, 10, 50, 100, 500 }) { view.postDelayed(r, delayFactor * DEFAULT_ICON_TINT_DELAY); } } } catch (Exception ignored) { } }; public void tintAllPrefIcons(PreferenceFragmentCompat preferenceFragment, @ColorInt int iconColor) { tintPrefIconsRecursive(getPreferenceScreen(), iconColor); } private void tintPrefIconsRecursive(PreferenceGroup prefGroup, @ColorInt int iconColor) { if (prefGroup != null && isAdded()) { int prefCount = prefGroup.getPreferenceCount(); for (int i = 0; i < prefCount; i++) { Preference pref = prefGroup.getPreference(i); if (pref != null) { if (isAllowedToTint(pref)) { pref.setIcon(_cu.tintDrawable(pref.getIcon(), iconColor)); } if (pref instanceof PreferenceGroup) { tintPrefIconsRecursive((PreferenceGroup) pref, iconColor); } } } } } protected boolean isAllowedToTint(Preference pref) { return true; } /** * Try to fetch string resource id from key * This only works if the key is only defined once and value=key */ protected int keyToStringResId(Preference preference) { if (preference != null && !TextUtils.isEmpty(preference.getKey())) { return _cu.getResId(ContextUtils.ResType.STRING, preference.getKey()); } return 0; } /** * Try to fetch string resource id from key * This only works if the key is only defined once and value=key */ protected int keyToStringResId(String keyAsString) { return _cu.getResId(ContextUtils.ResType.STRING, keyAsString); } @Override public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); updatePreferenceIcons.callback(this); } private synchronized void updatePreferenceChangedListeners(boolean shouldListen) { String tprefname = getSharedPreferencesName(); if (shouldListen && tprefname != null && !_registeredPrefs.contains(tprefname)) { SharedPreferences preferences = _appSettings.getContext().getSharedPreferences(tprefname, Context.MODE_PRIVATE); _appSettings.registerPreferenceChangedListener(preferences, this); _registeredPrefs.add(tprefname); } else if (!shouldListen) { for (String prefname : _registeredPrefs) { SharedPreferences preferences = _appSettings.getContext().getSharedPreferences(tprefname, Context.MODE_PRIVATE); _appSettings.unregisterPreferenceChangedListener(preferences, this); } } } @Override public void onResume() { super.onResume(); updatePreferenceChangedListeners(true); doUpdatePreferences(); // Invoked later onPreferenceScreenChangedPriv(this, getPreferenceScreen()); } @Override public void onPause() { super.onPause(); updatePreferenceChangedListeners(false); } @Override @Deprecated public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (isAdded()) { onPreferenceChanged(sharedPreferences, key); doUpdatePreferences(); } } protected void onPreferenceChanged(SharedPreferences prefs, String key) { // Wait some ms to be sure the pref objects have changed it's internal values // and the new values are ready to be read ;) Runnable r = this::doUpdatePreferences; if (getView() != null) { getView().postDelayed(r, 350); } else { r.run(); } } @Override @Deprecated public boolean onPreferenceTreeClick(Preference preference) { if (isAdded() && preference.hasKey()) { Boolean ret = onPreferenceClicked(preference); if (ret != null) { return ret; } } return super.onPreferenceTreeClick(preference); } @Override public Fragment getCallbackFragment() { return this; } @Override public void onStop() { _prefScreenBackstack.clear(); super.onStop(); } @Deprecated @Override public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) { _prefScreenBackstack.add(getPreferenceScreen()); preferenceFragmentCompat.setPreferenceScreen(preferenceScreen); updatePreferenceIcons.callback(this); onPreferenceScreenChangedPriv(preferenceFragmentCompat, preferenceScreen); return true; } protected void updateSummary(@StringRes int keyResId, CharSequence summary) { updatePreference(keyResId, null, null, summary, null); } /** * Finds a {@link Preference} based on its key res id. * * @param key The key of the preference to retrieve. * @return The {@link Preference} with the key, or null. * @see android.support.v7.preference.PreferenceGroup#findPreference(CharSequence) */ public Preference findPreference(@StringRes int key) { return findPreference(getString(key)); } @Nullable @SuppressWarnings("SameParameterValue") protected Preference updatePreference(@StringRes int keyResId, @DrawableRes Integer iconRes, CharSequence title, CharSequence summary, Boolean visible) { Preference pref = findPreference(getString(keyResId)); if (pref != null) { if (summary != null) { pref.setSummary(summary); } if (title != null) { pref.setTitle(title); } if (iconRes != null && iconRes != 0) { if (isAllowedToTint(pref)) { pref.setIcon(_cu.tintDrawable(iconRes, getIconTintColor())); } else { pref.setIcon(iconRes); } } if (visible != null) { pref.setVisible(visible); } } return pref; } protected void removePreference(@Nullable Preference preference) { if (preference == null) { return; } PreferenceGroup parent = getPreferenceParent(getPreferenceScreen(), preference); if (parent == null) { return; } parent.removePreference(preference); } public boolean canGoBack() { return !_prefScreenBackstack.isEmpty(); } public void goBack() { if (canGoBack()) { PreferenceScreen screen = _prefScreenBackstack.remove(_prefScreenBackstack.size() - 1); if (screen != null) { setPreferenceScreen(screen); onPreferenceScreenChangedPriv(this, screen); } } } protected PreferenceGroup getPreferenceParent(PreferenceGroup prefGroup, Preference pref) { for (int i = 0; i < prefGroup.getPreferenceCount(); ++i) { Preference prefChild = prefGroup.getPreference(i); if (prefChild == pref) { return prefGroup; } if (prefChild instanceof PreferenceGroup) { PreferenceGroup childGroup = (PreferenceGroup) prefChild; PreferenceGroup result = getPreferenceParent(childGroup, pref); if (result != null) { return result; } } } return null; } private void onPreferenceScreenChangedPriv(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) { onPreferenceScreenChanged(preferenceFragmentCompat, preferenceScreen); updatePreferenceChangedListeners(true); } /** * Is key equal * * @param pref A preference * @param resIdKey the resource id of the string * @return if equals */ public boolean eq(@Nullable Preference pref, @StringRes int resIdKey) { return pref != null && getString(resIdKey).equals(pref.getKey()); } /** * Is key equal * * @param key the key * @param resIdKey the resource id of the string * @return if equals */ public boolean eq(@Nullable String key, @StringRes int resIdKey) { return getString(resIdKey).equals(key); } public boolean hasTitle() { return !TextUtils.isEmpty(getTitle()); } public String getTitleOrDefault(String defaultTitle) { return hasTitle() ? getTitle() : defaultTitle; } protected void restartActivity() { Activity activity; if (isAdded() && (activity = getActivity()) != null) { Intent intent = getActivity().getIntent(); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION); activity.overridePendingTransition(0, 0); activity.finish(); activity.overridePendingTransition(0, 0); startActivity(intent); } } /** * Append a pref to given {@code target}. If target is null, the current screen is taken * The pref icon is tint according to color * * @param pref Preference to add * @param target The target to add the pref to, or null for current screen * @return true if successfully added */ protected boolean appendPreference(Preference pref, @Nullable PreferenceGroup target) { if (target == null) { if ((target = getPreferenceScreen()) == null) { return false; } } if (getIconTintColor() != null && pref.getIcon() != null && isAllowedToTint(pref)) { pref.setIcon(_cu.tintDrawable(pref.getIcon(), getIconTintColor())); } return target.addPreference(pref); } }