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