Subclasses:
BaseLeanbackPreferenceFragmentCompat, LeanbackPreferenceFragmentCompat
Gradle dependencies
compile group: 'androidx.preference', name: 'preference', version: '1.2.1'
- groupId: androidx.preference
- artifactId: preference
- version: 1.2.1
Artifact androidx.preference:preference:1.2.1 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.preference:preference com.android.support:preference-v7
Androidx class mapping:
androidx.preference.PreferenceFragmentCompat android.support.v7.preference.PreferenceFragmentCompat
Overview
A PreferenceFragmentCompat is the entry point to using the Preference library. This
Fragment displays a hierarchy of Preference objects to the user. It also
handles persisting values to the device. To retrieve an instance of
android.content.SharedPreferences
that the preference hierarchy in this fragment will
use by default, call
PreferenceManager with a context
in the same package as this fragment.
You can define a preference hierarchy as an XML resource, or you can build a hierarchy in
code. In both cases you need to use a PreferenceScreen as the root component in your
hierarchy.
To inflate from XML, use the PreferenceFragmentCompat.setPreferencesFromResource(int, String). An example
example XML resource is shown further down.
To build a hierarchy from code, use
PreferenceManager.createPreferenceScreen(Context) to create the root
PreferenceScreen. Once you have added other Preferences to this root screen
with PreferenceGroup.addPreference(Preference), you then need to set the screen as
the root screen in your hierarchy with PreferenceFragmentCompat.setPreferenceScreen(PreferenceScreen).
As a convenience, this fragment implements a click listener for any preference in the
current hierarchy, see PreferenceFragmentCompat.onPreferenceTreeClick(Preference).
Developer Guides
For more information about
building a settings screen using the AndroidX Preference library, see
Settings.
Sample Code
The following sample code shows a simple settings screen using an XML resource. The XML
resource is as follows:
frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences
The fragment that loads the XML resource is as follows:
frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/Preferences.java preferences
Summary
Methods |
---|
public void | addPreferencesFromResource(int preferencesResId)
Inflates the given XML resource and adds the preference hierarchy to the current
preference hierarchy. |
public Preference | findPreference(java.lang.CharSequence key)
|
public Fragment | getCallbackFragment()
A wrapper for getParentFragment which is v17+. |
public final RecyclerView | getListView()
|
public PreferenceManager | getPreferenceManager()
Returns the PreferenceManager used by this fragment. |
public PreferenceScreen | getPreferenceScreen()
Gets the root of the preference hierarchy that this fragment is showing. |
protected void | onBindPreferences()
Used by Settings. |
public void | onCreate(Bundle savedInstanceState)
Called to do initial creation of a fragment. |
protected RecyclerView.Adapter | onCreateAdapter(PreferenceScreen preferenceScreen)
Creates the root adapter. |
public RecyclerView.LayoutManager | onCreateLayoutManager()
Called from PreferenceFragmentCompat.onCreateRecyclerView(LayoutInflater, ViewGroup, Bundle) to create the
for the created RecyclerView. |
public abstract void | onCreatePreferences(Bundle savedInstanceState, java.lang.String rootKey)
Called during PreferenceFragmentCompat.onCreate(Bundle) to supply the preferences for this fragment. |
public RecyclerView | onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
Creates the RecyclerView used to display the preferences. |
public View | onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
Called to have the fragment instantiate its user interface view. |
public void | onDestroyView()
Called when the view previously created by Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle) has
been detached from the fragment. |
public void | onDisplayPreferenceDialog(Preference preference)
Called when a preference in the tree requests to display a dialog. |
public void | onNavigateToScreen(PreferenceScreen preferenceScreen)
Called by PreferenceScreen.onClick() in order to navigate to a new screen of
preferences. |
public boolean | onPreferenceTreeClick(Preference preference)
|
public void | onSaveInstanceState(Bundle outState)
Called to ask the fragment to save its current dynamic state, so it
can later be reconstructed in a new instance if its process is
restarted. |
public void | onStart()
Called when the Fragment is visible to the user. |
public void | onStop()
Called when the Fragment is no longer started. |
protected void | onUnbindPreferences()
Used by Settings. |
public void | onViewCreated(View view, Bundle savedInstanceState)
Called immediately after Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle)
has returned, but before any saved state has been restored in to the view. |
public void | scrollToPreference(Preference preference)
|
public void | scrollToPreference(java.lang.String key)
|
public void | setDivider(Drawable divider)
Sets the Drawable that will be drawn between each item in the list. |
public void | setDividerHeight(int height)
Sets the height of the divider that will be drawn between each item in the list. |
public void | setPreferenceScreen(PreferenceScreen preferenceScreen)
Sets the root of the preference hierarchy that this fragment is showing. |
public void | setPreferencesFromResource(int preferencesResId, java.lang.String key)
Inflates the given XML resource and replaces the current preference hierarchy (if any) with
the preference hierarchy rooted at key. |
from Fragment | dump, equals, getActivity, getAllowEnterTransitionOverlap, getAllowReturnTransitionOverlap, getArguments, getChildFragmentManager, getContext, getDefaultViewModelCreationExtras, getDefaultViewModelProviderFactory, getEnterTransition, getExitTransition, getFragmentManager, getHost, getId, getLayoutInflater, getLayoutInflater, getLifecycle, getLoaderManager, getParentFragment, getParentFragmentManager, getReenterTransition, getResources, getRetainInstance, getReturnTransition, getSavedStateRegistry, getSharedElementEnterTransition, getSharedElementReturnTransition, getString, getString, getTag, getTargetFragment, getTargetRequestCode, getText, getUserVisibleHint, getView, getViewLifecycleOwner, getViewLifecycleOwnerLiveData, getViewModelStore, hashCode, hasOptionsMenu, instantiate, instantiate, isAdded, isDetached, isHidden, isInLayout, isMenuVisible, isRemoving, isResumed, isStateSaved, isVisible, onActivityCreated, onActivityResult, onAttach, onAttach, onAttachFragment, onConfigurationChanged, onContextItemSelected, onCreateAnimation, onCreateAnimator, onCreateContextMenu, onCreateOptionsMenu, onDestroy, onDestroyOptionsMenu, onDetach, onGetLayoutInflater, onHiddenChanged, onInflate, onInflate, onLowMemory, onMultiWindowModeChanged, onOptionsItemSelected, onOptionsMenuClosed, onPause, onPictureInPictureModeChanged, onPrepareOptionsMenu, onPrimaryNavigationFragmentChanged, onRequestPermissionsResult, onResume, onViewStateRestored, postponeEnterTransition, postponeEnterTransition, registerForActivityResult, registerForActivityResult, registerForContextMenu, requestPermissions, requireActivity, requireArguments, requireContext, requireFragmentManager, requireHost, requireParentFragment, requireView, setAllowEnterTransitionOverlap, setAllowReturnTransitionOverlap, setArguments, setEnterSharedElementCallback, setEnterTransition, setExitSharedElementCallback, setExitTransition, setHasOptionsMenu, setInitialSavedState, setMenuVisibility, setReenterTransition, setRetainInstance, setReturnTransition, setSharedElementEnterTransition, setSharedElementReturnTransition, setTargetFragment, setUserVisibleHint, shouldShowRequestPermissionRationale, startActivity, startActivity, startActivityForResult, startActivityForResult, startIntentSenderForResult, startPostponedEnterTransition, toString, unregisterForContextMenu |
from java.lang.Object | clone, finalize, getClass, notify, notifyAll, wait, wait, wait |
Fields
public static final java.lang.String
ARG_PREFERENCE_ROOTFragment argument used to specify the tag of the desired root PreferenceScreen
object.
Constructors
public
PreferenceFragmentCompat()
Methods
public void
onCreate(Bundle savedInstanceState)
Called to do initial creation of a fragment. This is called after
Fragment.onAttach(Activity) and before
Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle).
Note that this can be called while the fragment's activity is
still in the process of being created. As such, you can not rely
on things like the activity's content view hierarchy being initialized
at this point. If you want to do work once the activity itself is
created, add a LifecycleObserver on the
activity's Lifecycle, removing it when it receives the
callback.
Any restored child fragments will be created before the base
Fragment.onCreate
method returns.
Parameters:
savedInstanceState: If the fragment is being re-created from
a previous saved state, this is the state.
public abstract void
onCreatePreferences(Bundle savedInstanceState, java.lang.String rootKey)
Called during PreferenceFragmentCompat.onCreate(Bundle) to supply the preferences for this fragment.
Subclasses are expected to call PreferenceFragmentCompat.setPreferenceScreen(PreferenceScreen) either
directly or via helper methods such as PreferenceFragmentCompat.addPreferencesFromResource(int).
Parameters:
savedInstanceState: If the fragment is being re-created from a previous saved state,
this is the state.
rootKey: If non-null, this preference fragment should be rooted at the
PreferenceScreen with this key.
public View
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
Called to have the fragment instantiate its user interface view.
This is optional, and non-graphical fragments can return null. This will be called between
Fragment.onCreate(Bundle) and Fragment.onViewCreated(View, Bundle).
A default View can be returned by calling Fragment.Fragment(int) in your
constructor. Otherwise, this method returns null.
It is recommended to only inflate the layout in this method and move
logic that operates on the returned View to Fragment.onViewCreated(View, Bundle).
If you return a View from here, you will later be called in
Fragment.onDestroyView() when the view is being released.
Parameters:
inflater: The LayoutInflater object that can be used to inflate
any views in the fragment,
container: If non-null, this is the parent view that the fragment's
UI should be attached to. The fragment should not add the view itself,
but this can be used to generate the LayoutParams of the view.
savedInstanceState: If non-null, this fragment is being re-constructed
from a previous saved state as given here.
Returns:
Return the View for the fragment's UI, or null.
public void
setDivider(Drawable divider)
Sets the Drawable
that will be drawn between each item in the list.
Note: If the drawable does not have an intrinsic height, you should also
call PreferenceFragmentCompat.setDividerHeight(int).
Parameters:
divider: The drawable to use
public void
setDividerHeight(int height)
Sets the height of the divider that will be drawn between each item in the list. Calling
this will override the intrinsic height as set by PreferenceFragmentCompat.setDivider(Drawable).
Parameters:
height: The new height of the divider in pixels
public void
onViewCreated(View view, Bundle savedInstanceState)
Called immediately after Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle)
has returned, but before any saved state has been restored in to the view.
This gives subclasses a chance to initialize themselves once
they know their view hierarchy has been completely created. The fragment's
view hierarchy is not however attached to its parent at this point.
Parameters:
view: The View returned by Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle).
savedInstanceState: If non-null, this fragment is being re-constructed
from a previous saved state as given here.
Called when the Fragment is visible to the user. This is generally
tied to of the containing
Activity's lifecycle.
Called when the Fragment is no longer started. This is generally
tied to of the containing
Activity's lifecycle.
public void
onDestroyView()
Called when the view previously created by Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle) has
been detached from the fragment. The next time the fragment needs
to be displayed, a new view will be created. This is called
after Fragment.onStop() and before Fragment.onDestroy(). It is called
regardless of whether Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle) returned a
non-null view. Internally it is called after the view's state has
been saved but before it has been removed from its parent.
public void
onSaveInstanceState(Bundle outState)
Called to ask the fragment to save its current dynamic state, so it
can later be reconstructed in a new instance if its process is
restarted. If a new instance of the fragment later needs to be
created, the data you place in the Bundle here will be available
in the Bundle given to Fragment.onCreate(Bundle),
Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle), and
Fragment.onViewCreated(View, Bundle).
This corresponds to and most of the discussion there
applies here as well. Note however: this method may be called
at any time before Fragment.onDestroy(). There are many situations
where a fragment may be mostly torn down (such as when placed on the
back stack with no UI showing), but its state will not be saved until
its owning activity actually needs to save its state.
Parameters:
outState: Bundle in which to place your saved state.
Returns the PreferenceManager used by this fragment.
Returns:
The PreferenceManager used by this fragment
Gets the root of the preference hierarchy that this fragment is showing.
Returns:
The PreferenceScreen that is the root of the preference hierarchy
Sets the root of the preference hierarchy that this fragment is showing.
Parameters:
preferenceScreen: The root PreferenceScreen of the preference hierarchy
public void
addPreferencesFromResource(int preferencesResId)
Inflates the given XML resource and adds the preference hierarchy to the current
preference hierarchy.
Parameters:
preferencesResId: The XML resource ID to inflate
public void
setPreferencesFromResource(int preferencesResId, java.lang.String key)
Inflates the given XML resource and replaces the current preference hierarchy (if any) with
the preference hierarchy rooted at key.
Parameters:
preferencesResId: The XML resource ID to inflate
key: The preference key of the PreferenceScreen to use as the
root of the preference hierarchy, or null to use the root
PreferenceScreen.
public boolean
onPreferenceTreeClick(
Preference preference)
Called by PreferenceScreen.onClick() in order to navigate to a new screen of
preferences. Calls
PreferenceFragmentCompat.OnPreferenceStartScreenCallback.onPreferenceStartScreen(PreferenceFragmentCompat, PreferenceScreen)
if the target fragment or containing activity implements
PreferenceFragmentCompat.OnPreferenceStartScreenCallback.
Parameters:
preferenceScreen: The PreferenceScreen to navigate to
public
Preference findPreference(java.lang.CharSequence key)
protected void
onBindPreferences()
Used by Settings.
protected void
onUnbindPreferences()
Used by Settings.
public
RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
Creates the RecyclerView used to display the preferences.
Subclasses may override this to return a customized RecyclerView.
Parameters:
inflater: The LayoutInflater object that can be used to inflate the
RecyclerView.
parent: The parent ViewGroup
that the RecyclerView will be attached
to. This method should not add the view itself, but this can be
used to generate the layout params of the view.
savedInstanceState: If non-null, this view is being re-constructed from a previous
saved state as given here.
Returns:
A new RecyclerView object to be placed into the view hierarchy
Called from PreferenceFragmentCompat.onCreateRecyclerView(LayoutInflater, ViewGroup, Bundle) to create the
for the created RecyclerView.
Returns:
A new instance
Creates the root adapter.
Parameters:
preferenceScreen: The PreferenceScreen object to create the adapter for
Returns:
An adapter that contains the preferences contained in this PreferenceScreen
public void
onDisplayPreferenceDialog(
Preference preference)
Called when a preference in the tree requests to display a dialog. Subclasses should
override this method to display custom dialogs or to handle dialogs for custom preference
classes.
Parameters:
preference: The Preference object requesting the dialog
A wrapper for getParentFragment which is v17+. Used by the leanback preference lib.
Returns:
The Fragment to possibly use as a callback
public void
scrollToPreference(java.lang.String key)
public void
scrollToPreference(
Preference preference)
Source
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.preference;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.XmlRes;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
/**
* A PreferenceFragmentCompat is the entry point to using the Preference library. This
* {@link Fragment} displays a hierarchy of {@link Preference} objects to the user. It also
* handles persisting values to the device. To retrieve an instance of
* {@link android.content.SharedPreferences} that the preference hierarchy in this fragment will
* use by default, call
* {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)} with a context
* in the same package as this fragment.
*
* <p>You can define a preference hierarchy as an XML resource, or you can build a hierarchy in
* code. In both cases you need to use a {@link PreferenceScreen} as the root component in your
* hierarchy.
*
* <p>To inflate from XML, use the {@link #setPreferencesFromResource(int, String)}. An example
* example XML resource is shown further down.
*
* <p>To build a hierarchy from code, use
* {@link PreferenceManager#createPreferenceScreen(Context)} to create the root
* {@link PreferenceScreen}. Once you have added other {@link Preference}s to this root screen
* with {@link PreferenceScreen#addPreference(Preference)}, you then need to set the screen as
* the root screen in your hierarchy with {@link #setPreferenceScreen(PreferenceScreen)}.
*
* <p>As a convenience, this fragment implements a click listener for any preference in the
* current hierarchy, see {@link #onPreferenceTreeClick(Preference)}.
*
* <div class="special reference"> <h3>Developer Guides</h3> <p>For more information about
* building a settings screen using the AndroidX Preference library, see
* <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>.</p> </div>
*
* <a name="SampleCode"></a>
* <h3>Sample Code</h3>
*
* <p>The following sample code shows a simple settings screen using an XML resource. The XML
* resource is as follows:</p>
*
* {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences}
*
* <p>The fragment that loads the XML resource is as follows:</p>
*
* {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/Preferences.java preferences}
*
* @see Preference
* @see PreferenceScreen
*/
public abstract class PreferenceFragmentCompat extends Fragment implements
PreferenceManager.OnPreferenceTreeClickListener,
PreferenceManager.OnDisplayPreferenceDialogListener,
PreferenceManager.OnNavigateToScreenListener,
DialogPreference.TargetFragment {
private static final String TAG = "PreferenceFragment";
/**
* Fragment argument used to specify the tag of the desired root {@link PreferenceScreen}
* object.
*/
public static final String ARG_PREFERENCE_ROOT =
"androidx.preference.PreferenceFragmentCompat.PREFERENCE_ROOT";
private static final String PREFERENCES_TAG = "android:preferences";
private static final String DIALOG_FRAGMENT_TAG =
"androidx.preference.PreferenceFragment.DIALOG";
private static final int MSG_BIND_PREFERENCES = 1;
private final DividerDecoration mDividerDecoration = new DividerDecoration();
private PreferenceManager mPreferenceManager;
@SuppressWarnings("WeakerAccess") /* synthetic access */
RecyclerView mList;
private boolean mHavePrefs;
private boolean mInitDone;
private int mLayoutResId = R.layout.preference_list_fragment;
private Runnable mSelectPreferenceRunnable;
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_BIND_PREFERENCES:
bindPreferences();
break;
}
}
};
final private Runnable mRequestFocus = new Runnable() {
@Override
public void run() {
mList.focusableViewAvailable(mList);
}
};
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final TypedValue tv = new TypedValue();
requireContext().getTheme().resolveAttribute(R.attr.preferenceTheme, tv, true);
int theme = tv.resourceId;
if (theme == 0) {
// Fallback to default theme.
theme = R.style.PreferenceThemeOverlay;
}
requireContext().getTheme().applyStyle(theme, false);
mPreferenceManager = new PreferenceManager(requireContext());
mPreferenceManager.setOnNavigateToScreenListener(this);
final Bundle args = getArguments();
final String rootKey;
if (args != null) {
rootKey = getArguments().getString(ARG_PREFERENCE_ROOT);
} else {
rootKey = null;
}
onCreatePreferences(savedInstanceState, rootKey);
}
/**
* Called during {@link #onCreate(Bundle)} to supply the preferences for this fragment.
* Subclasses are expected to call {@link #setPreferenceScreen(PreferenceScreen)} either
* directly or via helper methods such as {@link #addPreferencesFromResource(int)}.
*
* @param savedInstanceState If the fragment is being re-created from a previous saved state,
* this is the state.
* @param rootKey If non-null, this preference fragment should be rooted at the
* {@link PreferenceScreen} with this key.
*/
public abstract void onCreatePreferences(@Nullable Bundle savedInstanceState,
@Nullable String rootKey);
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
TypedArray a = requireContext().obtainStyledAttributes(null,
R.styleable.PreferenceFragmentCompat,
R.attr.preferenceFragmentCompatStyle,
0);
mLayoutResId = a.getResourceId(R.styleable.PreferenceFragmentCompat_android_layout,
mLayoutResId);
final Drawable divider = a.getDrawable(
R.styleable.PreferenceFragmentCompat_android_divider);
final int dividerHeight = a.getDimensionPixelSize(
R.styleable.PreferenceFragmentCompat_android_dividerHeight, -1);
final boolean allowDividerAfterLastItem = a.getBoolean(
R.styleable.PreferenceFragmentCompat_allowDividerAfterLastItem, true);
a.recycle();
final LayoutInflater themedInflater = inflater.cloneInContext(requireContext());
final View view = themedInflater.inflate(mLayoutResId, container, false);
final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER);
if (!(rawListContainer instanceof ViewGroup)) {
throw new IllegalStateException("Content has view with id attribute "
+ "'android.R.id.list_container' that is not a ViewGroup class");
}
final ViewGroup listContainer = (ViewGroup) rawListContainer;
final RecyclerView listView = onCreateRecyclerView(themedInflater, listContainer,
savedInstanceState);
if (listView == null) {
throw new RuntimeException("Could not create RecyclerView");
}
mList = listView;
listView.addItemDecoration(mDividerDecoration);
setDivider(divider);
if (dividerHeight != -1) {
setDividerHeight(dividerHeight);
}
mDividerDecoration.setAllowDividerAfterLastItem(allowDividerAfterLastItem);
// If mList isn't present in the view hierarchy, add it. mList is automatically inflated
// on an Auto device so don't need to add it.
if (mList.getParent() == null) {
listContainer.addView(mList);
}
mHandler.post(mRequestFocus);
return view;
}
/**
* Sets the {@link Drawable} that will be drawn between each item in the list.
*
* <p><strong>Note:</strong> If the drawable does not have an intrinsic height, you should also
* call {@link #setDividerHeight(int)}.
*
* @param divider The drawable to use
* {@link android.R.attr#divider}
*/
public void setDivider(@Nullable Drawable divider) {
mDividerDecoration.setDivider(divider);
}
/**
* Sets the height of the divider that will be drawn between each item in the list. Calling
* this will override the intrinsic height as set by {@link #setDivider(Drawable)}.
*
* @param height The new height of the divider in pixels
* {@link android.R.attr#dividerHeight}
*/
public void setDividerHeight(int height) {
mDividerDecoration.setDividerHeight(height);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState != null) {
Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
if (container != null) {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
preferenceScreen.restoreHierarchyState(container);
}
}
}
if (mHavePrefs) {
bindPreferences();
if (mSelectPreferenceRunnable != null) {
mSelectPreferenceRunnable.run();
mSelectPreferenceRunnable = null;
}
}
mInitDone = true;
}
@Override
public void onStart() {
super.onStart();
mPreferenceManager.setOnPreferenceTreeClickListener(this);
mPreferenceManager.setOnDisplayPreferenceDialogListener(this);
}
@Override
public void onStop() {
super.onStop();
mPreferenceManager.setOnPreferenceTreeClickListener(null);
mPreferenceManager.setOnDisplayPreferenceDialogListener(null);
}
@Override
public void onDestroyView() {
mHandler.removeCallbacks(mRequestFocus);
mHandler.removeMessages(MSG_BIND_PREFERENCES);
if (mHavePrefs) {
unbindPreferences();
}
mList = null;
super.onDestroyView();
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
Bundle container = new Bundle();
preferenceScreen.saveHierarchyState(container);
outState.putBundle(PREFERENCES_TAG, container);
}
}
/**
* Returns the {@link PreferenceManager} used by this fragment.
*
* @return The {@link PreferenceManager} used by this fragment
*/
public PreferenceManager getPreferenceManager() {
return mPreferenceManager;
}
/**
* Gets the root of the preference hierarchy that this fragment is showing.
*
* @return The {@link PreferenceScreen} that is the root of the preference hierarchy
*/
public PreferenceScreen getPreferenceScreen() {
return mPreferenceManager.getPreferenceScreen();
}
/**
* Sets the root of the preference hierarchy that this fragment is showing.
*
* @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy
*/
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
onUnbindPreferences();
mHavePrefs = true;
if (mInitDone) {
postBindPreferences();
}
}
}
/**
* Inflates the given XML resource and adds the preference hierarchy to the current
* preference hierarchy.
*
* @param preferencesResId The XML resource ID to inflate
*/
public void addPreferencesFromResource(@XmlRes int preferencesResId) {
requirePreferenceManager();
setPreferenceScreen(mPreferenceManager.inflateFromResource(requireContext(),
preferencesResId, getPreferenceScreen()));
}
/**
* Inflates the given XML resource and replaces the current preference hierarchy (if any) with
* the preference hierarchy rooted at {@code key}.
*
* @param preferencesResId The XML resource ID to inflate
* @param key The preference key of the {@link PreferenceScreen} to use as the
* root of the preference hierarchy, or {@code null} to use the root
* {@link PreferenceScreen}.
*/
public void setPreferencesFromResource(@XmlRes int preferencesResId, @Nullable String key) {
requirePreferenceManager();
final PreferenceScreen xmlRoot = mPreferenceManager.inflateFromResource(requireContext(),
preferencesResId, null);
final Preference root;
if (key != null) {
root = xmlRoot.findPreference(key);
if (!(root instanceof PreferenceScreen)) {
throw new IllegalArgumentException("Preference object with key " + key
+ " is not a PreferenceScreen");
}
} else {
root = xmlRoot;
}
setPreferenceScreen((PreferenceScreen) root);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("deprecation")
@Override
public boolean onPreferenceTreeClick(@NonNull Preference preference) {
if (preference.getFragment() != null) {
boolean handled = false;
if (getCallbackFragment() instanceof OnPreferenceStartFragmentCallback) {
handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment())
.onPreferenceStartFragment(this, preference);
}
// If the callback fragment doesn't handle OnPreferenceStartFragmentCallback, looks up
// its parent fragment in the hierarchy that implements the callback until the first
// one that returns true
Fragment callbackFragment = this;
while (!handled && callbackFragment != null) {
if (callbackFragment instanceof OnPreferenceStartFragmentCallback) {
handled = ((OnPreferenceStartFragmentCallback) callbackFragment)
.onPreferenceStartFragment(this, preference);
}
callbackFragment = callbackFragment.getParentFragment();
}
if (!handled && getContext() instanceof OnPreferenceStartFragmentCallback) {
handled = ((OnPreferenceStartFragmentCallback) getContext())
.onPreferenceStartFragment(this, preference);
}
// Check the Activity as well in case getContext was overridden to return something
// other than the Activity.
if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback) {
handled = ((OnPreferenceStartFragmentCallback) getActivity())
.onPreferenceStartFragment(this, preference);
}
if (!handled) {
Log.w(TAG,
"onPreferenceStartFragment is not implemented in the parent activity - "
+ "attempting to use a fallback implementation. You should "
+ "implement this method so that you can configure the new "
+ "fragment that will be displayed, and set a transition between "
+ "the fragments.");
final FragmentManager fragmentManager = getParentFragmentManager();
final Bundle args = preference.getExtras();
final Fragment fragment = fragmentManager.getFragmentFactory().instantiate(
requireActivity().getClassLoader(), preference.getFragment());
fragment.setArguments(args);
fragment.setTargetFragment(this, 0);
fragmentManager.beginTransaction()
// Attempt to replace this fragment in its root view - developers should
// implement onPreferenceStartFragment in their activity so that they can
// customize this behaviour and handle any transitions between fragments
.replace(((View) requireView().getParent()).getId(), fragment)
.addToBackStack(null)
.commit();
}
return true;
}
return false;
}
/**
* Called by {@link PreferenceScreen#onClick()} in order to navigate to a new screen of
* preferences. Calls
* {@link PreferenceFragmentCompat.OnPreferenceStartScreenCallback#onPreferenceStartScreen}
* if the target fragment or containing activity implements
* {@link PreferenceFragmentCompat.OnPreferenceStartScreenCallback}.
*
* @param preferenceScreen The {@link PreferenceScreen} to navigate to
*/
@Override
public void onNavigateToScreen(@NonNull PreferenceScreen preferenceScreen) {
boolean handled = false;
if (getCallbackFragment() instanceof OnPreferenceStartScreenCallback) {
handled = ((OnPreferenceStartScreenCallback) getCallbackFragment())
.onPreferenceStartScreen(this, preferenceScreen);
}
// If the callback fragment doesn't handle OnPreferenceStartScreenCallback, looks up
// its parent fragment in the hierarchy that implements the callback until the first
// one that returns true
Fragment callbackFragment = this;
while (!handled && callbackFragment != null) {
if (callbackFragment instanceof OnPreferenceStartScreenCallback) {
handled = ((OnPreferenceStartScreenCallback) callbackFragment)
.onPreferenceStartScreen(this, preferenceScreen);
}
callbackFragment = callbackFragment.getParentFragment();
}
if (!handled && getContext() instanceof OnPreferenceStartScreenCallback) {
handled = ((OnPreferenceStartScreenCallback) getContext())
.onPreferenceStartScreen(this, preferenceScreen);
}
// Check the Activity as well in case getContext was overridden to return something other
// than the Activity.
if (!handled && getActivity() instanceof OnPreferenceStartScreenCallback) {
((OnPreferenceStartScreenCallback) getActivity())
.onPreferenceStartScreen(this, preferenceScreen);
}
}
@Override
@SuppressWarnings("TypeParameterUnusedInFormals")
@Nullable
public <T extends Preference> T findPreference(@NonNull CharSequence key) {
if (mPreferenceManager == null) {
return null;
}
return mPreferenceManager.findPreference(key);
}
private void requirePreferenceManager() {
if (mPreferenceManager == null) {
throw new RuntimeException("This should be called after super.onCreate.");
}
}
private void postBindPreferences() {
if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void bindPreferences() {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
getListView().setAdapter(onCreateAdapter(preferenceScreen));
preferenceScreen.onAttached();
}
onBindPreferences();
}
private void unbindPreferences() {
getListView().setAdapter(null);
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
preferenceScreen.onDetached();
}
onUnbindPreferences();
}
/**
* Used by Settings.
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
protected void onBindPreferences() {}
/**
* Used by Settings.
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
protected void onUnbindPreferences() {}
public final RecyclerView getListView() {
return mList;
}
/**
* Creates the {@link RecyclerView} used to display the preferences.
* Subclasses may override this to return a customized {@link RecyclerView}.
*
* @param inflater The LayoutInflater object that can be used to inflate the
* {@link RecyclerView}.
* @param parent The parent {@link ViewGroup} that the RecyclerView will be attached
* to. This method should not add the view itself, but this can be
* used to generate the layout params of the view.
* @param savedInstanceState If non-null, this view is being re-constructed from a previous
* saved state as given here.
* @return A new {@link RecyclerView} object to be placed into the view hierarchy
*/
@SuppressWarnings("deprecation")
@NonNull
public RecyclerView onCreateRecyclerView(@NonNull LayoutInflater inflater,
@NonNull ViewGroup parent, @Nullable Bundle savedInstanceState) {
// If device detected is Auto, use Auto's custom layout that contains a custom ViewGroup
// wrapping a RecyclerView
if (requireContext().getPackageManager().hasSystemFeature(PackageManager
.FEATURE_AUTOMOTIVE)) {
RecyclerView recyclerView = parent.findViewById(R.id.recycler_view);
if (recyclerView != null) {
return recyclerView;
}
}
RecyclerView recyclerView = (RecyclerView) inflater
.inflate(R.layout.preference_recyclerview, parent, false);
recyclerView.setLayoutManager(onCreateLayoutManager());
recyclerView.setAccessibilityDelegateCompat(
new PreferenceRecyclerViewAccessibilityDelegate(recyclerView));
return recyclerView;
}
/**
* Called from {@link #onCreateRecyclerView} to create the {@link RecyclerView.LayoutManager}
* for the created {@link RecyclerView}.
*
* @return A new {@link RecyclerView.LayoutManager} instance
*/
@NonNull
public RecyclerView.LayoutManager onCreateLayoutManager() {
return new LinearLayoutManager(requireContext());
}
/**
* Creates the root adapter.
*
* @param preferenceScreen The {@link PreferenceScreen} object to create the adapter for
* @return An adapter that contains the preferences contained in this {@link PreferenceScreen}
*/
@NonNull
protected RecyclerView.Adapter onCreateAdapter(@NonNull PreferenceScreen preferenceScreen) {
return new PreferenceGroupAdapter(preferenceScreen);
}
/**
* Called when a preference in the tree requests to display a dialog. Subclasses should
* override this method to display custom dialogs or to handle dialogs for custom preference
* classes.
*
* @param preference The {@link Preference} object requesting the dialog
*/
@SuppressWarnings("deprecation")
@Override
public void onDisplayPreferenceDialog(@NonNull Preference preference) {
boolean handled = false;
if (getCallbackFragment() instanceof OnPreferenceDisplayDialogCallback) {
handled = ((OnPreferenceDisplayDialogCallback) getCallbackFragment())
.onPreferenceDisplayDialog(this, preference);
}
// If the callback fragment doesn't handle OnPreferenceDisplayDialogCallback, looks up
// its parent fragment in the hierarchy that implements the callback until the first
// one that returns true
Fragment callbackFragment = this;
while (!handled && callbackFragment != null) {
if (callbackFragment instanceof OnPreferenceDisplayDialogCallback) {
handled = ((OnPreferenceDisplayDialogCallback) callbackFragment)
.onPreferenceDisplayDialog(this, preference);
}
callbackFragment = callbackFragment.getParentFragment();
}
if (!handled && getContext() instanceof OnPreferenceDisplayDialogCallback) {
handled = ((OnPreferenceDisplayDialogCallback) getContext())
.onPreferenceDisplayDialog(this, preference);
}
// Check the Activity as well in case getContext was overridden to return something other
// than the Activity.
if (!handled && getActivity() instanceof OnPreferenceDisplayDialogCallback) {
handled = ((OnPreferenceDisplayDialogCallback) getActivity())
.onPreferenceDisplayDialog(this, preference);
}
if (handled) {
return;
}
// check if dialog is already showing
if (getParentFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {
return;
}
final DialogFragment f;
if (preference instanceof EditTextPreference) {
f = EditTextPreferenceDialogFragmentCompat.newInstance(preference.getKey());
} else if (preference instanceof ListPreference) {
f = ListPreferenceDialogFragmentCompat.newInstance(preference.getKey());
} else if (preference instanceof MultiSelectListPreference) {
f = MultiSelectListPreferenceDialogFragmentCompat.newInstance(preference.getKey());
} else {
throw new IllegalArgumentException(
"Cannot display dialog for an unknown Preference type: "
+ preference.getClass().getSimpleName()
+ ". Make sure to implement onPreferenceDisplayDialog() to handle "
+ "displaying a custom dialog for this Preference.");
}
f.setTargetFragment(this, 0);
f.show(getParentFragmentManager(), DIALOG_FRAGMENT_TAG);
}
/**
* A wrapper for getParentFragment which is v17+. Used by the leanback preference lib.
*
* @return The {@link Fragment} to possibly use as a callback
* @hide
*/
@Nullable
@RestrictTo(LIBRARY_GROUP_PREFIX)
public Fragment getCallbackFragment() {
return null;
}
public void scrollToPreference(@NonNull String key) {
scrollToPreferenceInternal(null, key);
}
public void scrollToPreference(@NonNull Preference preference) {
scrollToPreferenceInternal(preference, null);
}
private void scrollToPreferenceInternal(@Nullable final Preference preference,
@Nullable final String key) {
final Runnable r = new Runnable() {
@Override
public void run() {
final RecyclerView.Adapter<?> adapter = mList.getAdapter();
if (!(adapter instanceof
PreferenceGroup.PreferencePositionCallback)) {
if (adapter != null) {
throw new IllegalStateException("Adapter must implement "
+ "PreferencePositionCallback");
} else {
// Adapter was set to null, so don't scroll
return;
}
}
final int position;
if (preference != null) {
position = ((PreferenceGroup.PreferencePositionCallback) adapter)
.getPreferenceAdapterPosition(preference);
} else {
position = ((PreferenceGroup.PreferencePositionCallback) adapter)
.getPreferenceAdapterPosition(key);
}
if (position != RecyclerView.NO_POSITION) {
mList.scrollToPosition(position);
} else {
// Item not found, wait for an update and try again
adapter.registerAdapterDataObserver(
new ScrollToPreferenceObserver(adapter, mList, preference, key));
}
}
};
if (mList == null) {
mSelectPreferenceRunnable = r;
} else {
r.run();
}
}
/**
* Interface that the fragment's containing activity should implement to be able to process
* preference items that wish to switch to a specified fragment.
*/
public interface OnPreferenceStartFragmentCallback {
/**
* Called when the user has clicked on a preference that has a fragment class name
* associated with it. The implementation should instantiate and switch to an instance
* of the given fragment.
*
* @param caller The fragment requesting navigation
* @param pref The preference requesting the fragment
* @return {@code true} if the fragment creation has been handled
*/
boolean onPreferenceStartFragment(@NonNull PreferenceFragmentCompat caller,
@NonNull Preference pref);
}
/**
* Interface that the fragment's containing activity should implement to be able to process
* preference items that wish to switch to a new screen of preferences.
*/
public interface OnPreferenceStartScreenCallback {
/**
* Called when the user has clicked on a {@link PreferenceScreen} in order to navigate to
* a new screen of preferences.
*
* @param caller The fragment requesting navigation
* @param pref The preference screen to navigate to
* @return {@code true} if the screen navigation has been handled
*/
boolean onPreferenceStartScreen(@NonNull PreferenceFragmentCompat caller,
@NonNull PreferenceScreen pref);
}
/**
* Interface that the fragment's containing activity should implement to be able to process
* preference items that wish to display a dialog.
*/
public interface OnPreferenceDisplayDialogCallback {
/**
* @param caller The fragment containing the preference requesting the dialog
* @param pref The preference requesting the dialog
* @return {@code true} if the dialog creation has been handled
*/
boolean onPreferenceDisplayDialog(@NonNull PreferenceFragmentCompat caller,
@NonNull Preference pref);
}
private static class ScrollToPreferenceObserver extends RecyclerView.AdapterDataObserver {
private final RecyclerView.Adapter<?> mAdapter;
private final RecyclerView mList;
private final Preference mPreference;
private final String mKey;
ScrollToPreferenceObserver(RecyclerView.Adapter<?> adapter, RecyclerView list,
Preference preference, String key) {
mAdapter = adapter;
mList = list;
mPreference = preference;
mKey = key;
}
private void scrollToPreference() {
mAdapter.unregisterAdapterDataObserver(this);
final int position;
if (mPreference != null) {
position = ((PreferenceGroup.PreferencePositionCallback) mAdapter)
.getPreferenceAdapterPosition(mPreference);
} else {
position = ((PreferenceGroup.PreferencePositionCallback) mAdapter)
.getPreferenceAdapterPosition(mKey);
}
if (position != RecyclerView.NO_POSITION) {
mList.scrollToPosition(position);
}
}
@Override
public void onChanged() {
scrollToPreference();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
scrollToPreference();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
scrollToPreference();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
scrollToPreference();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
scrollToPreference();
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
scrollToPreference();
}
}
private class DividerDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private int mDividerHeight;
private boolean mAllowDividerAfterLastItem = true;
DividerDecoration() {}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
@NonNull RecyclerView.State state) {
if (mDivider == null) {
return;
}
final int childCount = parent.getChildCount();
final int width = parent.getWidth();
for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
final View view = parent.getChildAt(childViewIndex);
if (shouldDrawDividerBelow(view, parent)) {
int top = (int) view.getY() + view.getHeight();
mDivider.setBounds(0, top, width, top + mDividerHeight);
mDivider.draw(c);
}
}
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
if (shouldDrawDividerBelow(view, parent)) {
outRect.bottom = mDividerHeight;
}
}
private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder
&& ((PreferenceViewHolder) holder).isDividerAllowedBelow();
if (!dividerAllowedBelow) {
return false;
}
boolean nextAllowed = mAllowDividerAfterLastItem;
int index = parent.indexOfChild(view);
if (index < parent.getChildCount() - 1) {
final View nextView = parent.getChildAt(index + 1);
final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView);
nextAllowed = nextHolder instanceof PreferenceViewHolder
&& ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove();
}
return nextAllowed;
}
public void setDivider(Drawable divider) {
if (divider != null) {
mDividerHeight = divider.getIntrinsicHeight();
} else {
mDividerHeight = 0;
}
mDivider = divider;
mList.invalidateItemDecorations();
}
public void setDividerHeight(int dividerHeight) {
mDividerHeight = dividerHeight;
mList.invalidateItemDecorations();
}
public void setAllowDividerAfterLastItem(boolean allowDividerAfterLastItem) {
mAllowDividerAfterLastItem = allowDividerAfterLastItem;
}
}
}