10744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen/*
20744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * Copyright (C) 2013 The Android Open Source Project
30744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen *
40744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * Licensed under the Apache License, Version 2.0 (the "License");
50744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * you may not use this file except in compliance with the License.
60744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * You may obtain a copy of the License at
70744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen *
80744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen *      http://www.apache.org/licenses/LICENSE-2.0
90744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen *
100744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * Unless required by applicable law or agreed to in writing, software
110744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * distributed under the License is distributed on an "AS IS" BASIS,
120744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * See the License for the specific language governing permissions and
140744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * limitations under the License.
150744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen */
160744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
170744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissenpackage com.android.camera;
180744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
1918b22b5963e8871e539edf669d96862eec72bb5dSpike Spragueimport android.animation.Animator;
2018b22b5963e8871e539edf669d96862eec72bb5dSpike Spragueimport android.animation.AnimatorListenerAdapter;
213c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Spragueimport android.animation.AnimatorSet;
2218b22b5963e8871e539edf669d96862eec72bb5dSpike Spragueimport android.animation.ValueAnimator;
230744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissenimport android.content.Context;
240744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissenimport android.content.res.TypedArray;
2518b22b5963e8871e539edf669d96862eec72bb5dSpike Spragueimport android.graphics.Bitmap;
2618b22b5963e8871e539edf669d96862eec72bb5dSpike Spragueimport android.graphics.Canvas;
2718b22b5963e8871e539edf669d96862eec72bb5dSpike Spragueimport android.graphics.Matrix;
2866a3e6ceb306833355acb740711ccb5c53576e0aSpike Spragueimport android.graphics.drawable.Drawable;
293c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Spragueimport android.os.AsyncTask;
300744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissenimport android.util.AttributeSet;
310744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissenimport android.view.View;
3218b22b5963e8871e539edf669d96862eec72bb5dSpike Spragueimport android.widget.ImageButton;
3318b22b5963e8871e539edf669d96862eec72bb5dSpike Spragueimport android.widget.ImageView;
340744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
3518b22b5963e8871e539edf669d96862eec72bb5dSpike Spragueimport com.android.camera.util.Gusterpolator;
360744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissenimport com.android.camera2.R;
370744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
380744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen/*
390744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * A toggle button that supports two or more states with images rendererd on top
400744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * for each state.
410744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * The button is initialized in an XML layout file with an array reference of
420744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * image ids (e.g. imageIds="@array/camera_flashmode_icons").
430744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * Each image in the referenced array represents a single integer state.
440744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * Every time the user touches the button it gets set to next state in line,
450744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * with the corresponding image drawn onto the face of the button.
460744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen * State wraps back to 0 on user touch when button is already at n-1 state.
470744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen */
480744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissenpublic class MultiToggleImageButton extends ImageButton {
490744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    /*
5018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague     * Listener interface for button state changes.
510744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     */
520744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    public interface OnStateChangeListener {
530744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        /*
540744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen         * @param view the MultiToggleImageButton that received the touch event
550744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen         * @param state the new state the button is in
560744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen         */
570744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        public abstract void stateChanged(View view, int state);
580744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    }
590744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
6018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    public static final int ANIM_DIRECTION_VERTICAL = 0;
6118b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    public static final int ANIM_DIRECTION_HORIZONTAL = 1;
6218b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
633c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague    private static final int ANIM_DURATION_MS = 250;
6418b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    private static final int UNSET = -1;
6518b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
660744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    private OnStateChangeListener mOnStateChangeListener;
6718b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    private int mState = UNSET;
680744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    private int[] mImageIds;
6979718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague    private int[] mDescIds;
7066a3e6ceb306833355acb740711ccb5c53576e0aSpike Sprague    private int mLevel;
7118b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    private boolean mClickEnabled = true;
7218b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    private int mParentSize;
7318b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    private int mAnimDirection;
743c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague    private Matrix mMatrix = new Matrix();
753c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague    private ValueAnimator mAnimator;
760744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
770744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    public MultiToggleImageButton(Context context) {
780744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        super(context);
790744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        init();
800744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    }
810744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
820744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    public MultiToggleImageButton(Context context, AttributeSet attrs) {
830744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        super(context, attrs);
840744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        init();
850744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        parseAttributes(context, attrs);
860744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        setState(0);
870744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    }
880744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
890744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    public MultiToggleImageButton(Context context, AttributeSet attrs, int defStyle) {
900744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        super(context, attrs, defStyle);
910744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        init();
920744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        parseAttributes(context, attrs);
930744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        setState(0);
940744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    }
950744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
960744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    /*
970744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     * Set the state change listener.
980744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     *
990744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     * @param onStateChangeListener the listener to set
1000744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     */
1010744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    public void setOnStateChangeListener(OnStateChangeListener onStateChangeListener) {
1020744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        mOnStateChangeListener = onStateChangeListener;
1030744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    }
1040744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
1050744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    /*
1060744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     * Get the current button state.
1070744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     *
1080744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     */
1090744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    public int getState() {
1100744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        return mState;
1110744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    }
1120744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
1130744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    /*
1140744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     * Set the current button state, thus causing the state change listener to
1150744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     * get called.
1160744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     *
1170744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     * @param state the desired state
1180744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     */
1190744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    public void setState(int state) {
1200744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        setState(state, true);
1210744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    }
1220744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
1230744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    /*
1240744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     * Set the current button state.
1250744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     *
1260744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     * @param state the desired state
1270744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     * @param callListener should the state change listener be called?
1280744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen     */
12918b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    public void setState(final int state, final boolean callListener) {
1303c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague        setStateAnimatedInternal(state, callListener);
131c91c8d273b8884468dfd66a5b82526dc8245934eAlan Newberger    }
132c91c8d273b8884468dfd66a5b82526dc8245934eAlan Newberger
133c91c8d273b8884468dfd66a5b82526dc8245934eAlan Newberger    /**
134c91c8d273b8884468dfd66a5b82526dc8245934eAlan Newberger     * Set the current button state via an animated transition.
135c91c8d273b8884468dfd66a5b82526dc8245934eAlan Newberger     *
136c91c8d273b8884468dfd66a5b82526dc8245934eAlan Newberger     * @param state
137c91c8d273b8884468dfd66a5b82526dc8245934eAlan Newberger     * @param callListener
138c91c8d273b8884468dfd66a5b82526dc8245934eAlan Newberger     */
139c91c8d273b8884468dfd66a5b82526dc8245934eAlan Newberger    private void setStateAnimatedInternal(final int state, final boolean callListener) {
14018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        if (mState == state || mState == UNSET) {
14118b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            setStateInternal(state, callListener);
14218b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            return;
14318b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        }
14418b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
14518b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        if (mImageIds == null) {
14618b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            return;
14718b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        }
14818b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
1493c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague        new AsyncTask<Integer, Void, Bitmap>() {
15018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            @Override
1513c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague            protected Bitmap doInBackground(Integer... params) {
1523c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                return combine(params[0], params[1]);
15318b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            }
15418b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
15518b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            @Override
1563c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague            protected void onPostExecute(Bitmap bitmap) {
1573c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                if (bitmap == null) {
1583c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    setStateInternal(state, callListener);
1593c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                } else {
1603c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    setImageBitmap(bitmap);
16118b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
1623c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    int offset;
1633c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    if (mAnimDirection == ANIM_DIRECTION_VERTICAL) {
1643c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                        offset = (mParentSize+getHeight())/2;
1653c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    } else if (mAnimDirection == ANIM_DIRECTION_HORIZONTAL) {
1663c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                        offset = (mParentSize+getWidth())/2;
1673c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    } else {
1683c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                        return;
1693c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    }
1703c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague
1713c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    mAnimator.setFloatValues(-offset, 0.0f);
1723c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    AnimatorSet s = new AnimatorSet();
1733c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    s.play(mAnimator);
1743c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    s.addListener(new AnimatorListenerAdapter() {
1753c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                        @Override
1763c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                        public void onAnimationStart(Animator animation) {
1773c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                            setClickEnabled(false);
1783c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                        }
1793c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague
1803c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                        @Override
1813c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                        public void onAnimationEnd(Animator animation) {
1823c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                            setStateInternal(state, callListener);
1833c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                            setClickEnabled(true);
1843c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                        }
1853c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    });
1863c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    s.start();
1873c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                }
18818b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            }
1893c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague        }.execute(mState, state);
19018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    }
19118b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
19266d3d0d0633324893b89e1ddec14a4710d8034c3Spike Sprague    /**
19366d3d0d0633324893b89e1ddec14a4710d8034c3Spike Sprague     * Enable or disable click reactions for this button
19466d3d0d0633324893b89e1ddec14a4710d8034c3Spike Sprague     * without affecting visual state.
19566d3d0d0633324893b89e1ddec14a4710d8034c3Spike Sprague     * For most cases you'll want to use {@link #setEnabled(boolean)}.
19666d3d0d0633324893b89e1ddec14a4710d8034c3Spike Sprague     * @param enabled True if click enabled, false otherwise.
19766d3d0d0633324893b89e1ddec14a4710d8034c3Spike Sprague     */
19866d3d0d0633324893b89e1ddec14a4710d8034c3Spike Sprague    public void setClickEnabled(boolean enabled) {
19966d3d0d0633324893b89e1ddec14a4710d8034c3Spike Sprague        mClickEnabled = enabled;
20066d3d0d0633324893b89e1ddec14a4710d8034c3Spike Sprague    }
20166d3d0d0633324893b89e1ddec14a4710d8034c3Spike Sprague
20218b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    private void setStateInternal(int state, boolean callListener) {
2030744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        mState = state;
204f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren        if (mImageIds != null) {
20518b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            setImageByState(mState);
206f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren        }
20718b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
208f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren        if (mDescIds != null) {
209f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren            String oldContentDescription = String.valueOf(getContentDescription());
210f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren            String newContentDescription = getResources().getString(mDescIds[mState]);
211f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren            if (oldContentDescription != null && !oldContentDescription.isEmpty()
212f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren                    && !oldContentDescription.equals(newContentDescription)) {
213f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren                setContentDescription(newContentDescription);
214f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren                String announceChange = getResources().getString(
2155c62dc726ef2818283e767cde292148c263bcbf8Alan Newberger                    R.string.button_change_announcement, newContentDescription);
216f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren                announceForAccessibility(announceChange);
217f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren            }
2185c62dc726ef2818283e767cde292148c263bcbf8Alan Newberger        }
21966a3e6ceb306833355acb740711ccb5c53576e0aSpike Sprague        super.setImageLevel(mLevel);
22018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
2210744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        if (callListener && mOnStateChangeListener != null) {
22218b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            mOnStateChangeListener.stateChanged(MultiToggleImageButton.this, getState());
2230744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        }
2240744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    }
2250744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
2260744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    private void nextState() {
2270744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        int state = mState + 1;
2280744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        if (state >= mImageIds.length) {
2290744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen            state = 0;
2300744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        }
2310744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        setState(state);
2320744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    }
2330744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
2340744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    protected void init() {
2350744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        this.setOnClickListener(new View.OnClickListener() {
2360744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen            @Override
2370744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen            public void onClick(View v) {
23818b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague                if (mClickEnabled) {
23918b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague                    nextState();
24018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague                }
2410744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen            }
2420744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        });
24318b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        setScaleType(ImageView.ScaleType.MATRIX);
2443c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague
2453c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague        mAnimator = ValueAnimator.ofFloat(0.0f, 0.0f);
2463c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague        mAnimator.setDuration(ANIM_DURATION_MS);
2473c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague        mAnimator.setInterpolator(Gusterpolator.INSTANCE);
2483c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
2493c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague            @Override
2503c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague            public void onAnimationUpdate(ValueAnimator animation) {
2513c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                mMatrix.reset();
2523c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                if (mAnimDirection == ANIM_DIRECTION_VERTICAL) {
2533c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    mMatrix.setTranslate(0.0f, (Float) animation.getAnimatedValue());
2543c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                } else if (mAnimDirection == ANIM_DIRECTION_HORIZONTAL) {
2553c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                    mMatrix.setTranslate((Float) animation.getAnimatedValue(), 0.0f);
2563c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                }
2573c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague
2583c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                setImageMatrix(mMatrix);
2593c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague                invalidate();
2603c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague            }
2613c3b31dfd5ec7a43e89cd4695cc8c498afc3a91dSpike Sprague        });
2620744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    }
2630744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen
2640744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    private void parseAttributes(Context context, AttributeSet attrs) {
2650744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        TypedArray a = context.getTheme().obtainStyledAttributes(
2660744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen            attrs,
2670744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen            R.styleable.MultiToggleImageButton,
2680744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen            0, 0);
26979718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague        int imageIds = a.getResourceId(R.styleable.MultiToggleImageButton_imageIds, 0);
270f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren        if (imageIds > 0) {
271f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren            overrideImageIds(imageIds);
272f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren        }
27379718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague        int descIds = a.getResourceId(R.styleable.MultiToggleImageButton_contentDescriptionIds, 0);
274f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren        if (descIds > 0) {
275f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren            overrideContentDescriptions(descIds);
276f80ac9ee5c66671c3a77ac6628fefc86cd39d57eErin Dahlgren        }
277a07e94c1ed57fa014e7428345927223768532d32Erin Dahlgren        a.recycle();
278a07e94c1ed57fa014e7428345927223768532d32Erin Dahlgren    }
279a07e94c1ed57fa014e7428345927223768532d32Erin Dahlgren
280a07e94c1ed57fa014e7428345927223768532d32Erin Dahlgren    /**
281a07e94c1ed57fa014e7428345927223768532d32Erin Dahlgren     * Override the image ids of this button.
282a07e94c1ed57fa014e7428345927223768532d32Erin Dahlgren     */
283a07e94c1ed57fa014e7428345927223768532d32Erin Dahlgren    public void overrideImageIds(int resId) {
2840744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        TypedArray ids = null;
2850744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        try {
286a07e94c1ed57fa014e7428345927223768532d32Erin Dahlgren            ids = getResources().obtainTypedArray(resId);
287a07e94c1ed57fa014e7428345927223768532d32Erin Dahlgren            mImageIds = new int[ids.length()];
288a07e94c1ed57fa014e7428345927223768532d32Erin Dahlgren            for (int i = 0; i < ids.length(); i++) {
289a07e94c1ed57fa014e7428345927223768532d32Erin Dahlgren                mImageIds[i] = ids.getResourceId(i, 0);
290a07e94c1ed57fa014e7428345927223768532d32Erin Dahlgren            }
2910744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        } finally {
2920744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen            if (ids != null) {
2930744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen                ids.recycle();
2940744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen            }
2950744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen        }
2960744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen    }
29766a3e6ceb306833355acb740711ccb5c53576e0aSpike Sprague
29879718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague    /**
29979718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague     * Override the content descriptions of this button.
30079718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague     */
30179718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague    public void overrideContentDescriptions(int resId) {
30279718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague        TypedArray ids = null;
30379718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague        try {
30479718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague            ids = getResources().obtainTypedArray(resId);
30579718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague            mDescIds = new int[ids.length()];
30679718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague            for (int i = 0; i < ids.length(); i++) {
30779718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague                mDescIds[i] = ids.getResourceId(i, 0);
30879718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague            }
30979718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague        } finally {
31079718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague            if (ids != null) {
31179718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague                ids.recycle();
31279718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague            }
31379718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague        }
31479718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague    }
31579718f6c6fb60aedd09edeedb2c16d1e2f9fe02dSpike Sprague
31618b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    /**
31718b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague     * Set size info (either width or height, as necessary) of the view containing
31818b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague     * this button. Used for offset calculations during animation.
31918b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague     * @param s The size.
32018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague     */
32118b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    public void setParentSize(int s) {
32218b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        mParentSize = s;
32318b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    }
32418b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
32518b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    /**
32618b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague     * Set the animation direction.
32718b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague     * @param d Either ANIM_DIRECTION_VERTICAL or ANIM_DIRECTION_HORIZONTAL.
32818b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague     */
32918b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    public void setAnimDirection(int d) {
33018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        mAnimDirection = d;
33118b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    }
33218b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
33366a3e6ceb306833355acb740711ccb5c53576e0aSpike Sprague    @Override
33466a3e6ceb306833355acb740711ccb5c53576e0aSpike Sprague    public void setImageLevel(int level) {
33566a3e6ceb306833355acb740711ccb5c53576e0aSpike Sprague        super.setImageLevel(level);
33666a3e6ceb306833355acb740711ccb5c53576e0aSpike Sprague        mLevel = level;
33766a3e6ceb306833355acb740711ccb5c53576e0aSpike Sprague    }
33818b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
33918b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    private void setImageByState(int state) {
34018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        if (mImageIds != null) {
34118b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            setImageResource(mImageIds[state]);
34218b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        }
34318b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        super.setImageLevel(mLevel);
34418b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    }
34518b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
34618b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    private Bitmap combine(int oldState, int newState) {
34739384da05ba9ced9086dba64932878ce447c6230Spike Sprague        // in some cases, a new set of image Ids are set via overrideImageIds()
34839384da05ba9ced9086dba64932878ce447c6230Spike Sprague        // and oldState overruns the array.
34939384da05ba9ced9086dba64932878ce447c6230Spike Sprague        // check here for that.
35039384da05ba9ced9086dba64932878ce447c6230Spike Sprague        if (oldState >= mImageIds.length) {
35139384da05ba9ced9086dba64932878ce447c6230Spike Sprague            return null;
35239384da05ba9ced9086dba64932878ce447c6230Spike Sprague        }
35339384da05ba9ced9086dba64932878ce447c6230Spike Sprague
35418b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        int width = getWidth();
35518b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        int height = getHeight();
35618b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
35718b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        if (width <= 0 || height <= 0) {
35818b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            return null;
35918b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        }
36018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
36118b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        int[] enabledState = new int[] {android.R.attr.state_enabled};
36218b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
36318b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        // new state
36418b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        Drawable newDrawable = getResources().getDrawable(mImageIds[newState]).mutate();
36518b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        newDrawable.setState(enabledState);
36618b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
36718b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        // old state
36818b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        Drawable oldDrawable = getResources().getDrawable(mImageIds[oldState]).mutate();
36918b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        oldDrawable.setState(enabledState);
37018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
37118b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        // combine 'em
37218b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        Bitmap bitmap = null;
37318b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        if (mAnimDirection == ANIM_DIRECTION_VERTICAL) {
37418b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            int bitmapHeight = (height*2) + ((mParentSize - height)/2);
37518b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            int oldBitmapOffset = height + ((mParentSize - height)/2);
37618b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            bitmap = Bitmap.createBitmap(width, bitmapHeight, Bitmap.Config.ARGB_8888);
37718b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            Canvas canvas = new Canvas(bitmap);
37818b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            newDrawable.setBounds(0, 0, newDrawable.getIntrinsicWidth(), newDrawable.getIntrinsicHeight());
37918b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            oldDrawable.setBounds(0, oldBitmapOffset, oldDrawable.getIntrinsicWidth(), oldDrawable.getIntrinsicHeight()+oldBitmapOffset);
38018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            newDrawable.draw(canvas);
38118b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            oldDrawable.draw(canvas);
38218b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        } else if (mAnimDirection == ANIM_DIRECTION_HORIZONTAL) {
38318b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            int bitmapWidth = (width*2) + ((mParentSize - width)/2);
38418b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            int oldBitmapOffset = width + ((mParentSize - width)/2);
38518b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            bitmap = Bitmap.createBitmap(bitmapWidth, height, Bitmap.Config.ARGB_8888);
38618b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            Canvas canvas = new Canvas(bitmap);
38718b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            newDrawable.setBounds(0, 0, newDrawable.getIntrinsicWidth(), newDrawable.getIntrinsicHeight());
38818b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            oldDrawable.setBounds(oldBitmapOffset, 0, oldDrawable.getIntrinsicWidth()+oldBitmapOffset, oldDrawable.getIntrinsicHeight());
38918b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            newDrawable.draw(canvas);
39018b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague            oldDrawable.draw(canvas);
39118b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        }
39218b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague
39318b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague        return bitmap;
39418b22b5963e8871e539edf669d96862eec72bb5dSpike Sprague    }
3950744e4ab3ec5f0270bdad33e7c477f52ba680bbdMarco Nelissen}