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