1/*
2 * Copyright (C) 2013 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.bitmap.drawable;
18
19import android.animation.ValueAnimator;
20import android.animation.ValueAnimator.AnimatorUpdateListener;
21import android.graphics.Canvas;
22import android.graphics.ColorFilter;
23import android.graphics.Paint;
24import android.graphics.Rect;
25import android.graphics.drawable.Drawable;
26
27import com.android.bitmap.drawable.ExtendedBitmapDrawable.ExtendedOptions;
28
29/**
30 * A drawable that wraps another drawable and places it in the center of this space. This drawable
31 * allows a background color for the "tile", and has a fade-out transition when
32 * {@link #setVisible(boolean, boolean)} indicates that it is no longer visible.
33 */
34public class TileDrawable extends Drawable implements Drawable.Callback {
35
36    private final ExtendedOptions mOpts;
37    private final Paint mPaint = new Paint();
38    private final Drawable mInner;
39    private final int mInnerWidth;
40    private final int mInnerHeight;
41
42    protected final ValueAnimator mFadeOutAnimator;
43
44    public TileDrawable(Drawable inner, int innerWidth, int innerHeight, int fadeOutDurationMs,
45            ExtendedOptions opts) {
46        mOpts = opts;
47        mInner = inner != null ? inner.mutate() : null;
48        mInnerWidth = innerWidth;
49        mInnerHeight = innerHeight;
50        if (inner != null) {
51            mInner.setCallback(this);
52        }
53
54        mFadeOutAnimator = ValueAnimator.ofInt(255, 0)
55                .setDuration(fadeOutDurationMs);
56        mFadeOutAnimator.addUpdateListener(new AnimatorUpdateListener() {
57            @Override
58            public void onAnimationUpdate(ValueAnimator animation) {
59                setAlpha((Integer) animation.getAnimatedValue());
60            }
61        });
62
63        reset();
64    }
65
66    public void reset() {
67        setAlpha(0);
68        setVisible(false);
69    }
70
71    public Drawable getInnerDrawable() {
72        return mInner;
73    }
74
75    @Override
76    protected void onBoundsChange(Rect bounds) {
77        super.onBoundsChange(bounds);
78
79        if (mInner == null) {
80            return;
81        }
82
83        if (bounds.isEmpty()) {
84            mInner.setBounds(0, 0, 0, 0);
85        } else {
86            final int l = bounds.left + (bounds.width() / 2) - (mInnerWidth / 2);
87            final int t = bounds.top + (bounds.height() / 2) - (mInnerHeight / 2);
88            mInner.setBounds(l, t, l + mInnerWidth, t + mInnerHeight);
89        }
90    }
91
92    @Override
93    public void draw(Canvas canvas) {
94        if (!isVisible() && mPaint.getAlpha() == 0) {
95            return;
96        }
97        final int alpha = mPaint.getAlpha();
98        mPaint.setColor(mOpts.backgroundColor);
99        mPaint.setAlpha(alpha);
100        canvas.drawRect(getBounds(), mPaint);
101        if (mInner != null) mInner.draw(canvas);
102    }
103
104    @Override
105    public void setAlpha(int alpha) {
106        final int old = mPaint.getAlpha();
107        mPaint.setAlpha(alpha);
108        setInnerAlpha(alpha);
109        if (alpha != old) {
110            invalidateSelf();
111        }
112    }
113
114    @Override
115    public void setColorFilter(ColorFilter cf) {
116        mPaint.setColorFilter(cf);
117        if (mInner != null) mInner.setColorFilter(cf);
118    }
119
120    @Override
121    public int getOpacity() {
122        return 0;
123    }
124
125    protected int getCurrentAlpha() {
126        return mPaint.getAlpha();
127    }
128
129    public boolean setVisible(boolean visible) {
130        return setVisible(visible, true /* dontcare */);
131    }
132
133    @Override
134    public boolean setVisible(boolean visible, boolean restart) {
135        if (mInner != null) mInner.setVisible(visible, restart);
136        final boolean changed = super.setVisible(visible, restart);
137        if (changed) {
138            if (isVisible()) {
139                // pop in (no-op)
140                // the transition will still be smooth if the previous state's layer fades out
141                mFadeOutAnimator.cancel();
142                setAlpha(255);
143            } else {
144                // fade out
145                if (mPaint.getAlpha() == 255) {
146                    mFadeOutAnimator.start();
147                }
148            }
149        }
150        return changed;
151    }
152
153    @Override
154    protected boolean onLevelChange(int level) {
155        if (mInner != null)
156            return mInner.setLevel(level);
157        else {
158            return super.onLevelChange(level);
159        }
160    }
161
162    /**
163     * Changes the alpha on just the inner wrapped drawable.
164     */
165    public void setInnerAlpha(int alpha) {
166        if (mInner != null) mInner.setAlpha(alpha);
167    }
168
169    @Override
170    public void invalidateDrawable(Drawable who) {
171        invalidateSelf();
172    }
173
174    @Override
175    public void scheduleDrawable(Drawable who, Runnable what, long when) {
176        scheduleSelf(what, when);
177    }
178
179    @Override
180    public void unscheduleDrawable(Drawable who, Runnable what) {
181        unscheduleSelf(what);
182    }
183}
184