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