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 }