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
18package android.support.v7.widget;
19
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.support.annotation.NonNull;
26import android.util.Log;
27import android.view.View;
28import 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 */
41public 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     */
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     */
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     */
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
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
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
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
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