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 com.android.setupwizardlib;
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.support.annotation.IntDef;
25import android.support.v4.view.ViewCompat;
26import android.support.v7.widget.RecyclerView;
27import android.view.View;
28
29import java.lang.annotation.Retention;
30import java.lang.annotation.RetentionPolicy;
31
32/**
33 * An {@link android.support.v7.widget.RecyclerView.ItemDecoration} for RecyclerView to draw
34 * dividers between items. This ItemDecoration will draw the drawable specified by
35 * {@link #setDivider(android.graphics.drawable.Drawable)} as the divider in between each item by
36 * default, and the behavior of whether the divider is shown can be customized by subclassing
37 * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}.
38 *
39 * <p>Modified from v14 PreferenceFragment.DividerDecoration.
40 */
41public class DividerItemDecoration extends RecyclerView.ItemDecoration {
42
43    /* static section */
44
45    /**
46     * An interface to be implemented by a {@link RecyclerView.ViewHolder} which controls whether
47     * dividers should be shown above and below that item.
48     */
49    public interface DividedViewHolder {
50
51        /**
52         * Returns whether divider is allowed above this item. A divider will be shown only if both
53         * items immediately above and below it allows this divider.
54         */
55        boolean isDividerAllowedAbove();
56
57        /**
58         * Returns whether divider is allowed below this item. A divider will be shown only if both
59         * items immediately above and below it allows this divider.
60         */
61        boolean isDividerAllowedBelow();
62    }
63
64    @Retention(RetentionPolicy.SOURCE)
65    @IntDef({
66            DIVIDER_CONDITION_EITHER,
67            DIVIDER_CONDITION_BOTH})
68    public @interface DividerCondition {}
69
70    public static final int DIVIDER_CONDITION_EITHER = 0;
71    public static final int DIVIDER_CONDITION_BOTH = 1;
72
73    /**
74     * @deprecated Use {@link #DividerItemDecoration(android.content.Context)}
75     */
76    @Deprecated
77    public static DividerItemDecoration getDefault(Context context) {
78        return new DividerItemDecoration(context);
79    }
80
81    /* non-static section */
82
83    private Drawable mDivider;
84    private int mDividerHeight;
85    private int mDividerIntrinsicHeight;
86    @DividerCondition
87    private int mDividerCondition;
88
89    public DividerItemDecoration() {
90    }
91
92    public DividerItemDecoration(Context context) {
93        final TypedArray a = context.obtainStyledAttributes(R.styleable.SuwDividerItemDecoration);
94        final Drawable divider = a.getDrawable(
95                R.styleable.SuwDividerItemDecoration_android_listDivider);
96        final int dividerHeight = a.getDimensionPixelSize(
97                R.styleable.SuwDividerItemDecoration_android_dividerHeight, 0);
98        @DividerCondition final int dividerCondition = a.getInt(
99                R.styleable.SuwDividerItemDecoration_suwDividerCondition,
100                DIVIDER_CONDITION_EITHER);
101        a.recycle();
102
103        setDivider(divider);
104        setDividerHeight(dividerHeight);
105        setDividerCondition(dividerCondition);
106    }
107
108    @Override
109    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
110        if (mDivider == null) {
111            return;
112        }
113        final int childCount = parent.getChildCount();
114        final int width = parent.getWidth();
115        final int dividerHeight = mDividerHeight != 0 ? mDividerHeight : mDividerIntrinsicHeight;
116        for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
117            final View view = parent.getChildAt(childViewIndex);
118            if (shouldDrawDividerBelow(view, parent)) {
119                final int top = (int) ViewCompat.getY(view) + view.getHeight();
120                mDivider.setBounds(0, top, width, top + dividerHeight);
121                mDivider.draw(c);
122            }
123        }
124    }
125
126    @Override
127    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
128            RecyclerView.State state) {
129        if (shouldDrawDividerBelow(view, parent)) {
130            outRect.bottom = mDividerHeight != 0 ? mDividerHeight : mDividerIntrinsicHeight;
131        }
132    }
133
134    private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
135        final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
136        final int index = holder.getLayoutPosition();
137        final int lastItemIndex = parent.getAdapter().getItemCount() - 1;
138        if (isDividerAllowedBelow(holder)) {
139            if (mDividerCondition == DIVIDER_CONDITION_EITHER) {
140                // Draw the divider without consulting the next item if we only
141                // need permission for either above or below.
142                return true;
143            }
144        } else if (mDividerCondition == DIVIDER_CONDITION_BOTH || index == lastItemIndex) {
145            // Don't draw if the current view holder doesn't allow drawing below
146            // and the current theme requires permission for both the item below and above.
147            // Also, if this is the last item, there is no item below to ask permission
148            // for whether to draw a divider above, so don't draw it.
149            return false;
150        }
151        // Require permission from index below to draw the divider.
152        if (index < lastItemIndex) {
153            final RecyclerView.ViewHolder nextHolder =
154                    parent.findViewHolderForLayoutPosition(index + 1);
155            if (!isDividerAllowedAbove(nextHolder)) {
156                // Don't draw if the next view holder doesn't allow drawing above
157                return false;
158            }
159        }
160        return true;
161    }
162
163    /**
164     * Whether a divider is allowed above the view holder. The allowed values will be combined
165     * according to {@link #getDividerCondition()}. The default implementation delegates to
166     * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows
167     * the divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can
168     * override this to give more information to decide whether a divider should be drawn.
169     *
170     * @return True if divider is allowed above this view holder.
171     */
172    protected boolean isDividerAllowedAbove(RecyclerView.ViewHolder viewHolder) {
173        return !(viewHolder instanceof DividedViewHolder)
174                || ((DividedViewHolder) viewHolder).isDividerAllowedAbove();
175    }
176
177    /**
178     * Whether a divider is allowed below the view holder. The allowed values will be combined
179     * according to {@link #getDividerCondition()}. The default implementation delegates to
180     * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}, or simply allows
181     * the divider if the view holder doesn't implement {@code DividedViewHolder}. Subclasses can
182     * override this to give more information to decide whether a divider should be drawn.
183     *
184     * @return True if divider is allowed below this view holder.
185     */
186    protected boolean isDividerAllowedBelow(RecyclerView.ViewHolder viewHolder) {
187        return !(viewHolder instanceof DividedViewHolder)
188                || ((DividedViewHolder) viewHolder).isDividerAllowedBelow();
189    }
190
191    /**
192     * Sets the drawable to be used as the divider.
193     */
194    public void setDivider(Drawable divider) {
195        if (divider != null) {
196            mDividerIntrinsicHeight = divider.getIntrinsicHeight();
197        } else {
198            mDividerIntrinsicHeight = 0;
199        }
200        mDivider = divider;
201    }
202
203    /**
204     * Gets the drawable currently used as the divider.
205     */
206    public Drawable getDivider() {
207        return mDivider;
208    }
209
210    /**
211     * Sets the divider height, in pixels.
212     */
213    public void setDividerHeight(int dividerHeight) {
214        mDividerHeight = dividerHeight;
215    }
216
217    /**
218     * Gets the divider height, in pixels.
219     */
220    public int getDividerHeight() {
221        return mDividerHeight;
222    }
223
224    /**
225     * Sets whether the divider needs permission from both the item view holder below
226     * and above from where the divider would draw itself or just needs permission from
227     * one or the other before drawing itself.
228     */
229    public void setDividerCondition(@DividerCondition int dividerCondition) {
230        mDividerCondition = dividerCondition;
231    }
232
233    /**
234     * Gets whether the divider needs permission from both the item view holder below
235     * and above from where the divider would draw itself or just needs permission from
236     * one or the other before drawing itself.
237     */
238    @DividerCondition
239    public int getDividerCondition() {
240        return mDividerCondition;
241    }
242}
243