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