blob: f368ce832d08cdad35ca6bb695b8cfea0fbc8042 [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;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.ArgbEvaluator;
23import android.animation.ObjectAnimator;
24import android.animation.ValueAnimator;
25import android.animation.ValueAnimator.AnimatorUpdateListener;
26import android.app.Activity;
27import android.os.Bundle;
28import android.text.Editable;
29import android.text.TextUtils;
30import android.text.TextWatcher;
31import android.view.KeyEvent;
32import android.view.View;
ztenghui3d6ecaf2014-06-05 09:56:00 -070033import android.view.ViewAnimationUtils;
Justin Klaassen4b3af052014-05-27 17:53:10 -070034import android.view.View.OnLongClickListener;
35import android.view.animation.AccelerateDecelerateInterpolator;
36import android.widget.Button;
37
38public class CalculatorActivity extends Activity
39 implements CalculatorExpressionEvaluator.EvaluateCallback, OnLongClickListener {
40
41 public static final String CALCULATOR_ACTIVITY_CURRENT_STATE =
42 CalculatorActivity.class.getSimpleName() + "_currentState";
43
44 private enum CalculatorState {
45 INPUT, EVALUATE, RESULT, ERROR
46 }
47
48 private final TextWatcher mFormulaTextWatcher = new TextWatcher() {
49 @Override
50 public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
51 }
52
53 @Override
54 public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
55 }
56
57 @Override
58 public void afterTextChanged(Editable editable) {
59 setState(CalculatorState.INPUT);
60 mEvaluator.evaluate(editable, CalculatorActivity.this);
61 }
62 };
63
64 private CalculatorState mCurrentState;
65 private CalculatorExpressionEvaluator mEvaluator;
66
67 private CalculatorEditText mFormulaEditText;
68 private CalculatorEditText mResultEditText;
69
70 private View mRevealView;
71 private View mDeleteButton;
72 private View mClearButton;
73
74 private Animator mCurrentAnimator;
75
76 @Override
77 protected void onCreate(Bundle savedInstanceState) {
78 super.onCreate(savedInstanceState);
79 setContentView(R.layout.activity_calculator);
80
81 mCurrentState = CalculatorState.INPUT;
82 mEvaluator = new CalculatorExpressionEvaluator(this);
83
84 mFormulaEditText = (CalculatorEditText) findViewById(R.id.formula);
85 mResultEditText = (CalculatorEditText) findViewById(R.id.result);
86
87 mRevealView = findViewById(R.id.reveal);
88 mDeleteButton = findViewById(R.id.del);
89 mClearButton = findViewById(R.id.clr);
90
91 mFormulaEditText.setEditableFactory(new CalculatorExpressionBuilder.Factory(this));
92 mFormulaEditText.addTextChangedListener(mFormulaTextWatcher);
93 mDeleteButton.setOnLongClickListener(this);
94 }
95
96 @Override
97 protected void onRestoreInstanceState(Bundle savedInstanceState) {
98 super.onRestoreInstanceState(savedInstanceState);
99 setState(CalculatorState.values()[savedInstanceState.getInt(
100 CALCULATOR_ACTIVITY_CURRENT_STATE, CalculatorState.INPUT.ordinal())]);
101 }
102
103 @Override
104 protected void onSaveInstanceState(Bundle outState) {
105 super.onSaveInstanceState(outState);
106 outState.putInt(CALCULATOR_ACTIVITY_CURRENT_STATE, mCurrentState.ordinal());
107 }
108
109 private void setState(CalculatorState state) {
110 if (mCurrentState != state) {
111 mCurrentState = state;
112
113 if (state == CalculatorState.RESULT || state == CalculatorState.ERROR) {
114 mDeleteButton.setVisibility(View.GONE);
115 mClearButton.setVisibility(View.VISIBLE);
116 } else {
117 mDeleteButton.setVisibility(View.VISIBLE);
118 mClearButton.setVisibility(View.GONE);
119 }
120
121 if (state == CalculatorState.ERROR) {
122 final int errorColor = getResources().getColor(R.color.calculator_error_color);
123 mFormulaEditText.setTextColor(errorColor);
124 mResultEditText.setTextColor(errorColor);
125 getWindow().setStatusBarColor(errorColor);
126 } else {
127 mFormulaEditText.setTextColor(
128 getResources().getColor(R.color.display_formula_text_color));
129 mResultEditText.setTextColor(
130 getResources().getColor(R.color.display_result_text_color));
131 getWindow().setStatusBarColor(
132 getResources().getColor(R.color.calculator_accent_color));
133 }
134 }
135 }
136
137 @Override
138 public void onUserInteraction() {
139 super.onUserInteraction();
140
141 // If there's an animation in progress, cancel it so the user interaction can be handled
142 // immediately.
143 if (mCurrentAnimator != null) {
144 mCurrentAnimator.cancel();
145 }
146 }
147
148 public void onButtonClick(View view) {
149 switch (view.getId()) {
150 case R.id.eq:
151 if (mCurrentState != CalculatorState.INPUT) {
152 mFormulaEditText.getEditableText().clear();
153 } else {
154 setState(CalculatorState.EVALUATE);
155 mEvaluator.evaluate(mFormulaEditText.getText(), this);
156 }
157 break;
158 case R.id.del:
159 mFormulaEditText.dispatchKeyEvent(
160 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
161 break;
162 case R.id.clr:
163 onClear(view);
164 break;
165 case R.id.fun_cos:
166 case R.id.fun_ln:
167 case R.id.fun_log:
168 case R.id.fun_sin:
169 case R.id.fun_tan:
170 // add left paren after functions
171 mFormulaEditText.append(((Button) view).getText() + "(");
172 break;
173 default:
174 mFormulaEditText.append(((Button) view).getText());
175 break;
176 }
177 }
178
179 @Override
180 public boolean onLongClick(View view) {
181 if (view.getId() == R.id.del) {
182 onClear(view);
183 return true;
184 }
185 return false;
186 }
187
188 @Override
189 public void onEvaluate(String expr, String result, String error) {
190 if (mCurrentState == CalculatorState.INPUT) {
191 mResultEditText.setText(result);
192 } else if (!TextUtils.isEmpty(error)) {
193 setState(CalculatorState.ERROR);
194 mResultEditText.setText(error);
195 } else if (!TextUtils.isEmpty(result)) {
196 onResult(result);
197 } else if (mCurrentState == CalculatorState.EVALUATE) {
198 // The current expression cannot be evaluated -> return to the input state.
199 setState(CalculatorState.INPUT);
200 }
201 }
202
203 private void onClear(View sourceView) {
204 final int[] clearLocation = new int[2];
205 sourceView.getLocationInWindow(clearLocation);
206 clearLocation[0] += sourceView.getWidth() / 2;
207 clearLocation[1] += sourceView.getHeight() / 2;
208
209 final int[] revealLocation = new int[2];
210 mRevealView.getLocationInWindow(revealLocation);
211
212 final int revealCenterX = clearLocation[0] - revealLocation[0];
213 final int revealCenterY = clearLocation[1] - revealLocation[1];
214
215 final double x1_2 = Math.pow(mRevealView.getLeft() - revealCenterX, 2);
216 final double x2_2 = Math.pow(mRevealView.getRight() - revealCenterX, 2);
217 final double y_2 = Math.pow(mRevealView.getTop() - revealCenterY, 2);
218 final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
219
ztenghui3d6ecaf2014-06-05 09:56:00 -0700220 final Animator clearAnimator =
221 ViewAnimationUtils.createCircularReveal(mRevealView,
222 revealCenterX, revealCenterY, 0.0f, revealRadius);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700223 clearAnimator.setDuration(
224 getResources().getInteger(android.R.integer.config_longAnimTime));
225 clearAnimator.addListener(new AnimatorListenerAdapter() {
226 @Override
227 public void onAnimationEnd(Animator animation) {
228 // Clear the formula after the reveal is finished, but before it's faded out.
229 mFormulaEditText.getEditableText().clear();
230 }
231 });
232
233 final Animator alphaAnimator = ObjectAnimator.ofFloat(mRevealView, View.ALPHA, 0.0f);
234 alphaAnimator.setDuration(
235 getResources().getInteger(android.R.integer.config_shortAnimTime));
236
237 final AnimatorSet animatorSet = new AnimatorSet();
238 animatorSet.play(clearAnimator).before(alphaAnimator);
239 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
240 animatorSet.addListener(new AnimatorListenerAdapter() {
241 @Override
242 public void onAnimationStart(Animator animator) {
243 mRevealView.setAlpha(1.0f);
244 mRevealView.setVisibility(View.VISIBLE);
245 }
246
247 @Override
248 public void onAnimationEnd(Animator animator) {
249 mRevealView.setVisibility(View.GONE);
250 mCurrentAnimator = null;
251 }
252 });
253
254 mCurrentAnimator = animatorSet;
255 animatorSet.start();
256 }
257
258 private void onResult(final String result) {
259 // Calculate the values needed to perform the scale and translation animations,
260 // accounting for how the scale will affect the final position of the text.
261 final float resultScale =
262 mFormulaEditText.getVariableTextSize(result) / mResultEditText.getTextSize();
263 final float resultTranslationX = (1.0f - resultScale) *
264 (mResultEditText.getWidth() / 2.0f - mResultEditText.getPaddingEnd());
265 final float resultTranslationY = (1.0f - resultScale) *
266 (mResultEditText.getHeight() / 2.0f - mResultEditText.getPaddingBottom()) +
267 (mFormulaEditText.getBottom() - mResultEditText.getBottom()) +
268 (mResultEditText.getPaddingBottom() - mFormulaEditText.getPaddingBottom());
269 final float formulaTranslationY = -mFormulaEditText.getBottom();
270
271 // Use a value animator to fade to the final text color over the course of the animation.
272 final int resultTextColor = mResultEditText.getCurrentTextColor();
273 final int formulaTextColor = mFormulaEditText.getCurrentTextColor();
274 final ValueAnimator textColorAnimator =
275 ValueAnimator.ofObject(new ArgbEvaluator(), resultTextColor, formulaTextColor);
276 textColorAnimator.addUpdateListener(new AnimatorUpdateListener() {
277 @Override
278 public void onAnimationUpdate(ValueAnimator valueAnimator) {
279 mResultEditText.setTextColor((int) valueAnimator.getAnimatedValue());
280 }
281 });
282
283 final AnimatorSet animatorSet = new AnimatorSet();
284 animatorSet.playTogether(
285 textColorAnimator,
286 ObjectAnimator.ofFloat(mResultEditText, View.SCALE_X, resultScale),
287 ObjectAnimator.ofFloat(mResultEditText, View.SCALE_Y, resultScale),
288 ObjectAnimator.ofFloat(mResultEditText, View.TRANSLATION_X, resultTranslationX),
289 ObjectAnimator.ofFloat(mResultEditText, View.TRANSLATION_Y, resultTranslationY),
290 ObjectAnimator.ofFloat(mFormulaEditText, View.TRANSLATION_Y, formulaTranslationY));
291 animatorSet.setDuration(getResources().getInteger(android.R.integer.config_longAnimTime));
292 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
293 animatorSet.addListener(new AnimatorListenerAdapter() {
294 @Override
295 public void onAnimationStart(Animator animation) {
296 mResultEditText.setText(result);
297 }
298
299 @Override
300 public void onAnimationEnd(Animator animation) {
301 // Reset all of the values modified during the animation.
302 mResultEditText.setTextColor(resultTextColor);
303 mResultEditText.setScaleX(1.0f);
304 mResultEditText.setScaleY(1.0f);
305 mResultEditText.setTranslationX(0.0f);
306 mResultEditText.setTranslationY(0.0f);
307 mFormulaEditText.setTranslationY(0.0f);
308
309 // Finally update the formula to use the current result.
310 mFormulaEditText.setText(result);
311 setState(CalculatorState.RESULT);
312
313 mCurrentAnimator = null;
314 }
315 });
316
317 mCurrentAnimator = animatorSet;
318 animatorSet.start();
319 }
320}