PreferenceFragment.java revision c39d9c75590eca86a5e7e32a8824ba04a0d42e9b
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 17package android.support.v14.preference; 18 19import android.app.DialogFragment; 20import android.app.Fragment; 21import android.content.Context; 22import android.content.res.TypedArray; 23import android.graphics.Canvas; 24import android.graphics.Rect; 25import android.graphics.drawable.Drawable; 26import android.os.Bundle; 27import android.os.Handler; 28import android.os.Message; 29import android.support.annotation.Nullable; 30import android.support.annotation.RestrictTo; 31import android.support.annotation.XmlRes; 32import android.support.v4.content.res.TypedArrayUtils; 33import android.support.v4.view.ViewCompat; 34import android.support.v7.preference.AndroidResources; 35import android.support.v7.preference.DialogPreference; 36import android.support.v7.preference.EditTextPreference; 37import android.support.v7.preference.ListPreference; 38import android.support.v7.preference.Preference; 39import android.support.v7.preference.PreferenceGroup; 40import android.support.v7.preference.PreferenceGroupAdapter; 41import android.support.v7.preference.PreferenceManager; 42import android.support.v7.preference.PreferenceRecyclerViewAccessibilityDelegate; 43import android.support.v7.preference.PreferenceScreen; 44import android.support.v7.preference.PreferenceViewHolder; 45import android.support.v7.widget.LinearLayoutManager; 46import android.support.v7.widget.RecyclerView; 47import android.util.TypedValue; 48import android.view.ContextThemeWrapper; 49import android.view.LayoutInflater; 50import android.view.View; 51import android.view.ViewGroup; 52 53import static android.support.annotation.RestrictTo.Scope.GROUP_ID; 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 */ 118public 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 public void run() { 162 mList.focusableViewAvailable(mList); 163 } 164 }; 165 166 private Runnable mSelectPreferenceRunnable; 167 168 /** 169 * Interface that PreferenceFragment's containing activity should 170 * implement to be able to process preference items that wish to 171 * switch to a specified fragment. 172 */ 173 public interface OnPreferenceStartFragmentCallback { 174 /** 175 * Called when the user has clicked on a Preference that has 176 * a fragment class name associated with it. The implementation 177 * should instantiate and switch to an instance of the given 178 * fragment. 179 * @param caller The fragment requesting navigation. 180 * @param pref The preference requesting the fragment. 181 * @return true if the fragment creation has been handled 182 */ 183 boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref); 184 } 185 186 /** 187 * Interface that PreferenceFragment's containing activity should 188 * implement to be able to process preference items that wish to 189 * switch to a new screen of preferences. 190 */ 191 public interface OnPreferenceStartScreenCallback { 192 /** 193 * Called when the user has clicked on a PreferenceScreen item in order to navigate to a new 194 * screen of preferences. 195 * @param caller The fragment requesting navigation. 196 * @param pref The preference screen to navigate to. 197 * @return true if the screen navigation has been handled 198 */ 199 boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref); 200 } 201 202 public interface OnPreferenceDisplayDialogCallback { 203 204 /** 205 * 206 * @param caller The fragment containing the preference requesting the dialog. 207 * @param pref The preference requesting the dialog. 208 * @return true if the dialog creation has been handled. 209 */ 210 boolean onPreferenceDisplayDialog(PreferenceFragment caller, Preference pref); 211 } 212 213 @Override 214 public void onCreate(Bundle savedInstanceState) { 215 super.onCreate(savedInstanceState); 216 final TypedValue tv = new TypedValue(); 217 getActivity().getTheme().resolveAttribute( 218 android.support.v7.preference.R.attr.preferenceTheme, tv, true); 219 final int theme = tv.resourceId; 220 if (theme <= 0) { 221 throw new IllegalStateException("Must specify preferenceTheme in theme"); 222 } 223 mStyledContext = new ContextThemeWrapper(getActivity(), theme); 224 225 mPreferenceManager = new PreferenceManager(mStyledContext); 226 mPreferenceManager.setOnNavigateToScreenListener(this); 227 final Bundle args = getArguments(); 228 final String rootKey; 229 if (args != null) { 230 rootKey = getArguments().getString(ARG_PREFERENCE_ROOT); 231 } else { 232 rootKey = null; 233 } 234 onCreatePreferences(savedInstanceState, rootKey); 235 } 236 237 /** 238 * Called during {@link #onCreate(Bundle)} to supply the preferences for this fragment. 239 * Subclasses are expected to call {@link #setPreferenceScreen(PreferenceScreen)} either 240 * directly or via helper methods such as {@link #addPreferencesFromResource(int)}. 241 * 242 * @param savedInstanceState If the fragment is being re-created from 243 * a previous saved state, this is the state. 244 * @param rootKey If non-null, this preference fragment should be rooted at the 245 * {@link android.support.v7.preference.PreferenceScreen} with this key. 246 */ 247 public abstract void onCreatePreferences(Bundle savedInstanceState, String rootKey); 248 249 @Override 250 public View onCreateView(LayoutInflater inflater, ViewGroup container, 251 Bundle savedInstanceState) { 252 253 TypedArray a = mStyledContext.obtainStyledAttributes(null, 254 R.styleable.PreferenceFragment, 255 TypedArrayUtils.getAttr(mStyledContext, 256 android.support.v7.preference.R.attr.preferenceFragmentStyle, 257 AndroidResources.ANDROID_R_PREFERENCE_FRAGMENT_STYLE), 258 0); 259 260 mLayoutResId = a.getResourceId(R.styleable.PreferenceFragment_android_layout, mLayoutResId); 261 262 final Drawable divider = a.getDrawable(R.styleable.PreferenceFragment_android_divider); 263 final int dividerHeight = a.getDimensionPixelSize( 264 R.styleable.PreferenceFragment_android_dividerHeight, -1); 265 266 a.recycle(); 267 268 // Need to theme the inflater to pick up the preferenceFragmentListStyle 269 final TypedValue tv = new TypedValue(); 270 getActivity().getTheme().resolveAttribute( 271 android.support.v7.preference.R.attr.preferenceTheme, tv, true); 272 final int theme = tv.resourceId; 273 274 final Context themedContext = new ContextThemeWrapper(inflater.getContext(), theme); 275 final LayoutInflater themedInflater = inflater.cloneInContext(themedContext); 276 277 final View view = themedInflater.inflate(mLayoutResId, container, false); 278 279 final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER); 280 if (!(rawListContainer instanceof ViewGroup)) { 281 throw new RuntimeException("Content has view with id attribute " 282 + "'android.R.id.list_container' that is not a ViewGroup class"); 283 } 284 285 final ViewGroup listContainer = (ViewGroup) rawListContainer; 286 287 final RecyclerView listView = onCreateRecyclerView(themedInflater, listContainer, 288 savedInstanceState); 289 if (listView == null) { 290 throw new RuntimeException("Could not create RecyclerView"); 291 } 292 293 mList = listView; 294 295 listView.addItemDecoration(mDividerDecoration); 296 setDivider(divider); 297 if (dividerHeight != -1) { 298 setDividerHeight(dividerHeight); 299 } 300 301 listContainer.addView(mList); 302 mHandler.post(mRequestFocus); 303 304 return view; 305 } 306 307 /** 308 * Sets the drawable that will be drawn between each item in the list. 309 * <p> 310 * <strong>Note:</strong> If the drawable does not have an intrinsic 311 * height, you should also call {@link #setDividerHeight(int)}. 312 * 313 * @param divider the drawable to use 314 * @attr ref R.styleable#PreferenceFragment_android_divider 315 */ 316 public void setDivider(Drawable divider) { 317 mDividerDecoration.setDivider(divider); 318 } 319 320 /** 321 * Sets the height of the divider that will be drawn between each item in the list. Calling 322 * this will override the intrinsic height as set by {@link #setDivider(Drawable)} 323 * 324 * @param height The new height of the divider in pixels. 325 * @attr ref R.styleable#PreferenceFragment_android_dividerHeight 326 */ 327 public void setDividerHeight(int height) { 328 mDividerDecoration.setDividerHeight(height); 329 } 330 331 @Override 332 public void onViewCreated(View view, Bundle savedInstanceState) { 333 super.onViewCreated(view, savedInstanceState); 334 335 if (mHavePrefs) { 336 bindPreferences(); 337 if (mSelectPreferenceRunnable != null) { 338 mSelectPreferenceRunnable.run(); 339 mSelectPreferenceRunnable = null; 340 } 341 } 342 343 mInitDone = true; 344 } 345 346 @Override 347 public void onActivityCreated(Bundle savedInstanceState) { 348 super.onActivityCreated(savedInstanceState); 349 350 if (savedInstanceState != null) { 351 Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG); 352 if (container != null) { 353 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 354 if (preferenceScreen != null) { 355 preferenceScreen.restoreHierarchyState(container); 356 } 357 } 358 } 359 } 360 361 @Override 362 public void onStart() { 363 super.onStart(); 364 mPreferenceManager.setOnPreferenceTreeClickListener(this); 365 mPreferenceManager.setOnDisplayPreferenceDialogListener(this); 366 } 367 368 @Override 369 public void onStop() { 370 super.onStop(); 371 mPreferenceManager.setOnPreferenceTreeClickListener(null); 372 mPreferenceManager.setOnDisplayPreferenceDialogListener(null); 373 } 374 375 @Override 376 public void onDestroyView() { 377 mHandler.removeCallbacks(mRequestFocus); 378 mHandler.removeMessages(MSG_BIND_PREFERENCES); 379 if (mHavePrefs) { 380 unbindPreferences(); 381 } 382 mList = null; 383 super.onDestroyView(); 384 } 385 386 @Override 387 public void onSaveInstanceState(Bundle outState) { 388 super.onSaveInstanceState(outState); 389 390 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 391 if (preferenceScreen != null) { 392 Bundle container = new Bundle(); 393 preferenceScreen.saveHierarchyState(container); 394 outState.putBundle(PREFERENCES_TAG, container); 395 } 396 } 397 398 /** 399 * Returns the {@link PreferenceManager} used by this fragment. 400 * @return The {@link PreferenceManager}. 401 */ 402 public PreferenceManager getPreferenceManager() { 403 return mPreferenceManager; 404 } 405 406 /** 407 * Sets the root of the preference hierarchy that this fragment is showing. 408 * 409 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 410 */ 411 public void setPreferenceScreen(PreferenceScreen preferenceScreen) { 412 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) { 413 onUnbindPreferences(); 414 mHavePrefs = true; 415 if (mInitDone) { 416 postBindPreferences(); 417 } 418 } 419 } 420 421 /** 422 * Gets the root of the preference hierarchy that this fragment is showing. 423 * 424 * @return The {@link PreferenceScreen} that is the root of the preference 425 * hierarchy. 426 */ 427 public PreferenceScreen getPreferenceScreen() { 428 return mPreferenceManager.getPreferenceScreen(); 429 } 430 431 /** 432 * Inflates the given XML resource and adds the preference hierarchy to the current 433 * preference hierarchy. 434 * 435 * @param preferencesResId The XML resource ID to inflate. 436 */ 437 public void addPreferencesFromResource(@XmlRes int preferencesResId) { 438 requirePreferenceManager(); 439 440 setPreferenceScreen(mPreferenceManager.inflateFromResource(mStyledContext, 441 preferencesResId, getPreferenceScreen())); 442 } 443 444 /** 445 * Inflates the given XML resource and replaces the current preference hierarchy (if any) with 446 * the preference hierarchy rooted at {@code key}. 447 * 448 * @param preferencesResId The XML resource ID to inflate. 449 * @param key The preference key of the {@link android.support.v7.preference.PreferenceScreen} 450 * to use as the root of the preference hierarchy, or null to use the root 451 * {@link android.support.v7.preference.PreferenceScreen}. 452 */ 453 public void setPreferencesFromResource(@XmlRes int preferencesResId, @Nullable String key) { 454 requirePreferenceManager(); 455 456 final PreferenceScreen xmlRoot = mPreferenceManager.inflateFromResource(mStyledContext, 457 preferencesResId, null); 458 459 final Preference root; 460 if (key != null) { 461 root = xmlRoot.findPreference(key); 462 if (!(root instanceof PreferenceScreen)) { 463 throw new IllegalArgumentException("Preference object with key " + key 464 + " is not a PreferenceScreen"); 465 } 466 } else { 467 root = xmlRoot; 468 } 469 470 setPreferenceScreen((PreferenceScreen) root); 471 } 472 473 /** 474 * {@inheritDoc} 475 */ 476 public boolean onPreferenceTreeClick(Preference preference) { 477 if (preference.getFragment() != null) { 478 boolean handled = false; 479 if (getCallbackFragment() instanceof OnPreferenceStartFragmentCallback) { 480 handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment()) 481 .onPreferenceStartFragment(this, preference); 482 } 483 if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback){ 484 handled = ((OnPreferenceStartFragmentCallback) getActivity()) 485 .onPreferenceStartFragment(this, preference); 486 } 487 return handled; 488 } 489 return false; 490 } 491 492 /** 493 * Called by 494 * {@link android.support.v7.preference.PreferenceScreen#onClick()} in order to navigate to a 495 * new screen of preferences. Calls 496 * {@link PreferenceFragment.OnPreferenceStartScreenCallback#onPreferenceStartScreen} 497 * if the target fragment or containing activity implements 498 * {@link PreferenceFragment.OnPreferenceStartScreenCallback}. 499 * @param preferenceScreen The {@link android.support.v7.preference.PreferenceScreen} to 500 * navigate to. 501 */ 502 @Override 503 public void onNavigateToScreen(PreferenceScreen preferenceScreen) { 504 boolean handled = false; 505 if (getCallbackFragment() instanceof OnPreferenceStartScreenCallback) { 506 handled = ((OnPreferenceStartScreenCallback) getCallbackFragment()) 507 .onPreferenceStartScreen(this, preferenceScreen); 508 } 509 if (!handled && getActivity() instanceof OnPreferenceStartScreenCallback) { 510 ((OnPreferenceStartScreenCallback) getActivity()) 511 .onPreferenceStartScreen(this, preferenceScreen); 512 } 513 } 514 515 /** 516 * Finds a {@link Preference} based on its key. 517 * 518 * @param key The key of the preference to retrieve. 519 * @return The {@link Preference} with the key, or null. 520 * @see android.support.v7.preference.PreferenceGroup#findPreference(CharSequence) 521 */ 522 public Preference findPreference(CharSequence key) { 523 if (mPreferenceManager == null) { 524 return null; 525 } 526 return mPreferenceManager.findPreference(key); 527 } 528 529 private void requirePreferenceManager() { 530 if (mPreferenceManager == null) { 531 throw new RuntimeException("This should be called after super.onCreate."); 532 } 533 } 534 535 private void postBindPreferences() { 536 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return; 537 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); 538 } 539 540 private void bindPreferences() { 541 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 542 if (preferenceScreen != null) { 543 getListView().setAdapter(onCreateAdapter(preferenceScreen)); 544 preferenceScreen.onAttached(); 545 } 546 onBindPreferences(); 547 } 548 549 private void unbindPreferences() { 550 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 551 if (preferenceScreen != null) { 552 preferenceScreen.onDetached(); 553 } 554 onUnbindPreferences(); 555 } 556 557 /** @hide */ 558 @RestrictTo(GROUP_ID) 559 protected void onBindPreferences() { 560 } 561 562 /** @hide */ 563 @RestrictTo(GROUP_ID) 564 protected void onUnbindPreferences() { 565 } 566 567 public final RecyclerView getListView() { 568 return mList; 569 } 570 571 /** 572 * Creates the {@link android.support.v7.widget.RecyclerView} used to display the preferences. 573 * Subclasses may override this to return a customized 574 * {@link android.support.v7.widget.RecyclerView}. 575 * @param inflater The LayoutInflater object that can be used to inflate the 576 * {@link android.support.v7.widget.RecyclerView}. 577 * @param parent The parent {@link android.view.View} that the RecyclerView will be attached to. 578 * This method should not add the view itself, but this can be used to generate 579 * the LayoutParams of the view. 580 * @param savedInstanceState If non-null, this view is being re-constructed from a previous 581 * saved state as given here 582 * @return A new RecyclerView object to be placed into the view hierarchy 583 */ 584 public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, 585 Bundle savedInstanceState) { 586 RecyclerView recyclerView = (RecyclerView) inflater 587 .inflate(android.support.v7.preference.R.layout.preference_recyclerview, 588 parent, false); 589 590 recyclerView.setLayoutManager(onCreateLayoutManager()); 591 recyclerView.setAccessibilityDelegateCompat( 592 new PreferenceRecyclerViewAccessibilityDelegate(recyclerView)); 593 594 return recyclerView; 595 } 596 597 /** 598 * Called from {@link #onCreateRecyclerView} to create the 599 * {@link android.support.v7.widget.RecyclerView.LayoutManager} for the created 600 * {@link android.support.v7.widget.RecyclerView}. 601 * @return A new {@link android.support.v7.widget.RecyclerView.LayoutManager} instance. 602 */ 603 public RecyclerView.LayoutManager onCreateLayoutManager() { 604 return new LinearLayoutManager(getActivity()); 605 } 606 607 /** 608 * Creates the root adapter. 609 * 610 * @param preferenceScreen Preference screen object to create the adapter for. 611 * @return An adapter that contains the preferences contained in this {@link PreferenceScreen}. 612 */ 613 protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { 614 return new PreferenceGroupAdapter(preferenceScreen); 615 } 616 617 /** 618 * Called when a preference in the tree requests to display a dialog. Subclasses should 619 * override this method to display custom dialogs or to handle dialogs for custom preference 620 * classes. 621 * 622 * @param preference The Preference object requesting the dialog. 623 */ 624 @Override 625 public void onDisplayPreferenceDialog(Preference preference) { 626 627 boolean handled = false; 628 if (getCallbackFragment() instanceof OnPreferenceDisplayDialogCallback) { 629 handled = ((OnPreferenceDisplayDialogCallback) getCallbackFragment()) 630 .onPreferenceDisplayDialog(this, preference); 631 } 632 if (!handled && getActivity() instanceof OnPreferenceDisplayDialogCallback) { 633 handled = ((OnPreferenceDisplayDialogCallback) getActivity()) 634 .onPreferenceDisplayDialog(this, preference); 635 } 636 637 if (handled) { 638 return; 639 } 640 641 // check if dialog is already showing 642 if (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) { 643 return; 644 } 645 646 final DialogFragment f; 647 if (preference instanceof EditTextPreference) { 648 f = EditTextPreferenceDialogFragment.newInstance(preference.getKey()); 649 } else if (preference instanceof ListPreference) { 650 f = ListPreferenceDialogFragment.newInstance(preference.getKey()); 651 } else if (preference instanceof MultiSelectListPreference) { 652 f = MultiSelectListPreferenceDialogFragment.newInstance(preference.getKey()); 653 } else { 654 throw new IllegalArgumentException("Tried to display dialog for unknown " + 655 "preference type. Did you forget to override onDisplayPreferenceDialog()?"); 656 } 657 f.setTargetFragment(this, 0); 658 f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); 659 } 660 661 /** 662 * Basically a wrapper for getParentFragment which is v17+. Used by the leanback preference lib. 663 * @return Fragment to possibly use as a callback 664 * @hide 665 */ 666 @RestrictTo(GROUP_ID) 667 public Fragment getCallbackFragment() { 668 return null; 669 } 670 671 public void scrollToPreference(final String key) { 672 scrollToPreferenceInternal(null, key); 673 } 674 675 public void scrollToPreference(final Preference preference) { 676 scrollToPreferenceInternal(preference, null); 677 } 678 679 private void scrollToPreferenceInternal(final Preference preference, final String key) { 680 final Runnable r = new Runnable() { 681 @Override 682 public void run() { 683 final RecyclerView.Adapter adapter = mList.getAdapter(); 684 if (!(adapter instanceof 685 PreferenceGroup.PreferencePositionCallback)) { 686 if (adapter != null) { 687 throw new IllegalStateException("Adapter must implement " 688 + "PreferencePositionCallback"); 689 } else { 690 // Adapter was set to null, so don't scroll I guess? 691 return; 692 } 693 } 694 final int position; 695 if (preference != null) { 696 position = ((PreferenceGroup.PreferencePositionCallback) adapter) 697 .getPreferenceAdapterPosition(preference); 698 } else { 699 position = ((PreferenceGroup.PreferencePositionCallback) adapter) 700 .getPreferenceAdapterPosition(key); 701 } 702 if (position != RecyclerView.NO_POSITION) { 703 mList.scrollToPosition(position); 704 } else { 705 // Item not found, wait for an update and try again 706 adapter.registerAdapterDataObserver( 707 new ScrollToPreferenceObserver(adapter, mList, preference, key)); 708 } 709 } 710 }; 711 if (mList == null) { 712 mSelectPreferenceRunnable = r; 713 } else { 714 r.run(); 715 } 716 } 717 718 private static class ScrollToPreferenceObserver extends RecyclerView.AdapterDataObserver { 719 private final RecyclerView.Adapter mAdapter; 720 private final RecyclerView mList; 721 private final Preference mPreference; 722 private final String mKey; 723 724 public ScrollToPreferenceObserver(RecyclerView.Adapter adapter, RecyclerView list, 725 Preference preference, String key) { 726 mAdapter = adapter; 727 mList = list; 728 mPreference = preference; 729 mKey = key; 730 } 731 732 private void scrollToPreference() { 733 mAdapter.unregisterAdapterDataObserver(this); 734 final int position; 735 if (mPreference != null) { 736 position = ((PreferenceGroup.PreferencePositionCallback) mAdapter) 737 .getPreferenceAdapterPosition(mPreference); 738 } else { 739 position = ((PreferenceGroup.PreferencePositionCallback) mAdapter) 740 .getPreferenceAdapterPosition(mKey); 741 } 742 if (position != RecyclerView.NO_POSITION) { 743 mList.scrollToPosition(position); 744 } 745 } 746 747 @Override 748 public void onChanged() { 749 scrollToPreference(); 750 } 751 752 @Override 753 public void onItemRangeChanged(int positionStart, int itemCount) { 754 scrollToPreference(); 755 } 756 757 @Override 758 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 759 scrollToPreference(); 760 } 761 762 @Override 763 public void onItemRangeInserted(int positionStart, int itemCount) { 764 scrollToPreference(); 765 } 766 767 @Override 768 public void onItemRangeRemoved(int positionStart, int itemCount) { 769 scrollToPreference(); 770 } 771 772 @Override 773 public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 774 scrollToPreference(); 775 } 776 } 777 778 private class DividerDecoration extends RecyclerView.ItemDecoration { 779 780 private Drawable mDivider; 781 private int mDividerHeight; 782 783 @Override 784 public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 785 if (mDivider == null) { 786 return; 787 } 788 final int childCount = parent.getChildCount(); 789 final int width = parent.getWidth(); 790 for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { 791 final View view = parent.getChildAt(childViewIndex); 792 if (shouldDrawDividerBelow(view, parent)) { 793 int top = (int) ViewCompat.getY(view) + view.getHeight(); 794 mDivider.setBounds(0, top, width, top + mDividerHeight); 795 mDivider.draw(c); 796 } 797 } 798 } 799 800 @Override 801 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 802 RecyclerView.State state) { 803 if (shouldDrawDividerBelow(view, parent)) { 804 outRect.bottom = mDividerHeight; 805 } 806 } 807 808 private boolean shouldDrawDividerBelow(View view, RecyclerView parent) { 809 final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); 810 final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder 811 && ((PreferenceViewHolder) holder).isDividerAllowedBelow(); 812 if (!dividerAllowedBelow) { 813 return false; 814 } 815 boolean nextAllowed = true; 816 int index = parent.indexOfChild(view); 817 if (index < parent.getChildCount() - 1) { 818 final View nextView = parent.getChildAt(index + 1); 819 final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView); 820 nextAllowed = nextHolder instanceof PreferenceViewHolder 821 && ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove(); 822 } 823 return nextAllowed; 824 } 825 826 public void setDivider(Drawable divider) { 827 if (divider != null) { 828 mDividerHeight = divider.getIntrinsicHeight(); 829 } else { 830 mDividerHeight = 0; 831 } 832 mDivider = divider; 833 mList.invalidateItemDecorations(); 834 } 835 836 public void setDividerHeight(int dividerHeight) { 837 mDividerHeight = dividerHeight; 838 mList.invalidateItemDecorations(); 839 } 840 } 841} 842