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