1 /* 2 * Copyright 2018 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 18 package androidx.recyclerview.widget; 19 20 import android.annotation.SuppressLint; 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.graphics.Canvas; 24 import android.graphics.Rect; 25 import android.graphics.drawable.Drawable; 26 import android.util.Log; 27 import android.view.View; 28 import android.widget.LinearLayout; 29 30 import org.jspecify.annotations.NonNull; 31 import org.jspecify.annotations.Nullable; 32 33 /** 34 * DividerItemDecoration is a {@link RecyclerView.ItemDecoration} that can be used as a divider 35 * between items of a {@link LinearLayoutManager}. It supports both {@link #HORIZONTAL} and 36 * {@link #VERTICAL} orientations. 37 * 38 * <pre> 39 * mDividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), 40 * mLayoutManager.getOrientation()); 41 * recyclerView.addItemDecoration(mDividerItemDecoration); 42 * </pre> 43 */ 44 public class DividerItemDecoration extends RecyclerView.ItemDecoration { 45 public static final int HORIZONTAL = LinearLayout.HORIZONTAL; 46 public static final int VERTICAL = LinearLayout.VERTICAL; 47 48 private static final String TAG = "DividerItem"; 49 private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; 50 51 private Drawable mDivider; 52 53 /** 54 * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}. 55 */ 56 private int mOrientation; 57 58 private final Rect mBounds = new Rect(); 59 60 /** 61 * Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a 62 * {@link LinearLayoutManager}. 63 * 64 * @param context Current context, it will be used to access resources. 65 * @param orientation Divider orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}. 66 */ 67 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly DividerItemDecoration(Context context, int orientation)68 public DividerItemDecoration(Context context, int orientation) { 69 final TypedArray a = context.obtainStyledAttributes(ATTRS); 70 mDivider = a.getDrawable(0); 71 if (mDivider == null) { 72 Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this " 73 + "DividerItemDecoration. Please set that attribute all call setDrawable()"); 74 } 75 a.recycle(); 76 setOrientation(orientation); 77 } 78 79 /** 80 * Sets the orientation for this divider. This should be called if 81 * {@link RecyclerView.LayoutManager} changes orientation. 82 * 83 * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} 84 */ setOrientation(int orientation)85 public void setOrientation(int orientation) { 86 if (orientation != HORIZONTAL && orientation != VERTICAL) { 87 throw new IllegalArgumentException( 88 "Invalid orientation. It should be either HORIZONTAL or VERTICAL"); 89 } 90 mOrientation = orientation; 91 } 92 93 /** 94 * Sets the {@link Drawable} for this divider. 95 * 96 * @param drawable Drawable that should be used as a divider. 97 */ setDrawable(@onNull Drawable drawable)98 public void setDrawable(@NonNull Drawable drawable) { 99 if (drawable == null) { 100 throw new IllegalArgumentException("Drawable cannot be null."); 101 } 102 mDivider = drawable; 103 } 104 105 /** 106 * @return the {@link Drawable} for this divider. 107 */ getDrawable()108 public @Nullable Drawable getDrawable() { 109 return mDivider; 110 } 111 112 @Override 113 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)114 public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 115 if (parent.getLayoutManager() == null || mDivider == null) { 116 return; 117 } 118 if (mOrientation == VERTICAL) { 119 drawVertical(c, parent); 120 } else { 121 drawHorizontal(c, parent); 122 } 123 } 124 drawVertical(Canvas canvas, RecyclerView parent)125 private void drawVertical(Canvas canvas, RecyclerView parent) { 126 canvas.save(); 127 final int left; 128 final int right; 129 //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides. 130 if (parent.getClipToPadding()) { 131 left = parent.getPaddingLeft(); 132 right = parent.getWidth() - parent.getPaddingRight(); 133 canvas.clipRect(left, parent.getPaddingTop(), right, 134 parent.getHeight() - parent.getPaddingBottom()); 135 } else { 136 left = 0; 137 right = parent.getWidth(); 138 } 139 140 final int childCount = parent.getChildCount(); 141 for (int i = 0; i < childCount; i++) { 142 final View child = parent.getChildAt(i); 143 parent.getDecoratedBoundsWithMargins(child, mBounds); 144 final int bottom = mBounds.bottom + Math.round(child.getTranslationY()); 145 final int top = bottom - mDivider.getIntrinsicHeight(); 146 mDivider.setBounds(left, top, right, bottom); 147 mDivider.draw(canvas); 148 } 149 canvas.restore(); 150 } 151 drawHorizontal(Canvas canvas, RecyclerView parent)152 private void drawHorizontal(Canvas canvas, RecyclerView parent) { 153 canvas.save(); 154 final int top; 155 final int bottom; 156 //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides. 157 if (parent.getClipToPadding()) { 158 top = parent.getPaddingTop(); 159 bottom = parent.getHeight() - parent.getPaddingBottom(); 160 canvas.clipRect(parent.getPaddingLeft(), top, 161 parent.getWidth() - parent.getPaddingRight(), bottom); 162 } else { 163 top = 0; 164 bottom = parent.getHeight(); 165 } 166 167 final int childCount = parent.getChildCount(); 168 for (int i = 0; i < childCount; i++) { 169 final View child = parent.getChildAt(i); 170 parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); 171 final int right = mBounds.right + Math.round(child.getTranslationX()); 172 final int left = right - mDivider.getIntrinsicWidth(); 173 mDivider.setBounds(left, top, right, bottom); 174 mDivider.draw(canvas); 175 } 176 canvas.restore(); 177 } 178 179 @Override 180 @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)181 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 182 RecyclerView.State state) { 183 if (mDivider == null) { 184 outRect.set(0, 0, 0, 0); 185 return; 186 } 187 if (mOrientation == VERTICAL) { 188 outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); 189 } else { 190 outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); 191 } 192 } 193 } 194