1/*
2 * Copyright (C) 2014 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 */
16package android.support.v7.widget;
17
18import android.content.res.ColorStateList;
19import android.graphics.Canvas;
20import android.graphics.Color;
21import android.graphics.ColorFilter;
22import android.graphics.Outline;
23import android.graphics.Paint;
24import android.graphics.PixelFormat;
25import android.graphics.PorterDuff;
26import android.graphics.PorterDuffColorFilter;
27import android.graphics.Rect;
28import android.graphics.RectF;
29import android.graphics.drawable.Drawable;
30import android.support.annotation.Nullable;
31
32import static android.support.v7.widget.RoundRectDrawableWithShadow.calculateVerticalPadding;
33import static android.support.v7.widget.RoundRectDrawableWithShadow.calculateHorizontalPadding;
34
35/**
36 * Very simple drawable that draws a rounded rectangle background with arbitrary corners and also
37 * reports proper outline for Lollipop.
38 * <p>
39 * Simpler and uses less resources compared to GradientDrawable or ShapeDrawable.
40 */
41class RoundRectDrawable extends Drawable {
42    private float mRadius;
43    private final Paint mPaint;
44    private final RectF mBoundsF;
45    private final Rect mBoundsI;
46    private float mPadding;
47    private boolean mInsetForPadding = false;
48    private boolean mInsetForRadius = true;
49
50    private ColorStateList mBackground;
51    private PorterDuffColorFilter mTintFilter;
52    private ColorStateList mTint;
53    private PorterDuff.Mode mTintMode = PorterDuff.Mode.SRC_IN;
54
55    public RoundRectDrawable(ColorStateList backgroundColor, float radius) {
56        mRadius = radius;
57        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
58        setBackground(backgroundColor);
59
60        mBoundsF = new RectF();
61        mBoundsI = new Rect();
62    }
63
64    private void setBackground(ColorStateList color) {
65        mBackground = (color == null) ?  ColorStateList.valueOf(Color.TRANSPARENT) : color;
66        mPaint.setColor(mBackground.getColorForState(getState(), mBackground.getDefaultColor()));
67    }
68
69    void setPadding(float padding, boolean insetForPadding, boolean insetForRadius) {
70        if (padding == mPadding && mInsetForPadding == insetForPadding &&
71                mInsetForRadius == insetForRadius) {
72            return;
73        }
74        mPadding = padding;
75        mInsetForPadding = insetForPadding;
76        mInsetForRadius = insetForRadius;
77        updateBounds(null);
78        invalidateSelf();
79    }
80
81    float getPadding() {
82        return mPadding;
83    }
84
85    @Override
86    public void draw(Canvas canvas) {
87        final Paint paint = mPaint;
88
89        final boolean clearColorFilter;
90        if (mTintFilter != null && paint.getColorFilter() == null) {
91            paint.setColorFilter(mTintFilter);
92            clearColorFilter = true;
93        } else {
94            clearColorFilter = false;
95        }
96
97        canvas.drawRoundRect(mBoundsF, mRadius, mRadius, paint);
98
99        if (clearColorFilter) {
100            paint.setColorFilter(null);
101        }
102    }
103
104    private void updateBounds(Rect bounds) {
105        if (bounds == null) {
106            bounds = getBounds();
107        }
108        mBoundsF.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
109        mBoundsI.set(bounds);
110        if (mInsetForPadding) {
111            float vInset = calculateVerticalPadding(mPadding, mRadius, mInsetForRadius);
112            float hInset = calculateHorizontalPadding(mPadding, mRadius, mInsetForRadius);
113            mBoundsI.inset((int) Math.ceil(hInset), (int) Math.ceil(vInset));
114            // to make sure they have same bounds.
115            mBoundsF.set(mBoundsI);
116        }
117    }
118
119    @Override
120    protected void onBoundsChange(Rect bounds) {
121        super.onBoundsChange(bounds);
122        updateBounds(bounds);
123    }
124
125    @Override
126    public void getOutline(Outline outline) {
127        outline.setRoundRect(mBoundsI, mRadius);
128    }
129
130    void setRadius(float radius) {
131        if (radius == mRadius) {
132            return;
133        }
134        mRadius = radius;
135        updateBounds(null);
136        invalidateSelf();
137    }
138
139    @Override
140    public void setAlpha(int alpha) {
141        mPaint.setAlpha(alpha);
142    }
143
144    @Override
145    public void setColorFilter(ColorFilter cf) {
146        mPaint.setColorFilter(cf);
147    }
148
149    @Override
150    public int getOpacity() {
151        return PixelFormat.TRANSLUCENT;
152    }
153
154    public float getRadius() {
155        return mRadius;
156    }
157
158    public void setColor(@Nullable ColorStateList color) {
159        setBackground(color);
160        invalidateSelf();
161    }
162
163    public ColorStateList getColor() {
164        return mBackground;
165    }
166
167    @Override
168    public void setTintList(ColorStateList tint) {
169        mTint = tint;
170        mTintFilter = createTintFilter(mTint, mTintMode);
171        invalidateSelf();
172    }
173
174    @Override
175    public void setTintMode(PorterDuff.Mode tintMode) {
176        mTintMode = tintMode;
177        mTintFilter = createTintFilter(mTint, mTintMode);
178        invalidateSelf();
179    }
180
181    @Override
182    protected boolean onStateChange(int[] stateSet) {
183        final int newColor = mBackground.getColorForState(stateSet, mBackground.getDefaultColor());
184        final boolean colorChanged = newColor != mPaint.getColor();
185        if (colorChanged) {
186            mPaint.setColor(newColor);
187        }
188        if (mTint != null && mTintMode != null) {
189            mTintFilter = createTintFilter(mTint, mTintMode);
190            return true;
191        }
192        return colorChanged;
193    }
194
195    @Override
196    public boolean isStateful() {
197        return (mTint != null && mTint.isStateful())
198                || (mBackground != null && mBackground.isStateful()) || super.isStateful();
199    }
200
201    /**
202     * Ensures the tint filter is consistent with the current tint color and
203     * mode.
204     */
205    private PorterDuffColorFilter createTintFilter(ColorStateList tint, PorterDuff.Mode tintMode) {
206        if (tint == null || tintMode == null) {
207            return null;
208        }
209        final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
210        return new PorterDuffColorFilter(color, tintMode);
211    }
212}
213