1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package android.support.v14.preference; 18 19 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.app.DialogFragment; 22 import android.app.Fragment; 23 import android.content.Context; 24 import android.content.res.TypedArray; 25 import android.graphics.Canvas; 26 import android.graphics.Rect; 27 import android.graphics.drawable.Drawable; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.Message; 31 import android.support.annotation.NonNull; 32 import android.support.annotation.Nullable; 33 import android.support.annotation.RestrictTo; 34 import android.support.annotation.XmlRes; 35 import android.support.v4.content.res.TypedArrayUtils; 36 import android.support.v7.preference.AndroidResources; 37 import android.support.v7.preference.DialogPreference; 38 import android.support.v7.preference.EditTextPreference; 39 import android.support.v7.preference.ListPreference; 40 import android.support.v7.preference.Preference; 41 import android.support.v7.preference.PreferenceGroup; 42 import android.support.v7.preference.PreferenceGroupAdapter; 43 import android.support.v7.preference.PreferenceManager; 44 import android.support.v7.preference.PreferenceRecyclerViewAccessibilityDelegate; 45 import android.support.v7.preference.PreferenceScreen; 46 import android.support.v7.preference.PreferenceViewHolder; 47 import android.support.v7.widget.LinearLayoutManager; 48 import android.support.v7.widget.RecyclerView; 49 import android.util.TypedValue; 50 import android.view.ContextThemeWrapper; 51 import android.view.LayoutInflater; 52 import android.view.View; 53 import android.view.ViewGroup; 54 55 /** 56 * Shows a hierarchy of {@link Preference} objects as 57 * lists. These preferences will 58 * automatically save to {@link android.content.SharedPreferences} as the user interacts with 59 * them. To retrieve an instance of {@link android.content.SharedPreferences} that the 60 * preference hierarchy in this fragment will use, call 61 * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)} 62 * with a context in the same package as this fragment. 63 * <p> 64 * Furthermore, the preferences shown will follow the visual style of system 65 * preferences. It is easy to create a hierarchy of preferences (that can be 66 * shown on multiple screens) via XML. For these reasons, it is recommended to 67 * use this fragment (as a superclass) to deal with preferences in applications. 68 * <p> 69 * A {@link PreferenceScreen} object should be at the top of the preference 70 * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy 71 * denote a screen break--that is the preferences contained within subsequent 72 * {@link PreferenceScreen} should be shown on another screen. The preference 73 * framework handles this by calling {@link #onNavigateToScreen(PreferenceScreen)}. 74 * <p> 75 * The preference hierarchy can be formed in multiple ways: 76 * <li> From an XML file specifying the hierarchy 77 * <li> From different {@link android.app.Activity Activities} that each specify its own 78 * preferences in an XML file via {@link android.app.Activity} meta-data 79 * <li> From an object hierarchy rooted with {@link PreferenceScreen} 80 * <p> 81 * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The 82 * root element should be a {@link PreferenceScreen}. Subsequent elements can point 83 * to actual {@link Preference} subclasses. As mentioned above, subsequent 84 * {@link PreferenceScreen} in the hierarchy will result in the screen break. 85 * <p> 86 * To specify an object hierarchy rooted with {@link PreferenceScreen}, use 87 * {@link #setPreferenceScreen(PreferenceScreen)}. 88 * <p> 89 * As a convenience, this fragment implements a click listener for any 90 * preference in the current hierarchy, see 91 * {@link #onPreferenceTreeClick(Preference)}. 92 * 93 * <div class="special reference"> 94 * <h3>Developer Guides</h3> 95 * <p>For information about using {@code PreferenceFragment}, 96 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a> 97 * guide.</p> 98 * </div> 99 * 100 * <a name="SampleCode"></a> 101 * <h3>Sample Code</h3> 102 * 103 * <p>The following sample code shows a simple preference fragment that is 104 * populated from a resource. The resource it loads is:</p> 105 * 106 * {@sample frameworks/support/samples/SupportPreferenceDemos/res/xml/preferences.xml preferences} 107 * 108 * <p>The fragment implementation itself simply populates the preferences 109 * when created. Note that the preferences framework takes care of loading 110 * the current values out of the app preferences and writing them when changed:</p> 111 * 112 * {@sample frameworks/support/samples/SupportPreferenceDemos/src/com/example/android/supportpreference/FragmentSupportPreferences.java 113 * support_fragment} 114 * 115 * @see Preference 116 * @see PreferenceScreen 117 */ 118 public abstract class PreferenceFragment extends Fragment implements 119 PreferenceManager.OnPreferenceTreeClickListener, 120 PreferenceManager.OnDisplayPreferenceDialogListener, 121 PreferenceManager.OnNavigateToScreenListener, 122 DialogPreference.TargetFragment { 123 124 /** 125 * Fragment argument used to specify the tag of the desired root 126 * {@link android.support.v7.preference.PreferenceScreen} object. 127 */ 128 public static final String ARG_PREFERENCE_ROOT = 129 "android.support.v7.preference.PreferenceFragmentCompat.PREFERENCE_ROOT"; 130 131 private static final String PREFERENCES_TAG = "android:preferences"; 132 133 private static final String DIALOG_FRAGMENT_TAG = 134 "android.support.v14.preference.PreferenceFragment.DIALOG"; 135 136 private PreferenceManager mPreferenceManager; 137 private RecyclerView mList; 138 private boolean mHavePrefs; 139 private boolean mInitDone; 140 141 private Context mStyledContext; 142 143 private int mLayoutResId = android.support.v7.preference.R.layout.preference_list_fragment; 144 145 private final DividerDecoration mDividerDecoration = new DividerDecoration(); 146 147 private static final int MSG_BIND_PREFERENCES = 1; 148 private Handler mHandler = new Handler() { 149 @Override 150 public void handleMessage(Message msg) { 151 switch (msg.what) { 152 153 case MSG_BIND_PREFERENCES: 154 bindPreferences(); 155 break; 156 } 157 } 158 }; 159 160 final private Runnable mRequestFocus = new Runnable() { 161 @Override 162 public void run() { 163 mList.focusableViewAvailable(mList); 164 } 165 }; 166 167 private Runnable mSelectPreferenceRunnable; 168 169 /** 170 * Interface that PreferenceFragment's containing activity should 171 * implement to be able to process preference items that wish to 172 * switch to a specified fragment. 173 */ 174 public interface OnPreferenceStartFragmentCallback { 175 /** 176 * Called when the user has clicked on a Preference that has 177 * a fragment class name associated with it. The implementation 178 * should instantiate and switch to an instance of the given 179 * fragment. 180 * @param caller The fragment requesting navigation. 181 * @param pref The preference requesting the fragment. 182 * @return true if the fragment creation has been handled 183 */ onPreferenceStartFragment(PreferenceFragment caller, Preference pref)184 boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref); 185 } 186 187 /** 188 * Interface that PreferenceFragment's containing activity should 189 * implement to be able to process preference items that wish to 190 * switch to a new screen of preferences. 191 */ 192 public interface OnPreferenceStartScreenCallback { 193 /** 194 * Called when the user has clicked on a PreferenceScreen item in order to navigate to a new 195 * screen of preferences. 196 * @param caller The fragment requesting navigation. 197 * @param pref The preference screen to navigate to. 198 * @return true if the screen navigation has been handled 199 */ onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref)200 boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref); 201 } 202 203 public interface OnPreferenceDisplayDialogCallback { 204 205 /** 206 * 207 * @param caller The fragment containing the preference requesting the dialog. 208 * @param pref The preference requesting the dialog. 209 * @return true if the dialog creation has been handled. 210 */ onPreferenceDisplayDialog(@onNull PreferenceFragment caller, Preference pref)211 boolean onPreferenceDisplayDialog(@NonNull PreferenceFragment caller, Preference pref); 212 } 213 214 @Override onCreate(Bundle savedInstanceState)215 public void onCreate(Bundle savedInstanceState) { 216 super.onCreate(savedInstanceState); 217 final TypedValue tv = new TypedValue(); 218 getActivity().getTheme().resolveAttribute( 219 android.support.v7.preference.R.attr.preferenceTheme, tv, true); 220 final int theme = tv.resourceId; 221 if (theme == 0) { 222 throw new IllegalStateException("Must specify preferenceTheme in theme"); 223 } 224 mStyledContext = new ContextThemeWrapper(getActivity(), theme); 225 226 mPreferenceManager = new PreferenceManager(mStyledContext); 227 mPreferenceManager.setOnNavigateToScreenListener(this); 228 final Bundle args = getArguments(); 229 final String rootKey; 230 if (args != null) { 231 rootKey = getArguments().getString(ARG_PREFERENCE_ROOT); 232 } else { 233 rootKey = null; 234 } 235 onCreatePreferences(savedInstanceState, rootKey); 236 } 237 238 /** 239 * Called during {@link #onCreate(Bundle)} to supply the preferences for this fragment. 240 * Subclasses are expected to call {@link #setPreferenceScreen(PreferenceScreen)} either 241 * directly or via helper methods such as {@link #addPreferencesFromResource(int)}. 242 * 243 * @param savedInstanceState If the fragment is being re-created from 244 * a previous saved state, this is the state. 245 * @param rootKey If non-null, this preference fragment should be rooted at the 246 * {@link android.support.v7.preference.PreferenceScreen} with this key. 247 */ onCreatePreferences(Bundle savedInstanceState, String rootKey)248 public abstract void onCreatePreferences(Bundle savedInstanceState, String rootKey); 249 250 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)251 public View onCreateView(LayoutInflater inflater, ViewGroup container, 252 Bundle savedInstanceState) { 253 254 TypedArray a = mStyledContext.obtainStyledAttributes(null, 255 R.styleable.PreferenceFragment, 256 TypedArrayUtils.getAttr(mStyledContext, 257 android.support.v7.preference.R.attr.preferenceFragmentStyle, 258 AndroidResources.ANDROID_R_PREFERENCE_FRAGMENT_STYLE), 259 0); 260 261 mLayoutResId = a.getResourceId(R.styleable.PreferenceFragment_android_layout, mLayoutResId); 262 263 final Drawable divider = a.getDrawable(R.styleable.PreferenceFragment_android_divider); 264 final int dividerHeight = a.getDimensionPixelSize( 265 R.styleable.PreferenceFragment_android_dividerHeight, -1); 266 final boolean allowDividerAfterLastItem = a.getBoolean( 267 R.styleable.PreferenceFragment_allowDividerAfterLastItem, true); 268 a.recycle(); 269 270 // Need to theme the inflater to pick up the preferenceFragmentListStyle 271 final TypedValue tv = new TypedValue(); 272 getActivity().getTheme().resolveAttribute( 273 android.support.v7.preference.R.attr.preferenceTheme, tv, true); 274 final int theme = tv.resourceId; 275 276 final Context themedContext = new ContextThemeWrapper(inflater.getContext(), theme); 277 final LayoutInflater themedInflater = inflater.cloneInContext(themedContext); 278 279 final View view = themedInflater.inflate(mLayoutResId, container, false); 280 281 final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER); 282 if (!(rawListContainer instanceof ViewGroup)) { 283 throw new RuntimeException("Content has view with id attribute " 284 + "'android.R.id.list_container' that is not a ViewGroup class"); 285 } 286 287 final ViewGroup listContainer = (ViewGroup) rawListContainer; 288 289 final RecyclerView listView = onCreateRecyclerView(themedInflater, listContainer, 290 savedInstanceState); 291 if (listView == null) { 292 throw new RuntimeException("Could not create RecyclerView"); 293 } 294 295 mList = listView; 296 297 listView.addItemDecoration(mDividerDecoration); 298 setDivider(divider); 299 if (dividerHeight != -1) { 300 setDividerHeight(dividerHeight); 301 } 302 mDividerDecoration.setAllowDividerAfterLastItem(allowDividerAfterLastItem); 303 304 listContainer.addView(mList); 305 mHandler.post(mRequestFocus); 306 307 return view; 308 } 309 310 /** 311 * Sets the drawable that will be drawn between each item in the list. 312 * <p> 313 * <strong>Note:</strong> If the drawable does not have an intrinsic 314 * height, you should also call {@link #setDividerHeight(int)}. 315 * 316 * @param divider the drawable to use 317 * @attr ref R.styleable#PreferenceFragment_android_divider 318 */ setDivider(Drawable divider)319 public void setDivider(Drawable divider) { 320 mDividerDecoration.setDivider(divider); 321 } 322 323 /** 324 * Sets the height of the divider that will be drawn between each item in the list. Calling 325 * this will override the intrinsic height as set by {@link #setDivider(Drawable)} 326 * 327 * @param height The new height of the divider in pixels. 328 * @attr ref R.styleable#PreferenceFragment_android_dividerHeight 329 */ setDividerHeight(int height)330 public void setDividerHeight(int height) { 331 mDividerDecoration.setDividerHeight(height); 332 } 333 334 @Override onViewCreated(View view, Bundle savedInstanceState)335 public void onViewCreated(View view, Bundle savedInstanceState) { 336 super.onViewCreated(view, savedInstanceState); 337 338 if (mHavePrefs) { 339 bindPreferences(); 340 if (mSelectPreferenceRunnable != null) { 341 mSelectPreferenceRunnable.run(); 342 mSelectPreferenceRunnable = null; 343 } 344 } 345 346 mInitDone = true; 347 } 348 349 @Override onActivityCreated(Bundle savedInstanceState)350 public void onActivityCreated(Bundle savedInstanceState) { 351 super.onActivityCreated(savedInstanceState); 352 353 if (savedInstanceState != null) { 354 Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG); 355 if (container != null) { 356 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 357 if (preferenceScreen != null) { 358 preferenceScreen.restoreHierarchyState(container); 359 } 360 } 361 } 362 } 363 364 @Override onStart()365 public void onStart() { 366 super.onStart(); 367 mPreferenceManager.setOnPreferenceTreeClickListener(this); 368 mPreferenceManager.setOnDisplayPreferenceDialogListener(this); 369 } 370 371 @Override onStop()372 public void onStop() { 373 super.onStop(); 374 mPreferenceManager.setOnPreferenceTreeClickListener(null); 375 mPreferenceManager.setOnDisplayPreferenceDialogListener(null); 376 } 377 378 @Override onDestroyView()379 public void onDestroyView() { 380 mHandler.removeCallbacks(mRequestFocus); 381 mHandler.removeMessages(MSG_BIND_PREFERENCES); 382 if (mHavePrefs) { 383 unbindPreferences(); 384 } 385 mList = null; 386 super.onDestroyView(); 387 } 388 389 @Override onSaveInstanceState(Bundle outState)390 public void onSaveInstanceState(Bundle outState) { 391 super.onSaveInstanceState(outState); 392 393 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 394 if (preferenceScreen != null) { 395 Bundle container = new Bundle(); 396 preferenceScreen.saveHierarchyState(container); 397 outState.putBundle(PREFERENCES_TAG, container); 398 } 399 } 400 401 /** 402 * Returns the {@link PreferenceManager} used by this fragment. 403 * @return The {@link PreferenceManager}. 404 */ getPreferenceManager()405 public PreferenceManager getPreferenceManager() { 406 return mPreferenceManager; 407 } 408 409 /** 410 * Sets the root of the preference hierarchy that this fragment is showing. 411 * 412 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 413 */ setPreferenceScreen(PreferenceScreen preferenceScreen)414 public void setPreferenceScreen(PreferenceScreen preferenceScreen) { 415 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) { 416 onUnbindPreferences(); 417 mHavePrefs = true; 418 if (mInitDone) { 419 postBindPreferences(); 420 } 421 } 422 } 423 424 /** 425 * Gets the root of the preference hierarchy that this fragment is showing. 426 * 427 * @return The {@link PreferenceScreen} that is the root of the preference 428 * hierarchy. 429 */ getPreferenceScreen()430 public PreferenceScreen getPreferenceScreen() { 431 return mPreferenceManager.getPreferenceScreen(); 432 } 433 434 /** 435 * Inflates the given XML resource and adds the preference hierarchy to the current 436 * preference hierarchy. 437 * 438 * @param preferencesResId The XML resource ID to inflate. 439 */ addPreferencesFromResource(@mlRes int preferencesResId)440 public void addPreferencesFromResource(@XmlRes int preferencesResId) { 441 requirePreferenceManager(); 442 443 setPreferenceScreen(mPreferenceManager.inflateFromResource(mStyledContext, 444 preferencesResId, getPreferenceScreen())); 445 } 446 447 /** 448 * Inflates the given XML resource and replaces the current preference hierarchy (if any) with 449 * the preference hierarchy rooted at {@code key}. 450 * 451 * @param preferencesResId The XML resource ID to inflate. 452 * @param key The preference key of the {@link android.support.v7.preference.PreferenceScreen} 453 * to use as the root of the preference hierarchy, or null to use the root 454 * {@link android.support.v7.preference.PreferenceScreen}. 455 */ setPreferencesFromResource(@mlRes int preferencesResId, @Nullable String key)456 public void setPreferencesFromResource(@XmlRes int preferencesResId, @Nullable String key) { 457 requirePreferenceManager(); 458 459 final PreferenceScreen xmlRoot = mPreferenceManager.inflateFromResource(mStyledContext, 460 preferencesResId, null); 461 462 final Preference root; 463 if (key != null) { 464 root = xmlRoot.findPreference(key); 465 if (!(root instanceof PreferenceScreen)) { 466 throw new IllegalArgumentException("Preference object with key " + key 467 + " is not a PreferenceScreen"); 468 } 469 } else { 470 root = xmlRoot; 471 } 472 473 setPreferenceScreen((PreferenceScreen) root); 474 } 475 476 /** 477 * {@inheritDoc} 478 */ 479 @Override onPreferenceTreeClick(Preference preference)480 public boolean onPreferenceTreeClick(Preference preference) { 481 if (preference.getFragment() != null) { 482 boolean handled = false; 483 if (getCallbackFragment() instanceof OnPreferenceStartFragmentCallback) { 484 handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment()) 485 .onPreferenceStartFragment(this, preference); 486 } 487 if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback){ 488 handled = ((OnPreferenceStartFragmentCallback) getActivity()) 489 .onPreferenceStartFragment(this, preference); 490 } 491 return handled; 492 } 493 return false; 494 } 495 496 /** 497 * Called by 498 * {@link android.support.v7.preference.PreferenceScreen#onClick()} in order to navigate to a 499 * new screen of preferences. Calls 500 * {@link PreferenceFragment.OnPreferenceStartScreenCallback#onPreferenceStartScreen} 501 * if the target fragment or containing activity implements 502 * {@link PreferenceFragment.OnPreferenceStartScreenCallback}. 503 * @param preferenceScreen The {@link android.support.v7.preference.PreferenceScreen} to 504 * navigate to. 505 */ 506 @Override onNavigateToScreen(PreferenceScreen preferenceScreen)507 public void onNavigateToScreen(PreferenceScreen preferenceScreen) { 508 boolean handled = false; 509 if (getCallbackFragment() instanceof OnPreferenceStartScreenCallback) { 510 handled = ((OnPreferenceStartScreenCallback) getCallbackFragment()) 511 .onPreferenceStartScreen(this, preferenceScreen); 512 } 513 if (!handled && getActivity() instanceof OnPreferenceStartScreenCallback) { 514 ((OnPreferenceStartScreenCallback) getActivity()) 515 .onPreferenceStartScreen(this, preferenceScreen); 516 } 517 } 518 519 /** 520 * Finds a {@link Preference} based on its key. 521 * 522 * @param key The key of the preference to retrieve. 523 * @return The {@link Preference} with the key, or null. 524 * @see android.support.v7.preference.PreferenceGroup#findPreference(CharSequence) 525 */ 526 @Override findPreference(CharSequence key)527 public Preference findPreference(CharSequence key) { 528 if (mPreferenceManager == null) { 529 return null; 530 } 531 return mPreferenceManager.findPreference(key); 532 } 533 requirePreferenceManager()534 private void requirePreferenceManager() { 535 if (mPreferenceManager == null) { 536 throw new RuntimeException("This should be called after super.onCreate."); 537 } 538 } 539 postBindPreferences()540 private void postBindPreferences() { 541 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return; 542 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); 543 } 544 bindPreferences()545 private void bindPreferences() { 546 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 547 if (preferenceScreen != null) { 548 getListView().setAdapter(onCreateAdapter(preferenceScreen)); 549 preferenceScreen.onAttached(); 550 } 551 onBindPreferences(); 552 } 553 unbindPreferences()554 private void unbindPreferences() { 555 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 556 if (preferenceScreen != null) { 557 preferenceScreen.onDetached(); 558 } 559 onUnbindPreferences(); 560 } 561 562 /** @hide */ 563 @RestrictTo(LIBRARY_GROUP) onBindPreferences()564 protected void onBindPreferences() { 565 } 566 567 /** @hide */ 568 @RestrictTo(LIBRARY_GROUP) onUnbindPreferences()569 protected void onUnbindPreferences() { 570 } 571 getListView()572 public final RecyclerView getListView() { 573 return mList; 574 } 575 576 /** 577 * Creates the {@link android.support.v7.widget.RecyclerView} used to display the preferences. 578 * Subclasses may override this to return a customized 579 * {@link android.support.v7.widget.RecyclerView}. 580 * @param inflater The LayoutInflater object that can be used to inflate the 581 * {@link android.support.v7.widget.RecyclerView}. 582 * @param parent The parent {@link android.view.View} that the RecyclerView will be attached to. 583 * This method should not add the view itself, but this can be used to generate 584 * the LayoutParams of the view. 585 * @param savedInstanceState If non-null, this view is being re-constructed from a previous 586 * saved state as given here 587 * @return A new RecyclerView object to be placed into the view hierarchy 588 */ onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)589 public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, 590 Bundle savedInstanceState) { 591 RecyclerView recyclerView = (RecyclerView) inflater 592 .inflate(android.support.v7.preference.R.layout.preference_recyclerview, 593 parent, false); 594 595 recyclerView.setLayoutManager(onCreateLayoutManager()); 596 recyclerView.setAccessibilityDelegateCompat( 597 new PreferenceRecyclerViewAccessibilityDelegate(recyclerView)); 598 599 return recyclerView; 600 } 601 602 /** 603 * Called from {@link #onCreateRecyclerView} to create the 604 * {@link android.support.v7.widget.RecyclerView.LayoutManager} for the created 605 * {@link android.support.v7.widget.RecyclerView}. 606 * @return A new {@link android.support.v7.widget.RecyclerView.LayoutManager} instance. 607 */ onCreateLayoutManager()608 public RecyclerView.LayoutManager onCreateLayoutManager() { 609 return new LinearLayoutManager(getActivity()); 610 } 611 612 /** 613 * Creates the root adapter. 614 * 615 * @param preferenceScreen Preference screen object to create the adapter for. 616 * @return An adapter that contains the preferences contained in this {@link PreferenceScreen}. 617 */ onCreateAdapter(PreferenceScreen preferenceScreen)618 protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { 619 return new PreferenceGroupAdapter(preferenceScreen); 620 } 621 622 /** 623 * Called when a preference in the tree requests to display a dialog. Subclasses should 624 * override this method to display custom dialogs or to handle dialogs for custom preference 625 * classes. 626 * 627 * @param preference The Preference object requesting the dialog. 628 */ 629 @Override onDisplayPreferenceDialog(Preference preference)630 public void onDisplayPreferenceDialog(Preference preference) { 631 632 boolean handled = false; 633 if (getCallbackFragment() instanceof OnPreferenceDisplayDialogCallback) { 634 handled = ((OnPreferenceDisplayDialogCallback) getCallbackFragment()) 635 .onPreferenceDisplayDialog(this, preference); 636 } 637 if (!handled && getActivity() instanceof OnPreferenceDisplayDialogCallback) { 638 handled = ((OnPreferenceDisplayDialogCallback) getActivity()) 639 .onPreferenceDisplayDialog(this, preference); 640 } 641 642 if (handled) { 643 return; 644 } 645 646 // check if dialog is already showing 647 if (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) { 648 return; 649 } 650 651 final DialogFragment f; 652 if (preference instanceof EditTextPreference) { 653 f = EditTextPreferenceDialogFragment.newInstance(preference.getKey()); 654 } else if (preference instanceof ListPreference) { 655 f = ListPreferenceDialogFragment.newInstance(preference.getKey()); 656 } else if (preference instanceof MultiSelectListPreference) { 657 f = MultiSelectListPreferenceDialogFragment.newInstance(preference.getKey()); 658 } else { 659 throw new IllegalArgumentException("Tried to display dialog for unknown " + 660 "preference type. Did you forget to override onDisplayPreferenceDialog()?"); 661 } 662 f.setTargetFragment(this, 0); 663 f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); 664 } 665 666 /** 667 * Basically a wrapper for getParentFragment which is v17+. Used by the leanback preference lib. 668 * @return Fragment to possibly use as a callback 669 * @hide 670 */ 671 @RestrictTo(LIBRARY_GROUP) getCallbackFragment()672 public Fragment getCallbackFragment() { 673 return null; 674 } 675 scrollToPreference(final String key)676 public void scrollToPreference(final String key) { 677 scrollToPreferenceInternal(null, key); 678 } 679 scrollToPreference(final Preference preference)680 public void scrollToPreference(final Preference preference) { 681 scrollToPreferenceInternal(preference, null); 682 } 683 scrollToPreferenceInternal(final Preference preference, final String key)684 private void scrollToPreferenceInternal(final Preference preference, final String key) { 685 final Runnable r = new Runnable() { 686 @Override 687 public void run() { 688 final RecyclerView.Adapter adapter = mList.getAdapter(); 689 if (!(adapter instanceof 690 PreferenceGroup.PreferencePositionCallback)) { 691 if (adapter != null) { 692 throw new IllegalStateException("Adapter must implement " 693 + "PreferencePositionCallback"); 694 } else { 695 // Adapter was set to null, so don't scroll I guess? 696 return; 697 } 698 } 699 final int position; 700 if (preference != null) { 701 position = ((PreferenceGroup.PreferencePositionCallback) adapter) 702 .getPreferenceAdapterPosition(preference); 703 } else { 704 position = ((PreferenceGroup.PreferencePositionCallback) adapter) 705 .getPreferenceAdapterPosition(key); 706 } 707 if (position != RecyclerView.NO_POSITION) { 708 mList.scrollToPosition(position); 709 } else { 710 // Item not found, wait for an update and try again 711 adapter.registerAdapterDataObserver( 712 new ScrollToPreferenceObserver(adapter, mList, preference, key)); 713 } 714 } 715 }; 716 if (mList == null) { 717 mSelectPreferenceRunnable = r; 718 } else { 719 r.run(); 720 } 721 } 722 723 private static class ScrollToPreferenceObserver extends RecyclerView.AdapterDataObserver { 724 private final RecyclerView.Adapter mAdapter; 725 private final RecyclerView mList; 726 private final Preference mPreference; 727 private final String mKey; 728 ScrollToPreferenceObserver(RecyclerView.Adapter adapter, RecyclerView list, Preference preference, String key)729 public ScrollToPreferenceObserver(RecyclerView.Adapter adapter, RecyclerView list, 730 Preference preference, String key) { 731 mAdapter = adapter; 732 mList = list; 733 mPreference = preference; 734 mKey = key; 735 } 736 scrollToPreference()737 private void scrollToPreference() { 738 mAdapter.unregisterAdapterDataObserver(this); 739 final int position; 740 if (mPreference != null) { 741 position = ((PreferenceGroup.PreferencePositionCallback) mAdapter) 742 .getPreferenceAdapterPosition(mPreference); 743 } else { 744 position = ((PreferenceGroup.PreferencePositionCallback) mAdapter) 745 .getPreferenceAdapterPosition(mKey); 746 } 747 if (position != RecyclerView.NO_POSITION) { 748 mList.scrollToPosition(position); 749 } 750 } 751 752 @Override onChanged()753 public void onChanged() { 754 scrollToPreference(); 755 } 756 757 @Override onItemRangeChanged(int positionStart, int itemCount)758 public void onItemRangeChanged(int positionStart, int itemCount) { 759 scrollToPreference(); 760 } 761 762 @Override onItemRangeChanged(int positionStart, int itemCount, Object payload)763 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 764 scrollToPreference(); 765 } 766 767 @Override onItemRangeInserted(int positionStart, int itemCount)768 public void onItemRangeInserted(int positionStart, int itemCount) { 769 scrollToPreference(); 770 } 771 772 @Override onItemRangeRemoved(int positionStart, int itemCount)773 public void onItemRangeRemoved(int positionStart, int itemCount) { 774 scrollToPreference(); 775 } 776 777 @Override onItemRangeMoved(int fromPosition, int toPosition, int itemCount)778 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 779 scrollToPreference(); 780 } 781 } 782 783 private class DividerDecoration extends RecyclerView.ItemDecoration { 784 785 private Drawable mDivider; 786 private int mDividerHeight; 787 private boolean mAllowDividerAfterLastItem = true; 788 789 @Override onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)790 public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 791 if (mDivider == null) { 792 return; 793 } 794 final int childCount = parent.getChildCount(); 795 final int width = parent.getWidth(); 796 for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { 797 final View view = parent.getChildAt(childViewIndex); 798 if (shouldDrawDividerBelow(view, parent)) { 799 int top = (int) view.getY() + view.getHeight(); 800 mDivider.setBounds(0, top, width, top + mDividerHeight); 801 mDivider.draw(c); 802 } 803 } 804 } 805 806 @Override getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)807 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 808 RecyclerView.State state) { 809 if (shouldDrawDividerBelow(view, parent)) { 810 outRect.bottom = mDividerHeight; 811 } 812 } 813 shouldDrawDividerBelow(View view, RecyclerView parent)814 private boolean shouldDrawDividerBelow(View view, RecyclerView parent) { 815 final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); 816 final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder 817 && ((PreferenceViewHolder) holder).isDividerAllowedBelow(); 818 if (!dividerAllowedBelow) { 819 return false; 820 } 821 boolean nextAllowed = mAllowDividerAfterLastItem; 822 int index = parent.indexOfChild(view); 823 if (index < parent.getChildCount() - 1) { 824 final View nextView = parent.getChildAt(index + 1); 825 final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView); 826 nextAllowed = nextHolder instanceof PreferenceViewHolder 827 && ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove(); 828 } 829 return nextAllowed; 830 } 831 setDivider(Drawable divider)832 public void setDivider(Drawable divider) { 833 if (divider != null) { 834 mDividerHeight = divider.getIntrinsicHeight(); 835 } else { 836 mDividerHeight = 0; 837 } 838 mDivider = divider; 839 mList.invalidateItemDecorations(); 840 } 841 setDividerHeight(int dividerHeight)842 public void setDividerHeight(int dividerHeight) { 843 mDividerHeight = dividerHeight; 844 mList.invalidateItemDecorations(); 845 } 846 setAllowDividerAfterLastItem(boolean allowDividerAfterLastItem)847 public void setAllowDividerAfterLastItem(boolean allowDividerAfterLastItem) { 848 mAllowDividerAfterLastItem = allowDividerAfterLastItem; 849 } 850 } 851 } 852