165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane/*
265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Copyright (C) 2014 The Android Open Source Project
365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *
465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Licensed under the Apache License, Version 2.0 (the "License");
565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * you may not use this file except in compliance with the License.
665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * You may obtain a copy of the License at
765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *
865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *      http://www.apache.org/licenses/LICENSE-2.0
965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *
1065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Unless required by applicable law or agreed to in writing, software
1165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * distributed under the License is distributed on an "AS IS" BASIS,
1265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * See the License for the specific language governing permissions and
1465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * limitations under the License.
1565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane */
1665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
1765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lanepackage com.android.tv.settings.widget;
1865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
1965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.Context;
2065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.res.TypedArray;
2165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.graphics.Canvas;
2265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.graphics.Matrix;
2365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.graphics.Rect;
2465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.graphics.RectF;
2565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.graphics.drawable.Drawable;
2665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.graphics.drawable.ShapeDrawable;
2765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.graphics.drawable.shapes.RectShape;
2865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.util.AttributeSet;
2965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.View;
306e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantlerimport android.view.ViewDebug.ExportedProperty;
3165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.ViewGroup;
3265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.ViewParent;
3365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.widget.FrameLayout;
3465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.widget.ImageView;
3565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
3665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport com.android.tv.settings.R;
3765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
3865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport java.util.ArrayList;
3965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
4065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane/**
4165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Allows a drawable to be added for shadowing views in this layout. The shadows
4265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * will automatically be sized to wrap their corresponding view. The default
4365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * drawable to use can be set in xml by defining the namespace and then using
4465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * defaultShadow="@drawable/reference"
4565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * <p>
4665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * In code views can then have Shadows added to them via
4765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * {@link #addShadowView(View)} to use the default drawable or with
4865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * {@link #addShadowView(View, Drawable)}.
4965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane */
5065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lanepublic class FrameLayoutWithShadows extends FrameLayout {
5165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
5265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int MAX_RECYCLE = 12;
5365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
5465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    static class ShadowView extends View {
5565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
5665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private View shadowedView;
5765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private Drawable mDrawableBottom;
5865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private float mAlpha = 1f;
5965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
6065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ShadowView(Context context) {
6165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super(context);
6265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            setWillNotDraw(false);
6365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
6465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
6565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void init() {
6665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            shadowedView = null;
6765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mDrawableBottom = null;
6865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
6965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
7065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
7165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void setBackground(Drawable background) {
7265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super.setBackground(background);
7365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (background != null) {
7465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // framework adds a callback on background to trigger a repaint
7565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // when call Drawable.setAlpha(),  this is not desired when we override
7665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // setAlpha();  if we call Drawable.setAlpha() in the overriden
7765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // setAlpha(),  it will trigger another repaint event thus cause system
7865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // never stop rendering.
7965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                background.setCallback(null);
8065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                background.setAlpha((int)(255 * mAlpha));
8165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
8265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
8365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
8465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
8565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void setAlpha(float alpha) {
8665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mAlpha != alpha) {
8765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mAlpha = alpha;
8865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                Drawable d = getBackground();
8965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int alphaMulitplied = (int)(alpha * 255);
9065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (d != null) {
9165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    d.setAlpha(alphaMulitplied);
9265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
9365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mDrawableBottom != null) {
9465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mDrawableBottom.setAlpha(alphaMulitplied);
9565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
9665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                invalidate();
9765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
9865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
9965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
10065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
10165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @ExportedProperty(category = "drawing")
10265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public float getAlpha() {
10365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return mAlpha;
10465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
10565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
10665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
10765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        protected boolean onSetAlpha(int alpha) {
10865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return true;
10965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
11065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
11165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void setDrawableBottom(Drawable drawable) {
11265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mDrawableBottom = drawable;
11365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mAlpha >= 0) {
11465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mDrawableBottom.setAlpha((int)(255 * mAlpha));
11565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
11665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            invalidate();
11765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
11865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
11965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
12065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        protected void onDraw(Canvas canvas) {
12165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // draw background 9 patch
12265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super.onDraw(canvas);
12365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // draw bottom
12465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mDrawableBottom != null) {
12565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mDrawableBottom.setBounds(getPaddingLeft(), getHeight() - getPaddingBottom(),
12665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()
12765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        + mDrawableBottom.getIntrinsicHeight());
12865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mDrawableBottom.draw(canvas);
12965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
13065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
13165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
13265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
1336e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private final Rect rect = new Rect();
1346e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private final RectF rectf = new RectF();
13565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mShadowResourceId;
13665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mBottomResourceId;
13765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private float mShadowsAlpha = 1f;
1386e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private final ArrayList<ShadowView> mRecycleBin = new ArrayList<>(MAX_RECYCLE);
13965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
14065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public FrameLayoutWithShadows(Context context) {
14165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        this(context, null);
14265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
14365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
14465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public FrameLayoutWithShadows(Context context, AttributeSet attrs) {
14565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        this(context, attrs, 0);
14665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
14765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
14865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public FrameLayoutWithShadows(Context context, AttributeSet attrs, int defStyle) {
14965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        super(context, attrs, defStyle);
15065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        initFromAttributes(context, attrs);
15165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
15265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
15365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
15465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    protected void onLayout(boolean changed, int l, int t, int r, int b) {
15565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        super.onLayout(changed, l, t, r, b);
15665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        layoutShadows();
15765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
15865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
15965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void initFromAttributes(Context context, AttributeSet attrs) {
16065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (attrs == null) {
16165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
16265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
16365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FrameLayoutWithShadows);
16465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
16565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setDefaultShadowResourceId(a.getResourceId(
16665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                R.styleable.FrameLayoutWithShadows_defaultShadow, 0));
16765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setDrawableBottomResourceId(a.getResourceId(
16865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                R.styleable.FrameLayoutWithShadows_drawableBottom, 0));
16965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
17065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        a.recycle();
17165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
17265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
17365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setDefaultShadowResourceId(int id) {
17465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mShadowResourceId = id;
17565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
17665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
17765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getDefaultShadowResourceId() {
17865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mShadowResourceId;
17965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
18065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
18165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setDrawableBottomResourceId(int id) {
18265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mBottomResourceId = id;
18365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
18465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
18565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getDrawableBottomResourceId() {
18665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mBottomResourceId;
18765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
18865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
18965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setShadowsAlpha(float alpha) {
19065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mShadowsAlpha = alpha;
19165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = getChildCount() - 1; i >= 0; i--) {
19265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View shadow = getChildAt(i);
19365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (shadow instanceof ShadowView) {
19465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                shadow.setAlpha(alpha);
19565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
19665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
19765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
19865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
19965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
20065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * prune shadow views whose related view was detached from FrameLayoutWithShadows
20165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
20265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void prune() {
20365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (getWindowToken() ==null) {
20465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
20565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
20665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = getChildCount() - 1; i >= 0; i--) {
20765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View shadow = getChildAt(i);
20865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (shadow instanceof ShadowView) {
20965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                ShadowView shadowView = (ShadowView) shadow;
21065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                View view = shadowView.shadowedView;
21165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (this != findParentShadowsView(view)) {
21265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    view.setTag(R.id.ShadowView, null);
21365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    shadowView.shadowedView = null;
21465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    removeView(shadowView);
21565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    addToRecycleBin(shadowView);
21665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
21765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
21865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
21965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
22065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
22165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
22265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Perform a layout of the shadow views. This is done as part of the layout
22365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * pass for the view but may also be triggered manually if the borders of a
22465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * child view has changed.
22565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
22665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void layoutShadows() {
22765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        prune();
22865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = getChildCount() - 1; i >= 0; i--) {
22965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View shadow = getChildAt(i);
23065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (!(shadow instanceof ShadowView)) {
23165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                continue;
23265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
23365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            ShadowView shadowView = (ShadowView) shadow;
23465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View view = shadowView.shadowedView;
23565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (view != null) {
23665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (this != findParentShadowsView(view)) {
23765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    continue;
23865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
23965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                boolean isImageMatrix = false;
24065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (view instanceof ImageView) {
24165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    // For ImageView, we get the draw bounds of the image drawable,
24265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    // which could be smaller than the imageView depending on ScaleType.
24365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    Matrix matrix = ((ImageView) view).getImageMatrix();
24465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    Drawable drawable = ((ImageView) view).getDrawable();
24565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (drawable != null) {
24665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        isImageMatrix = true;
24765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        rect.set(drawable.getBounds());
24865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        rectf.set(rect);
24965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        matrix.mapRect(rectf);
25065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        rectf.offset(view.getPaddingLeft(), view.getPaddingTop());
25165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        rectf.intersect(view.getPaddingLeft(), view.getPaddingTop(),
25265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                view.getWidth() - view.getPaddingLeft() - view.getPaddingRight(),
25365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                view.getHeight() - view.getPaddingTop() - view.getPaddingBottom());
25465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        rectf.left -= shadow.getPaddingLeft();
25565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        rectf.top -= shadow.getPaddingTop();
25665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        rectf.right += shadow.getPaddingRight();
25765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        rectf.bottom += shadow.getPaddingBottom();
25865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        rect.left = (int) (rectf.left + 0.5f);
25965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        rect.top = (int) (rectf.top + 0.5f);
26065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        rect.right = (int) (rectf.right + 0.5f);
26165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        rect.bottom = (int) (rectf.bottom + 0.5f);
26265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
26365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
26465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (!isImageMatrix){
26565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    rect.left = view.getPaddingLeft() - shadow.getPaddingLeft();
26665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    rect.top = view.getPaddingTop() - shadow.getPaddingTop();
26765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    rect.right = view.getWidth() + view.getPaddingRight()
26865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            + shadow.getPaddingRight();
26965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    rect.bottom = view.getHeight() + view.getPaddingBottom()
27065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            + shadow.getPaddingBottom();
27165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
27265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                offsetDescendantRectToMyCoords(view, rect);
27365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                shadow.layout(rect.left, rect.top, rect.right, rect.bottom);
27465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
27565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
27665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
27765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
27865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
27965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Add a shadow view to FrameLayoutWithShadows. This will use the drawable
28065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * specified for the shadow view and will also handle clean-up of any
28165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * previous shadow set for this view.
28265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
28365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public View addShadowView(View view, Drawable shadow) {
28465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ShadowView shadowView = (ShadowView) view.getTag(R.id.ShadowView);
28565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (shadowView == null) {
28665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            shadowView = getFromRecycleBin();
28765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (shadowView == null) {
28865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                shadowView = new ShadowView(getContext());
28965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                shadowView.setLayoutParams(new LayoutParams(0, 0));
29065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
29165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            view.setTag(R.id.ShadowView, shadowView);
29265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            shadowView.shadowedView = view;
29365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            addView(shadowView, 0);
29465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
29565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        shadow.mutate();
29665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        shadowView.setAlpha(mShadowsAlpha);
29765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        shadowView.setBackground(shadow);
29865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mBottomResourceId != 0) {
2996e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler            Drawable d = getContext().getDrawable(mBottomResourceId);
30065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            shadowView.setDrawableBottom(d.mutate());
30165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
30265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return shadowView;
30365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
30465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
30565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
30665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Add a shadow view using the default shadow. This will also handle
30765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * clean-up of any previous shadow set for this view.
30865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
30965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public View addShadowView(View view) {
3106e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        final Drawable shadow;
31165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mShadowResourceId != 0) {
3126e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler            shadow = getContext().getDrawable(mShadowResourceId);
31365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
31465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return null;
31565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
31665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return addShadowView(view, shadow);
31765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
31865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
31965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
32065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Get the shadow associated with the given view. Returns null if the view
32165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * does not have a shadow.
32265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
32365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static View getShadowView(View view) {
32465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View shadowView = (View) view.getTag(R.id.ShadowView);
32565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (shadowView != null) {
32665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return shadowView;
32765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
32865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return null;
32965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
33065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
33165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setShadowViewUnderline(View shadowView, int underlineColor, int heightInPx) {
33265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ShapeDrawable drawable = new ShapeDrawable();
33365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        drawable.setShape(new RectShape());
33465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        drawable.setIntrinsicHeight(heightInPx);
33565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        drawable.getPaint().setColor(underlineColor);
33665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ((ShadowView) shadowView).setDrawableBottom(drawable);
33765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
33865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
33965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setShadowViewUnderline(View shadowView, Drawable drawable) {
34065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ((ShadowView) shadowView).setDrawableBottom(drawable);
34165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
34265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
34365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
34465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Makes the shadow associated with the given view draw above other views.
34565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Subsequent calls to this or changes to the z-order may move the shadow
34665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * back down in the z-order.
34765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
34865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void bringViewShadowToTop(View view) {
34965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View shadowView = (View) view.getTag(R.id.ShadowView);
35065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (shadowView == null) {
35165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
35265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
35365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int index = indexOfChild(shadowView);
35465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (index < 0) {
35565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // not found
35665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
35765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
35865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int lastIndex = getChildCount() - 1;
35965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (lastIndex == index) {
36065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // already last one
36165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
36265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
36365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View lastShadowView = getChildAt(lastIndex);
36465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (!(lastShadowView instanceof ShadowView)) {
36565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            removeView(shadowView);
36665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            addView(shadowView);
36765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
36865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            removeView(lastShadowView);
36965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            removeView(shadowView);
37065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            addView(lastShadowView, 0);
37165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            addView(shadowView);
37265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
37365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
37465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
37565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
37665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Utility function to remove the shadow associated with the given view.
37765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
37865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static void removeShadowView(View view) {
37965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ShadowView shadowView = (ShadowView) view.getTag(R.id.ShadowView);
38065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (shadowView != null) {
38165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            view.setTag(R.id.ShadowView, null);
38265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            shadowView.shadowedView = null;
38365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (shadowView.getRootView() != null) {
38465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                ViewParent parent = shadowView.getParent();
38565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (parent instanceof ViewGroup) {
38665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    ((ViewGroup) parent).removeView(shadowView);
38765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (parent instanceof FrameLayoutWithShadows) {
38865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        ((FrameLayoutWithShadows) parent).addToRecycleBin(shadowView);
38965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
39065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
39165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
39265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
39365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
39465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
39565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void addToRecycleBin(ShadowView shadowView) {
39665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mRecycleBin.size() < MAX_RECYCLE) {
39765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mRecycleBin.add(shadowView);
39865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
39965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
40065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
40165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public ShadowView getFromRecycleBin() {
40265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int size = mRecycleBin.size();
40365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (size > 0) {
40465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            ShadowView view = mRecycleBin.remove(size - 1);
40565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            view.init();
40665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
40765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return null;
40865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
40965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
41065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
41165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Sets the visibility of the shadow associated with the given view. This
41265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * should be called when the view's visibility changes to keep the shadow's
41365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * visibility in sync.
41465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
41565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void setShadowVisibility(View view, int visibility) {
41665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        View shadowView = (View) view.getTag(R.id.ShadowView);
41765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (shadowView != null) {
41865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            shadowView.setVisibility(visibility);
41965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
42065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
42165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
42265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
42365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
42465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Finds the first parent of this view that is a FrameLayoutWithShadows and
42565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * returns that or null if there is none.
42665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
42765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static FrameLayoutWithShadows findParentShadowsView(View view) {
42865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        ViewParent nextView = view.getParent();
42965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        while (nextView != null && !(nextView instanceof FrameLayoutWithShadows)) {
43065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            nextView = nextView.getParent();
43165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
43265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return (FrameLayoutWithShadows) nextView;
43365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
43465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane}
435