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