blob: 256cf4efe3678d5621939146437375dc0de6a28e [file] [log] [blame]
Justin Klaassen4b3af052014-05-27 17:53:10 -07001/*
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
17package com.android.calculator2;
18
19import android.animation.Animator;
Justin Klaassen5f2a3342014-06-11 17:40:22 -070020import android.animation.Animator.AnimatorListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -070021import android.animation.AnimatorListenerAdapter;
22import android.animation.AnimatorSet;
23import android.animation.ArgbEvaluator;
24import android.animation.ObjectAnimator;
25import android.animation.ValueAnimator;
26import android.animation.ValueAnimator.AnimatorUpdateListener;
27import android.app.Activity;
Justin Klaassen8fff1442014-06-19 10:43:29 -070028import android.graphics.Rect;
Justin Klaassen4b3af052014-05-27 17:53:10 -070029import android.os.Bundle;
Justin Klaassen3b4d13d2014-06-06 18:18:37 +010030import android.support.v4.view.ViewPager;
Justin Klaassen4b3af052014-05-27 17:53:10 -070031import android.text.Editable;
32import android.text.TextUtils;
33import android.text.TextWatcher;
34import android.view.KeyEvent;
35import android.view.View;
36import android.view.View.OnLongClickListener;
Justin Klaassen5f2a3342014-06-11 17:40:22 -070037import android.view.ViewAnimationUtils;
Justin Klaassen8fff1442014-06-19 10:43:29 -070038import android.view.ViewGroupOverlay;
Justin Klaassen4b3af052014-05-27 17:53:10 -070039import android.view.animation.AccelerateDecelerateInterpolator;
40import android.widget.Button;
Justin Klaassenfed941a2014-06-09 18:42:40 +010041import android.widget.TextView;
42
43import com.android.calculator2.CalculatorEditText.OnTextSizeChangeListener;
44import com.android.calculator2.CalculatorExpressionEvaluator.EvaluateCallback;
Justin Klaassen4b3af052014-05-27 17:53:10 -070045
Justin Klaassen04f79c72014-06-27 17:25:35 -070046public class Calculator extends Activity
Justin Klaassenfed941a2014-06-09 18:42:40 +010047 implements OnTextSizeChangeListener, EvaluateCallback, OnLongClickListener {
Justin Klaassen4b3af052014-05-27 17:53:10 -070048
49 public static final String CALCULATOR_ACTIVITY_CURRENT_STATE =
Justin Klaassen04f79c72014-06-27 17:25:35 -070050 Calculator.class.getSimpleName() + "_currentState";
Justin Klaassen4b3af052014-05-27 17:53:10 -070051
52 private enum CalculatorState {
53 INPUT, EVALUATE, RESULT, ERROR
54 }
55
56 private final TextWatcher mFormulaTextWatcher = new TextWatcher() {
57 @Override
58 public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
59 }
60
61 @Override
62 public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
63 }
64
65 @Override
66 public void afterTextChanged(Editable editable) {
67 setState(CalculatorState.INPUT);
Justin Klaassen04f79c72014-06-27 17:25:35 -070068 mEvaluator.evaluate(editable, Calculator.this);
Justin Klaassen4b3af052014-05-27 17:53:10 -070069 }
70 };
71
Justin Klaassen741471e2014-06-11 09:43:44 -070072 private final Editable.Factory mFormulaEditableFactory = new Editable.Factory() {
73 @Override
74 public Editable newEditable(CharSequence source) {
Justin Klaassen04f79c72014-06-27 17:25:35 -070075 return new CalculatorExpressionBuilder(Calculator.this, source,
Justin Klaassen741471e2014-06-11 09:43:44 -070076 mCurrentState == CalculatorState.INPUT);
77 }
78 };
79
Justin Klaassen4b3af052014-05-27 17:53:10 -070080 private CalculatorState mCurrentState;
81 private CalculatorExpressionEvaluator mEvaluator;
82
83 private CalculatorEditText mFormulaEditText;
84 private CalculatorEditText mResultEditText;
85
Justin Klaassen3b4d13d2014-06-06 18:18:37 +010086 private ViewPager mPadViewPager;
87
Justin Klaassen4b3af052014-05-27 17:53:10 -070088 private View mDeleteButton;
89 private View mClearButton;
90
Justin Klaassen5f2a3342014-06-11 17:40:22 -070091 private View mCurrentButton;
Justin Klaassen4b3af052014-05-27 17:53:10 -070092 private Animator mCurrentAnimator;
93
94 @Override
95 protected void onCreate(Bundle savedInstanceState) {
96 super.onCreate(savedInstanceState);
Justin Klaassen5f2a3342014-06-11 17:40:22 -070097 setContentView(R.layout.activity_calculator);
Justin Klaassen4b3af052014-05-27 17:53:10 -070098
99 mFormulaEditText = (CalculatorEditText) findViewById(R.id.formula);
100 mResultEditText = (CalculatorEditText) findViewById(R.id.result);
101
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100102 mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
103
Justin Klaassen4b3af052014-05-27 17:53:10 -0700104 mDeleteButton = findViewById(R.id.del);
105 mClearButton = findViewById(R.id.clr);
106
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700107 savedInstanceState = savedInstanceState == null ? Bundle.EMPTY : savedInstanceState;
Justin Klaassen741471e2014-06-11 09:43:44 -0700108 setState(CalculatorState.values()[savedInstanceState.getInt(
109 CALCULATOR_ACTIVITY_CURRENT_STATE, CalculatorState.INPUT.ordinal())]);
110 mEvaluator = new CalculatorExpressionEvaluator(this);
111
112 mFormulaEditText.setEditableFactory(mFormulaEditableFactory);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700113 mFormulaEditText.addTextChangedListener(mFormulaTextWatcher);
Justin Klaassenfed941a2014-06-09 18:42:40 +0100114 mFormulaEditText.setOnTextSizeChangeListener(this);
115
Justin Klaassen4b3af052014-05-27 17:53:10 -0700116 mDeleteButton.setOnLongClickListener(this);
117 }
118
119 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700120 protected void onSaveInstanceState(Bundle outState) {
121 super.onSaveInstanceState(outState);
122 outState.putInt(CALCULATOR_ACTIVITY_CURRENT_STATE, mCurrentState.ordinal());
123 }
124
125 private void setState(CalculatorState state) {
126 if (mCurrentState != state) {
127 mCurrentState = state;
128
129 if (state == CalculatorState.RESULT || state == CalculatorState.ERROR) {
130 mDeleteButton.setVisibility(View.GONE);
131 mClearButton.setVisibility(View.VISIBLE);
132 } else {
133 mDeleteButton.setVisibility(View.VISIBLE);
134 mClearButton.setVisibility(View.GONE);
135 }
136
137 if (state == CalculatorState.ERROR) {
138 final int errorColor = getResources().getColor(R.color.calculator_error_color);
139 mFormulaEditText.setTextColor(errorColor);
140 mResultEditText.setTextColor(errorColor);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700141 getWindow().setStatusBarColor(errorColor);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700142 } else {
143 mFormulaEditText.setTextColor(
144 getResources().getColor(R.color.display_formula_text_color));
145 mResultEditText.setTextColor(
146 getResources().getColor(R.color.display_result_text_color));
Justin Klaassen8fff1442014-06-19 10:43:29 -0700147 getWindow().setStatusBarColor(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700148 getResources().getColor(R.color.calculator_accent_color));
149 }
150 }
151 }
152
153 @Override
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100154 public void onBackPressed() {
155 if (mPadViewPager == null || mPadViewPager.getCurrentItem() == 0) {
156 // If the user is currently looking at the first pad (or the pad is not paged),
157 // allow the system to handle the Back button.
158 super.onBackPressed();
159 } else {
160 // Otherwise, select the previous pad.
161 mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
162 }
163 }
164
165 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700166 public void onUserInteraction() {
167 super.onUserInteraction();
168
169 // If there's an animation in progress, cancel it so the user interaction can be handled
170 // immediately.
171 if (mCurrentAnimator != null) {
172 mCurrentAnimator.cancel();
173 }
174 }
175
176 public void onButtonClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700177 mCurrentButton = view;
178
Justin Klaassen4b3af052014-05-27 17:53:10 -0700179 switch (view.getId()) {
180 case R.id.eq:
Justin Klaassen5a3bbad2014-06-11 09:47:12 -0700181 if (mCurrentState == CalculatorState.INPUT) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700182 setState(CalculatorState.EVALUATE);
183 mEvaluator.evaluate(mFormulaEditText.getText(), this);
184 }
185 break;
186 case R.id.del:
187 mFormulaEditText.dispatchKeyEvent(
188 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
189 break;
190 case R.id.clr:
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700191 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700192 break;
193 case R.id.fun_cos:
194 case R.id.fun_ln:
195 case R.id.fun_log:
196 case R.id.fun_sin:
197 case R.id.fun_tan:
198 // add left paren after functions
199 mFormulaEditText.append(((Button) view).getText() + "(");
200 break;
201 default:
202 mFormulaEditText.append(((Button) view).getText());
203 break;
204 }
205 }
206
207 @Override
208 public boolean onLongClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700209 mCurrentButton = view;
210
Justin Klaassen4b3af052014-05-27 17:53:10 -0700211 if (view.getId() == R.id.del) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700212 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700213 return true;
214 }
215 return false;
216 }
217
218 @Override
219 public void onEvaluate(String expr, String result, String error) {
220 if (mCurrentState == CalculatorState.INPUT) {
221 mResultEditText.setText(result);
222 } else if (!TextUtils.isEmpty(error)) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700223 onError(error);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700224 } else if (!TextUtils.isEmpty(result)) {
225 onResult(result);
226 } else if (mCurrentState == CalculatorState.EVALUATE) {
227 // The current expression cannot be evaluated -> return to the input state.
228 setState(CalculatorState.INPUT);
229 }
230 }
231
Justin Klaassenfed941a2014-06-09 18:42:40 +0100232 @Override
233 public void onTextSizeChanged(final TextView textView, float oldSize) {
234 if (mCurrentState != CalculatorState.INPUT) {
235 // Only animate text changes that occur from user input.
236 return;
237 }
238
239 // Calculate the values needed to perform the scale and translation animations,
240 // maintaining the same apparent baseline for the displayed text.
241 final float textScale = oldSize / textView.getTextSize();
242 final float translationX = (1.0f - textScale) *
243 (textView.getWidth() / 2.0f - textView.getPaddingEnd());
244 final float translationY = (1.0f - textScale) *
245 (textView.getHeight() / 2.0f - textView.getPaddingBottom());
246
247 final AnimatorSet animatorSet = new AnimatorSet();
248 animatorSet.playTogether(
249 ObjectAnimator.ofFloat(textView, View.SCALE_X, textScale, 1.0f),
250 ObjectAnimator.ofFloat(textView, View.SCALE_Y, textScale, 1.0f),
251 ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, translationX, 0.0f),
252 ObjectAnimator.ofFloat(textView, View.TRANSLATION_Y, translationY, 0.0f));
Justin Klaassen94db7202014-06-11 11:22:31 -0700253 animatorSet.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassenfed941a2014-06-09 18:42:40 +0100254 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
255 animatorSet.start();
256 }
257
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700258 private void reveal(View sourceView, int colorRes, AnimatorListener listener) {
Justin Klaassen8fff1442014-06-19 10:43:29 -0700259 final View displayView = findViewById(R.id.display);
260 final View decorView = getWindow().getDecorView();
261
262 final Rect displayRect = new Rect();
263 displayView.getGlobalVisibleRect(displayRect);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700264
265 // Make reveal cover the display and status bar.
266 final View revealView = new View(this);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700267 revealView.setBottom(displayRect.bottom);
268 revealView.setLeft(displayRect.left);
269 revealView.setRight(displayRect.right);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700270 revealView.setBackgroundColor(getResources().getColor(colorRes));
271
Justin Klaassen4b3af052014-05-27 17:53:10 -0700272 final int[] clearLocation = new int[2];
273 sourceView.getLocationInWindow(clearLocation);
274 clearLocation[0] += sourceView.getWidth() / 2;
275 clearLocation[1] += sourceView.getHeight() / 2;
276
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700277 final int revealCenterX = clearLocation[0] - revealView.getLeft();
278 final int revealCenterY = clearLocation[1] - revealView.getTop();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700279
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700280 final double x1_2 = Math.pow(revealView.getLeft() - revealCenterX, 2);
281 final double x2_2 = Math.pow(revealView.getRight() - revealCenterX, 2);
282 final double y_2 = Math.pow(revealView.getTop() - revealCenterY, 2);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700283 final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
284
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700285 final Animator revealAnimator =
286 ViewAnimationUtils.createCircularReveal(revealView,
ztenghui3d6ecaf2014-06-05 09:56:00 -0700287 revealCenterX, revealCenterY, 0.0f, revealRadius);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700288 revealAnimator.setDuration(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700289 getResources().getInteger(android.R.integer.config_longAnimTime));
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700290 revealAnimator.addListener(listener);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700291
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700292 final Animator alphaAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700293 alphaAnimator.setDuration(
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700294 getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassen4b3af052014-05-27 17:53:10 -0700295
Justin Klaassen8fff1442014-06-19 10:43:29 -0700296 final ViewGroupOverlay groupOverlay = (ViewGroupOverlay) decorView.getOverlay();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700297 final AnimatorSet animatorSet = new AnimatorSet();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700298 animatorSet.play(revealAnimator).before(alphaAnimator);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700299 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
300 animatorSet.addListener(new AnimatorListenerAdapter() {
301 @Override
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700302 public void onAnimationStart(Animator animation) {
Justin Klaassen8fff1442014-06-19 10:43:29 -0700303 groupOverlay.add(revealView);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700304 }
305
306 @Override
307 public void onAnimationEnd(Animator animator) {
Justin Klaassen8fff1442014-06-19 10:43:29 -0700308 groupOverlay.remove(revealView);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700309 mCurrentAnimator = null;
310 }
311 });
312
313 mCurrentAnimator = animatorSet;
314 animatorSet.start();
315 }
316
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700317 private void onClear() {
318 if (TextUtils.isEmpty(mFormulaEditText.getText())) {
319 return;
320 }
321
322 reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerAdapter() {
323 @Override
324 public void onAnimationEnd(Animator animation) {
325 mFormulaEditText.getEditableText().clear();
326 }
327 });
328 }
329
330 private void onError(final String error) {
331 reveal(mCurrentButton, R.color.calculator_error_color, new AnimatorListenerAdapter() {
332 @Override
333 public void onAnimationEnd(Animator animation) {
334 setState(CalculatorState.ERROR);
335 mResultEditText.setText(error);
336 }
337 });
338 }
339
Justin Klaassen4b3af052014-05-27 17:53:10 -0700340 private void onResult(final String result) {
341 // Calculate the values needed to perform the scale and translation animations,
342 // accounting for how the scale will affect the final position of the text.
343 final float resultScale =
344 mFormulaEditText.getVariableTextSize(result) / mResultEditText.getTextSize();
345 final float resultTranslationX = (1.0f - resultScale) *
346 (mResultEditText.getWidth() / 2.0f - mResultEditText.getPaddingEnd());
347 final float resultTranslationY = (1.0f - resultScale) *
348 (mResultEditText.getHeight() / 2.0f - mResultEditText.getPaddingBottom()) +
349 (mFormulaEditText.getBottom() - mResultEditText.getBottom()) +
350 (mResultEditText.getPaddingBottom() - mFormulaEditText.getPaddingBottom());
351 final float formulaTranslationY = -mFormulaEditText.getBottom();
352
353 // Use a value animator to fade to the final text color over the course of the animation.
354 final int resultTextColor = mResultEditText.getCurrentTextColor();
355 final int formulaTextColor = mFormulaEditText.getCurrentTextColor();
356 final ValueAnimator textColorAnimator =
357 ValueAnimator.ofObject(new ArgbEvaluator(), resultTextColor, formulaTextColor);
358 textColorAnimator.addUpdateListener(new AnimatorUpdateListener() {
359 @Override
360 public void onAnimationUpdate(ValueAnimator valueAnimator) {
361 mResultEditText.setTextColor((int) valueAnimator.getAnimatedValue());
362 }
363 });
364
365 final AnimatorSet animatorSet = new AnimatorSet();
366 animatorSet.playTogether(
367 textColorAnimator,
368 ObjectAnimator.ofFloat(mResultEditText, View.SCALE_X, resultScale),
369 ObjectAnimator.ofFloat(mResultEditText, View.SCALE_Y, resultScale),
370 ObjectAnimator.ofFloat(mResultEditText, View.TRANSLATION_X, resultTranslationX),
371 ObjectAnimator.ofFloat(mResultEditText, View.TRANSLATION_Y, resultTranslationY),
372 ObjectAnimator.ofFloat(mFormulaEditText, View.TRANSLATION_Y, formulaTranslationY));
373 animatorSet.setDuration(getResources().getInteger(android.R.integer.config_longAnimTime));
374 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
375 animatorSet.addListener(new AnimatorListenerAdapter() {
376 @Override
377 public void onAnimationStart(Animator animation) {
378 mResultEditText.setText(result);
379 }
380
381 @Override
382 public void onAnimationEnd(Animator animation) {
383 // Reset all of the values modified during the animation.
384 mResultEditText.setTextColor(resultTextColor);
385 mResultEditText.setScaleX(1.0f);
386 mResultEditText.setScaleY(1.0f);
387 mResultEditText.setTranslationX(0.0f);
388 mResultEditText.setTranslationY(0.0f);
389 mFormulaEditText.setTranslationY(0.0f);
390
391 // Finally update the formula to use the current result.
392 mFormulaEditText.setText(result);
393 setState(CalculatorState.RESULT);
394
395 mCurrentAnimator = null;
396 }
397 });
398
399 mCurrentAnimator = animatorSet;
400 animatorSet.start();
401 }
402}