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