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  */
16 package com.android.camera.widget;
17 
18 import com.google.common.base.Optional;
19 
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.AnimatorSet;
23 import android.animation.ValueAnimator;
24 import android.content.Context;
25 import android.content.res.Configuration;
26 import android.graphics.Canvas;
27 import android.graphics.Paint;
28 import android.graphics.RectF;
29 import android.util.AttributeSet;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.FrameLayout;
33 import android.widget.ImageButton;
34 
35 import com.android.camera.MultiToggleImageButton;
36 import com.android.camera.ui.RadioOptions;
37 import com.android.camera.ui.TopRightWeightedLayout;
38 import com.android.camera.util.Gusterpolator;
39 import com.android.camera2.R;
40 
41 import java.util.ArrayList;
42 
43 public class ModeOptions extends FrameLayout {
44     private int mBackgroundColor;
45     private final Paint mPaint = new Paint();
46     private boolean mIsHiddenOrHiding;
47     private RectF mAnimateFrom = new RectF();
48     private View mViewToShowHide;
49     private TopRightWeightedLayout mModeOptionsButtons;
50     private RadioOptions mModeOptionsPano;
51     private RadioOptions mModeOptionsExposure;
52 
53     private AnimatorSet mVisibleAnimator;
54     private AnimatorSet mHiddenAnimator;
55     private boolean mDrawCircle;
56     private boolean mFill;
57     private static final int RADIUS_ANIMATION_TIME = 250;
58     private static final int SHOW_ALPHA_ANIMATION_TIME = 350;
59     private static final int HIDE_ALPHA_ANIMATION_TIME = 200;
60     public static final int PADDING_ANIMATION_TIME = 350;
61 
62     private ViewGroup mMainBar;
63     private ViewGroup mActiveBar;
64     public static final int BAR_INVALID = -1;
65     public static final int BAR_STANDARD = 0;
66     public static final int BAR_PANO = 1;
67 
68     private boolean mIsPortrait;
69     private float mRadius = 0f;
70 
71     /**
72      * A class implementing this interface will receive callback events from
73      * mode options.
74      */
75     public interface Listener {
76         /**
77          * Called when about to start animating the mode options from hidden
78          * to visible.
79          */
onBeginToShowModeOptions()80         public void onBeginToShowModeOptions();
81 
82         /**
83          * Called when about to start animating the mode options from visible
84          * to hidden.
85          */
onBeginToHideModeOptions()86         public void onBeginToHideModeOptions();
87     }
88 
89     /** The listener. */
90     private Optional<Listener> mListener;
91 
ModeOptions(Context context, AttributeSet attrs)92     public ModeOptions(Context context, AttributeSet attrs) {
93         super(context, attrs);
94         mListener = Optional.absent();
95     }
96 
97     /**
98      * Whether the mode options is hidden or in the middle of fading
99      * out.
100      */
isHiddenOrHiding()101     public boolean isHiddenOrHiding() {
102         return mIsHiddenOrHiding;
103     }
104 
105     /**
106      * Sets the listener.
107      *
108      * @param listener The listener to be set.
109      */
setListener(Listener listener)110     public void setListener(Listener listener) {
111         mListener = Optional.of(listener);
112     }
113 
setViewToShowHide(View v)114     public void setViewToShowHide(View v) {
115         mViewToShowHide = v;
116     }
117 
118     @Override
onFinishInflate()119     public void onFinishInflate() {
120         mIsHiddenOrHiding = true;
121         mBackgroundColor = getResources().getColor(R.color.mode_options_background);
122         mPaint.setAntiAlias(true);
123         mPaint.setColor(mBackgroundColor);
124         mModeOptionsButtons = (TopRightWeightedLayout) findViewById(R.id.mode_options_buttons);
125         mModeOptionsPano = (RadioOptions) findViewById(R.id.mode_options_pano);
126         mModeOptionsExposure = (RadioOptions) findViewById(R.id.mode_options_exposure);
127         mMainBar = mActiveBar = mModeOptionsButtons;
128     }
129 
showExposureOptions()130     public void showExposureOptions() {
131         mActiveBar = mModeOptionsExposure;
132         mMainBar.setVisibility(View.INVISIBLE);
133         mActiveBar.setVisibility(View.VISIBLE);
134     }
135 
setMainBar(int b)136     public void setMainBar(int b) {
137         for (int i = 0; i < getChildCount(); i++) {
138             getChildAt(i).setVisibility(View.INVISIBLE);
139         }
140         switch (b) {
141         case BAR_STANDARD:
142             mMainBar = mActiveBar = mModeOptionsButtons;
143             break;
144         case BAR_PANO:
145             mMainBar = mActiveBar = mModeOptionsPano;
146             break;
147         }
148         mMainBar.setVisibility(View.VISIBLE);
149     }
150 
getMainBar()151     public int getMainBar() {
152         if (mMainBar == mModeOptionsButtons) {
153             return BAR_STANDARD;
154         }
155         if (mMainBar == mModeOptionsPano) {
156             return BAR_PANO;
157         }
158         return BAR_INVALID;
159     }
160 
161     @Override
onWindowVisibilityChanged(int visibility)162     public void onWindowVisibilityChanged(int visibility) {
163         super.onWindowVisibilityChanged(visibility);
164         if (visibility != VISIBLE && !mIsHiddenOrHiding) {
165             // Collapse mode options when window is not visible.
166             setVisibility(INVISIBLE);
167             if (mMainBar != null) {
168                 mMainBar.setVisibility(VISIBLE);
169             }
170             if (mActiveBar != null && mActiveBar != mMainBar) {
171                 mActiveBar.setVisibility(INVISIBLE);
172             }
173             if (mViewToShowHide != null) {
174                 mViewToShowHide.setVisibility(VISIBLE);
175             }
176             mIsHiddenOrHiding = true;
177         }
178     }
179 
180     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)181     public void onLayout(boolean changed, int left, int top, int right, int bottom) {
182         if (changed) {
183             mIsPortrait = (getResources().getConfiguration().orientation ==
184                            Configuration.ORIENTATION_PORTRAIT);
185 
186             int buttonSize = getResources()
187                 .getDimensionPixelSize(R.dimen.option_button_circle_size);
188             int buttonPadding = getResources()
189                 .getDimensionPixelSize(R.dimen.mode_options_toggle_padding);
190 
191             float rLeft, rRight, rTop, rBottom;
192             if (mIsPortrait) {
193                 rLeft = getWidth() - buttonPadding - buttonSize;
194                 rTop = (getHeight() - buttonSize) / 2.0f;
195             } else {
196                 rLeft = buttonPadding;
197                 rTop = buttonPadding;
198             }
199             rRight = rLeft + buttonSize;
200             rBottom = rTop + buttonSize;
201             mAnimateFrom.set(rLeft, rTop, rRight, rBottom);
202 
203             setupAnimators();
204             setupToggleButtonParams();
205         }
206 
207         super.onLayout(changed, left, top, right, bottom);
208     }
209 
210     @Override
onDraw(Canvas canvas)211     public void onDraw(Canvas canvas) {
212         if (mDrawCircle) {
213             canvas.drawCircle(mAnimateFrom.centerX(), mAnimateFrom.centerY(), mRadius, mPaint);
214         } else if (mFill) {
215             canvas.drawPaint(mPaint);
216         }
217         super.onDraw(canvas);
218     }
219 
setupToggleButtonParams()220     private void setupToggleButtonParams() {
221         int size = (mIsPortrait ? getHeight() : getWidth());
222 
223         for (int i = 0; i < mModeOptionsButtons.getChildCount(); i++) {
224             View button = mModeOptionsButtons.getChildAt(i);
225             if (button instanceof MultiToggleImageButton) {
226                 MultiToggleImageButton toggleButton = (MultiToggleImageButton) button;
227                 toggleButton.setParentSize(size);
228                 toggleButton.setAnimDirection(mIsPortrait ?
229                         MultiToggleImageButton.ANIM_DIRECTION_VERTICAL :
230                         MultiToggleImageButton.ANIM_DIRECTION_HORIZONTAL);
231             }
232         }
233     }
234 
setupAnimators()235     private void setupAnimators() {
236         if (mVisibleAnimator != null) {
237             mVisibleAnimator.end();
238         }
239         if (mHiddenAnimator != null) {
240             mHiddenAnimator.end();
241         }
242 
243         final float fullSize = (mIsPortrait ? (float) getWidth() : (float) getHeight());
244 
245         // show
246         {
247             final ValueAnimator radiusAnimator =
248                 ValueAnimator.ofFloat(mAnimateFrom.width()/2.0f,
249                     fullSize-mAnimateFrom.width()/2.0f);
250             radiusAnimator.setDuration(RADIUS_ANIMATION_TIME);
251             radiusAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
252                 @Override
253                 public void onAnimationUpdate(ValueAnimator animation) {
254                     mRadius = (Float) animation.getAnimatedValue();
255                     mDrawCircle = true;
256                     mFill = false;
257                 }
258             });
259             radiusAnimator.addListener(new AnimatorListenerAdapter() {
260                 @Override
261                 public void onAnimationEnd(Animator animation) {
262                     mDrawCircle = false;
263                     mFill = true;
264                 }
265             });
266 
267             final ValueAnimator alphaAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
268             alphaAnimator.setDuration(SHOW_ALPHA_ANIMATION_TIME);
269             alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
270                 @Override
271                 public void onAnimationUpdate(ValueAnimator animation) {
272                     mActiveBar.setAlpha((Float) animation.getAnimatedValue());
273                 }
274             });
275             alphaAnimator.addListener(new AnimatorListenerAdapter() {
276                 @Override
277                 public void onAnimationEnd(Animator animation) {
278                     mActiveBar.setAlpha(1.0f);
279                 }
280             });
281 
282             final int deltaX = getResources()
283                 .getDimensionPixelSize(R.dimen.mode_options_buttons_anim_delta_x);
284             int childCount = mActiveBar.getChildCount();
285             ArrayList<Animator> paddingAnimators = new ArrayList<Animator>();
286             for (int i = 0; i < childCount; i++) {
287                 final View button;
288                 if (mIsPortrait) {
289                     button = mActiveBar.getChildAt(i);
290                 } else {
291                     button = mActiveBar.getChildAt(childCount-1-i);
292                 }
293 
294                 final ValueAnimator paddingAnimator =
295                     ValueAnimator.ofFloat(deltaX*(childCount-i), 0.0f);
296                 paddingAnimator.setDuration(PADDING_ANIMATION_TIME);
297                 paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
298                     @Override
299                     public void onAnimationUpdate(ValueAnimator animation) {
300                         if (mIsPortrait) {
301                             button.setTranslationX((Float) animation.getAnimatedValue());
302                         } else {
303                             button.setTranslationY(-((Float) animation.getAnimatedValue()));
304                         }
305                         invalidate();
306                     }
307                 });
308 
309                 paddingAnimators.add(paddingAnimator);
310             }
311 
312             AnimatorSet paddingAnimatorSet = new AnimatorSet();
313             paddingAnimatorSet.playTogether(paddingAnimators);
314 
315             mVisibleAnimator = new AnimatorSet();
316             mVisibleAnimator.setInterpolator(Gusterpolator.INSTANCE);
317             mVisibleAnimator.playTogether(radiusAnimator, alphaAnimator, paddingAnimatorSet);
318         }
319 
320         // hide
321         {
322             final ValueAnimator radiusAnimator =
323                 ValueAnimator.ofFloat(fullSize-mAnimateFrom.width()/2.0f,
324                     mAnimateFrom.width()/2.0f);
325             radiusAnimator.setDuration(RADIUS_ANIMATION_TIME);
326             radiusAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
327                 @Override
328                 public void onAnimationUpdate(ValueAnimator animation) {
329                     mRadius = (Float) animation.getAnimatedValue();
330                     mDrawCircle = true;
331                     mFill = false;
332                     invalidate();
333                 }
334             });
335             radiusAnimator.addListener(new AnimatorListenerAdapter() {
336                 @Override
337                 public void onAnimationEnd(Animator animation) {
338                     if (mViewToShowHide != null) {
339                         mViewToShowHide.setVisibility(View.VISIBLE);
340                         mDrawCircle = false;
341                         mFill = false;
342                         invalidate();
343                     }
344                 }
345             });
346 
347             final ValueAnimator alphaAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
348             alphaAnimator.setDuration(HIDE_ALPHA_ANIMATION_TIME);
349             alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
350                 @Override
351                 public void onAnimationUpdate(ValueAnimator animation) {
352                     mActiveBar.setAlpha((Float) animation.getAnimatedValue());
353                     invalidate();
354                 }
355             });
356             alphaAnimator.addListener(new AnimatorListenerAdapter() {
357                 @Override
358                 public void onAnimationEnd(Animator animation) {
359                     setVisibility(View.INVISIBLE);
360                     if (mActiveBar != mMainBar) {
361                         mActiveBar.setAlpha(1.0f);
362                         mActiveBar.setVisibility(View.INVISIBLE);
363                     }
364                     mMainBar.setAlpha(1.0f);
365                     mMainBar.setVisibility(View.VISIBLE);
366                     mActiveBar = mMainBar;
367                     invalidate();
368                 }
369             });
370 
371             mHiddenAnimator = new AnimatorSet();
372             mHiddenAnimator.setInterpolator(Gusterpolator.INSTANCE);
373             mHiddenAnimator.playTogether(radiusAnimator, alphaAnimator);
374         }
375     }
376 
animateVisible()377     public void animateVisible() {
378         if (mIsHiddenOrHiding) {
379             if (mViewToShowHide != null) {
380                 mViewToShowHide.setVisibility(View.INVISIBLE);
381             }
382             mHiddenAnimator.cancel();
383             mVisibleAnimator.end();
384             setVisibility(View.VISIBLE);
385             mVisibleAnimator.start();
386             if (mListener.isPresent()) {
387                 mListener.get().onBeginToShowModeOptions();
388             }
389         }
390         mIsHiddenOrHiding = false;
391     }
392 
animateHidden()393     public void animateHidden() {
394         if (!mIsHiddenOrHiding) {
395             mVisibleAnimator.cancel();
396             mHiddenAnimator.end();
397             mHiddenAnimator.start();
398             if (mListener.isPresent()) {
399                 mListener.get().onBeginToHideModeOptions();
400             }
401         }
402         mIsHiddenOrHiding = true;
403     }
404 }