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