• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.camera.ui;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.RectF;
22 import android.graphics.drawable.ColorDrawable;
23 import android.graphics.drawable.Drawable;
24 import android.graphics.drawable.LayerDrawable;
25 import android.graphics.drawable.TransitionDrawable;
26 import android.util.AttributeSet;
27 import android.view.MotionEvent;
28 import android.view.View;
29 import android.widget.FrameLayout;
30 import android.widget.ImageButton;
31 
32 import com.android.camera.CaptureLayoutHelper;
33 import com.android.camera.ShutterButton;
34 import com.android.camera.debug.Log;
35 import com.android.camera.util.ApiHelper;
36 import com.android.camera.util.CameraUtil;
37 import com.android.camera2.R;
38 
39 /**
40  * BottomBar swaps its width and height on rotation. In addition, it also
41  * changes gravity and layout orientation based on the new orientation.
42  * Specifically, in landscape it aligns to the right side of its parent and lays
43  * out its children vertically, whereas in portrait, it stays at the bottom of
44  * the parent and has a horizontal layout orientation.
45  */
46 public class BottomBar extends FrameLayout {
47 
48     private static final Log.Tag TAG = new Log.Tag("BottomBar");
49 
50     private static final int CIRCLE_ANIM_DURATION_MS = 300;
51     private static final int DRAWABLE_MAX_LEVEL = 10000;
52     private static final int MODE_CAPTURE = 0;
53     private static final int MODE_INTENT = 1;
54     private static final int MODE_INTENT_REVIEW = 2;
55     private static final int MODE_CANCEL = 3;
56 
57     private int mMode;
58 
59     private final int mBackgroundAlphaOverlay;
60     private final int mBackgroundAlphaDefault;
61     private boolean mOverLayBottomBar;
62 
63     private FrameLayout mCaptureLayout;
64     private FrameLayout mCancelLayout;
65     private TopRightWeightedLayout mIntentReviewLayout;
66 
67     private ShutterButton mShutterButton;
68     private ImageButton mCancelButton;
69 
70     private int mBackgroundColor;
71     private int mBackgroundPressedColor;
72     private int mBackgroundAlpha = 0xff;
73 
74     private boolean mDrawCircle;
75     private final float mCircleRadius;
76     private CaptureLayoutHelper mCaptureLayoutHelper = null;
77 
78     // for Android L, these backgrounds are RippleDrawables (ISA LayerDrawable)
79     // pre-L, they're plain old LayerDrawables
80     private final LayerDrawable[] mShutterButtonBackgrounds;
81     // a reference to the shutter background's first contained drawable
82     // if it's an animated circle drawable (for video mode)
83     private AnimatedCircleDrawable mAnimatedCircleDrawable;
84     // a reference to the shutter background's first contained drawable
85     // if it's a color drawable (for all other modes)
86     private ColorDrawable mColorDrawable;
87 
88     private RectF mRect = new RectF();
89 
BottomBar(Context context, AttributeSet attrs)90     public BottomBar(Context context, AttributeSet attrs) {
91         super(context, attrs);
92         mCircleRadius = getResources()
93                 .getDimensionPixelSize(R.dimen.video_capture_circle_diameter) / 2;
94         mBackgroundAlphaOverlay = getResources()
95                 .getInteger(R.integer.bottom_bar_background_alpha_overlay);
96         mBackgroundAlphaDefault = getResources()
97                 .getInteger(R.integer.bottom_bar_background_alpha);
98 
99         // preload all the drawable BGs
100         TypedArray ar = context.getResources()
101                 .obtainTypedArray(R.array.shutter_button_backgrounds);
102         int len = ar.length();
103 
104         mShutterButtonBackgrounds = new LayerDrawable[len];
105         for (int i = 0; i < len; i++) {
106             int drawableId = ar.getResourceId(i, -1);
107             LayerDrawable shutterBackground = mShutterButtonBackgrounds[i] =
108                     (LayerDrawable) context.getResources().getDrawable(drawableId).mutate();
109 
110             // the background for video has a circle_item drawable placeholder
111             // that gets replaced by an AnimatedCircleDrawable for the cool
112             // shrink-down-to-a-circle effect
113             // all other modes need not do this replace
114             Drawable d = shutterBackground.findDrawableByLayerId(R.id.circle_item);
115             if (d != null) {
116                 Drawable animatedCircleDrawable =
117                         new AnimatedCircleDrawable((int) mCircleRadius);
118                 animatedCircleDrawable.setLevel(DRAWABLE_MAX_LEVEL);
119                 shutterBackground
120                         .setDrawableByLayerId(R.id.circle_item, animatedCircleDrawable);
121             }
122         }
123         ar.recycle();
124     }
125 
setPaintColor(int alpha, int color)126     private void setPaintColor(int alpha, int color) {
127         if (mAnimatedCircleDrawable != null) {
128             mAnimatedCircleDrawable.setColor(color);
129             mAnimatedCircleDrawable.setAlpha(alpha);
130         } else if (mColorDrawable != null) {
131             mColorDrawable.setColor(color);
132             mColorDrawable.setAlpha(alpha);
133         }
134 
135         if (mIntentReviewLayout != null) {
136             ColorDrawable intentBackground = (ColorDrawable) mIntentReviewLayout
137                     .getBackground();
138             intentBackground.setColor(color);
139             intentBackground.setAlpha(alpha);
140         }
141     }
142 
refreshPaintColor()143     private void refreshPaintColor() {
144         setPaintColor(mBackgroundAlpha, mBackgroundColor);
145     }
146 
setCancelBackgroundColor(int alpha, int color)147     private void setCancelBackgroundColor(int alpha, int color) {
148         LayerDrawable layerDrawable = (LayerDrawable) mCancelButton.getBackground();
149         ColorDrawable colorDrawable = (ColorDrawable) layerDrawable.getDrawable(0);
150         if (!ApiHelper.isLOrHigher()) {
151             colorDrawable.setColor(color);
152         }
153         colorDrawable.setAlpha(alpha);
154     }
155 
setCaptureButtonUp()156     private void setCaptureButtonUp() {
157         setPaintColor(mBackgroundAlpha, mBackgroundColor);
158     }
159 
setCaptureButtonDown()160     private void setCaptureButtonDown() {
161         if (!ApiHelper.isLOrHigher()) {
162             setPaintColor(mBackgroundAlpha, mBackgroundPressedColor);
163         }
164     }
165 
setCancelButtonUp()166     private void setCancelButtonUp() {
167         setCancelBackgroundColor(mBackgroundAlpha, mBackgroundColor);
168     }
169 
setCancelButtonDown()170     private void setCancelButtonDown() {
171         setCancelBackgroundColor(mBackgroundAlpha, mBackgroundPressedColor);
172     }
173 
174     @Override
onFinishInflate()175     public void onFinishInflate() {
176         mCaptureLayout =
177                 (FrameLayout) findViewById(R.id.bottombar_capture);
178         mCancelLayout =
179                 (FrameLayout) findViewById(R.id.bottombar_cancel);
180         mCancelLayout.setVisibility(View.GONE);
181 
182         mIntentReviewLayout =
183                 (TopRightWeightedLayout) findViewById(R.id.bottombar_intent_review);
184 
185         mShutterButton =
186                 (ShutterButton) findViewById(R.id.shutter_button);
187         mShutterButton.setOnTouchListener(new OnTouchListener() {
188             @Override
189             public boolean onTouch(View v, MotionEvent event) {
190                 if (MotionEvent.ACTION_DOWN == event.getActionMasked()) {
191                     setCaptureButtonDown();
192                 } else if (MotionEvent.ACTION_UP == event.getActionMasked() ||
193                         MotionEvent.ACTION_CANCEL == event.getActionMasked()) {
194                     setCaptureButtonUp();
195                 } else if (MotionEvent.ACTION_MOVE == event.getActionMasked()) {
196                     mRect.set(0, 0, getWidth(), getHeight());
197                     if (!mRect.contains(event.getX(), event.getY())) {
198                         setCaptureButtonUp();
199                     }
200                 }
201                 return false;
202             }
203         });
204 
205         mCancelButton =
206                 (ImageButton) findViewById(R.id.shutter_cancel_button);
207         mCancelButton.setOnTouchListener(new OnTouchListener() {
208             @Override
209             public boolean onTouch(View v, MotionEvent event) {
210                 if (MotionEvent.ACTION_DOWN == event.getActionMasked()) {
211                     setCancelButtonDown();
212                 } else if (MotionEvent.ACTION_UP == event.getActionMasked() ||
213                         MotionEvent.ACTION_CANCEL == event.getActionMasked()) {
214                     setCancelButtonUp();
215                 } else if (MotionEvent.ACTION_MOVE == event.getActionMasked()) {
216                     mRect.set(0, 0, getWidth(), getHeight());
217                     if (!mRect.contains(event.getX(), event.getY())) {
218                         setCancelButtonUp();
219                     }
220                 }
221                 return false;
222             }
223         });
224 
225     }
226 
227     /**
228      * Perform a transition from the bottom bar options layout to the bottom bar
229      * capture layout.
230      */
transitionToCapture()231     public void transitionToCapture() {
232         mCaptureLayout.setVisibility(View.VISIBLE);
233         mCancelLayout.setVisibility(View.GONE);
234         mIntentReviewLayout.setVisibility(View.GONE);
235 
236         mMode = MODE_CAPTURE;
237     }
238 
239     /**
240      * Perform a transition from the bottom bar options layout to the bottom bar
241      * capture layout.
242      */
transitionToCancel()243     public void transitionToCancel() {
244         mCaptureLayout.setVisibility(View.GONE);
245         mIntentReviewLayout.setVisibility(View.GONE);
246         mCancelLayout.setVisibility(View.VISIBLE);
247 
248         mMode = MODE_CANCEL;
249     }
250 
251     /**
252      * Perform a transition to the global intent layout. The current layout
253      * state of the bottom bar is irrelevant.
254      */
transitionToIntentCaptureLayout()255     public void transitionToIntentCaptureLayout() {
256         mIntentReviewLayout.setVisibility(View.GONE);
257         mCaptureLayout.setVisibility(View.VISIBLE);
258         mCancelLayout.setVisibility(View.GONE);
259 
260         mMode = MODE_INTENT;
261     }
262 
263     /**
264      * Perform a transition to the global intent review layout. The current
265      * layout state of the bottom bar is irrelevant.
266      */
transitionToIntentReviewLayout()267     public void transitionToIntentReviewLayout() {
268         mCaptureLayout.setVisibility(View.GONE);
269         mIntentReviewLayout.setVisibility(View.VISIBLE);
270         mCancelLayout.setVisibility(View.GONE);
271 
272         mMode = MODE_INTENT_REVIEW;
273     }
274 
275     /**
276      * @return whether UI is in intent review mode
277      */
isInIntentReview()278     public boolean isInIntentReview() {
279         return mMode == MODE_INTENT_REVIEW;
280     }
281 
setButtonImageLevels(int level)282     private void setButtonImageLevels(int level) {
283         ((ImageButton) findViewById(R.id.cancel_button)).setImageLevel(level);
284         ((ImageButton) findViewById(R.id.done_button)).setImageLevel(level);
285         ((ImageButton) findViewById(R.id.retake_button)).setImageLevel(level);
286     }
287 
setOverlayBottomBar(boolean overlay)288     private void setOverlayBottomBar(boolean overlay) {
289         mOverLayBottomBar = overlay;
290         if (overlay) {
291             setBackgroundAlpha(mBackgroundAlphaOverlay);
292             setButtonImageLevels(1);
293         } else {
294             setBackgroundAlpha(mBackgroundAlphaDefault);
295             setButtonImageLevels(0);
296         }
297     }
298 
299     /**
300      * Sets a capture layout helper to query layout rect from.
301      */
setCaptureLayoutHelper(CaptureLayoutHelper helper)302     public void setCaptureLayoutHelper(CaptureLayoutHelper helper) {
303         mCaptureLayoutHelper = helper;
304     }
305 
306     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)307     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
308         final int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
309         final int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
310         if (measureWidth == 0 || measureHeight == 0) {
311             return;
312         }
313 
314         if (mCaptureLayoutHelper == null) {
315             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
316             Log.e(TAG, "Capture layout helper needs to be set first.");
317         } else {
318             RectF bottomBarRect = mCaptureLayoutHelper.getBottomBarRect();
319             super.onMeasure(MeasureSpec.makeMeasureSpec(
320                     (int) bottomBarRect.width(), MeasureSpec.EXACTLY),
321                     MeasureSpec.makeMeasureSpec((int) bottomBarRect.height(), MeasureSpec.EXACTLY)
322                     );
323             boolean shouldOverlayBottomBar = mCaptureLayoutHelper.shouldOverlayBottomBar();
324             setOverlayBottomBar(shouldOverlayBottomBar);
325         }
326     }
327 
328     // prevent touches on bottom bar (not its children)
329     // from triggering a touch event on preview area
330     @Override
onTouchEvent(MotionEvent event)331     public boolean onTouchEvent(MotionEvent event) {
332         return true;
333     }
334 
335     @Override
setBackgroundColor(int color)336     public void setBackgroundColor(int color) {
337         mBackgroundColor = color;
338         setPaintColor(mBackgroundAlpha, mBackgroundColor);
339         setCancelBackgroundColor(mBackgroundAlpha, mBackgroundColor);
340     }
341 
setBackgroundPressedColor(int color)342     private void setBackgroundPressedColor(int color) {
343         if (ApiHelper.isLOrHigher()) {
344             // not supported (setting a color on a RippleDrawable is hard =[ )
345         } else {
346             mBackgroundPressedColor = color;
347         }
348     }
349 
setupShutterBackgroundForModeIndex(int index)350     private void setupShutterBackgroundForModeIndex(int index) {
351         LayerDrawable shutterBackground = mShutterButtonBackgrounds[index];
352         mShutterButton.setBackground(shutterBackground);
353 
354         Drawable d = shutterBackground.getDrawable(0);
355         mAnimatedCircleDrawable = null;
356         mColorDrawable = null;
357         if (d instanceof AnimatedCircleDrawable) {
358             mAnimatedCircleDrawable = (AnimatedCircleDrawable) d;
359         } else if (d instanceof ColorDrawable) {
360             mColorDrawable = (ColorDrawable) d;
361         }
362 
363         int colorId = CameraUtil.getCameraThemeColorId(index, getContext());
364         int pressedColor = getContext().getResources().getColor(colorId);
365         setBackgroundPressedColor(pressedColor);
366         refreshPaintColor();
367     }
368 
setColorsForModeIndex(int index)369     public void setColorsForModeIndex(int index) {
370         setupShutterBackgroundForModeIndex(index);
371     }
372 
setBackgroundAlpha(int alpha)373     public void setBackgroundAlpha(int alpha) {
374         mBackgroundAlpha = alpha;
375         setPaintColor(mBackgroundAlpha, mBackgroundColor);
376         setCancelBackgroundColor(mBackgroundAlpha, mBackgroundColor);
377     }
378 
379     /**
380      * Sets the shutter button enabled if true, disabled if false.
381      * <p>
382      * Disabled means that the shutter button is not clickable and is greyed
383      * out.
384      */
setShutterButtonEnabled(final boolean enabled)385     public void setShutterButtonEnabled(final boolean enabled) {
386         mShutterButton.post(new Runnable() {
387             @Override
388             public void run() {
389                 mShutterButton.setEnabled(enabled);
390                 setShutterButtonImportantToA11y(enabled);
391             }
392         });
393     }
394 
395     /**
396      * Sets whether shutter button should be included in a11y announcement and
397      * navigation
398      */
setShutterButtonImportantToA11y(boolean important)399     public void setShutterButtonImportantToA11y(boolean important) {
400         if (important) {
401             mShutterButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
402         } else {
403             mShutterButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
404         }
405     }
406 
407     /**
408      * Returns whether the capture button is enabled.
409      */
isShutterButtonEnabled()410     public boolean isShutterButtonEnabled() {
411         return mShutterButton.isEnabled();
412     }
413 
crossfadeDrawable(Drawable from, Drawable to)414     private TransitionDrawable crossfadeDrawable(Drawable from, Drawable to) {
415         Drawable[] arrayDrawable = new Drawable[2];
416         arrayDrawable[0] = from;
417         arrayDrawable[1] = to;
418         TransitionDrawable transitionDrawable = new TransitionDrawable(arrayDrawable);
419         transitionDrawable.setCrossFadeEnabled(true);
420         return transitionDrawable;
421     }
422 
423     /**
424      * Sets the shutter button's icon resource. By default, all drawables
425      * instances loaded from the same resource share a common state; if you
426      * modify the state of one instance, all the other instances will receive
427      * the same modification. In order to modify properties of this icon
428      * drawable without affecting other drawables, here we use a mutable
429      * drawable which is guaranteed to not share states with other drawables.
430      */
setShutterButtonIcon(int resId)431     public void setShutterButtonIcon(int resId) {
432         Drawable iconDrawable = getResources().getDrawable(resId);
433         if (iconDrawable != null) {
434             iconDrawable = iconDrawable.mutate();
435         }
436         mShutterButton.setImageDrawable(iconDrawable);
437     }
438 
439     /**
440      * Animates bar to a single stop button
441      */
animateToVideoStop(int resId)442     public void animateToVideoStop(int resId) {
443         if (mOverLayBottomBar && mAnimatedCircleDrawable != null) {
444             mAnimatedCircleDrawable.animateToSmallRadius();
445             mDrawCircle = true;
446         }
447 
448         TransitionDrawable transitionDrawable = crossfadeDrawable(
449                 mShutterButton.getDrawable(),
450                 getResources().getDrawable(resId));
451         mShutterButton.setImageDrawable(transitionDrawable);
452         transitionDrawable.startTransition(CIRCLE_ANIM_DURATION_MS);
453     }
454 
455     /**
456      * Animates bar to full width / length with video capture icon
457      */
animateToFullSize(int resId)458     public void animateToFullSize(int resId) {
459         if (mDrawCircle && mAnimatedCircleDrawable != null) {
460             mAnimatedCircleDrawable.animateToFullSize();
461             mDrawCircle = false;
462         }
463 
464         TransitionDrawable transitionDrawable = crossfadeDrawable(
465                 mShutterButton.getDrawable(),
466                 getResources().getDrawable(resId));
467         mShutterButton.setImageDrawable(transitionDrawable);
468         transitionDrawable.startTransition(CIRCLE_ANIM_DURATION_MS);
469     }
470 }
471