blob: a9837b7463d6c38964c7f92fd9f66e037fefc080 [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;
Justin Klaassen3b4d13d2014-06-06 18:18:37 +010028import android.support.v4.view.ViewPager;
Justin Klaassen4b3af052014-05-27 17:53:10 -070029import android.text.Editable;
30import android.text.TextUtils;
31import android.text.TextWatcher;
32import android.view.KeyEvent;
33import android.view.View;
ztenghui3d6ecaf2014-06-05 09:56:00 -070034import android.view.ViewAnimationUtils;
Justin Klaassen4b3af052014-05-27 17:53:10 -070035import android.view.View.OnLongClickListener;
36import android.view.animation.AccelerateDecelerateInterpolator;
37import android.widget.Button;
38
39public class CalculatorActivity extends Activity
40 implements CalculatorExpressionEvaluator.EvaluateCallback, OnLongClickListener {
41
42 public static final String CALCULATOR_ACTIVITY_CURRENT_STATE =
43 CalculatorActivity.class.getSimpleName() + "_currentState";
44
45 private enum CalculatorState {
46 INPUT, EVALUATE, RESULT, ERROR
47 }
48
49 private final TextWatcher mFormulaTextWatcher = new TextWatcher() {
50 @Override
51 public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
52 }
53
54 @Override
55 public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
56 }
57
58 @Override
59 public void afterTextChanged(Editable editable) {
60 setState(CalculatorState.INPUT);
61 mEvaluator.evaluate(editable, CalculatorActivity.this);
62 }
63 };
64
65 private CalculatorState mCurrentState;
66 private CalculatorExpressionEvaluator mEvaluator;
67
68 private CalculatorEditText mFormulaEditText;
69 private CalculatorEditText mResultEditText;
70
Justin Klaassen3b4d13d2014-06-06 18:18:37 +010071 private ViewPager mPadViewPager;
72
Justin Klaassen4b3af052014-05-27 17:53:10 -070073 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
Justin Klaassen3b4d13d2014-06-06 18:18:37 +010090 mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
91
Justin Klaassen4b3af052014-05-27 17:53:10 -070092 mRevealView = findViewById(R.id.reveal);
93 mDeleteButton = findViewById(R.id.del);
94 mClearButton = findViewById(R.id.clr);
95
96 mFormulaEditText.setEditableFactory(new CalculatorExpressionBuilder.Factory(this));
97 mFormulaEditText.addTextChangedListener(mFormulaTextWatcher);
98 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
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100143 public void onBackPressed() {
144 if (mPadViewPager == null || mPadViewPager.getCurrentItem() == 0) {
145 // If the user is currently looking at the first pad (or the pad is not paged),
146 // allow the system to handle the Back button.
147 super.onBackPressed();
148 } else {
149 // Otherwise, select the previous pad.
150 mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
151 }
152 }
153
154 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700155 public void onUserInteraction() {
156 super.onUserInteraction();
157
158 // If there's an animation in progress, cancel it so the user interaction can be handled
159 // immediately.
160 if (mCurrentAnimator != null) {
161 mCurrentAnimator.cancel();
162 }
163 }
164
165 public void onButtonClick(View view) {
166 switch (view.getId()) {
167 case R.id.eq:
168 if (mCurrentState != CalculatorState.INPUT) {
169 mFormulaEditText.getEditableText().clear();
170 } else {
171 setState(CalculatorState.EVALUATE);
172 mEvaluator.evaluate(mFormulaEditText.getText(), this);
173 }
174 break;
175 case R.id.del:
176 mFormulaEditText.dispatchKeyEvent(
177 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
178 break;
179 case R.id.clr:
180 onClear(view);
181 break;
182 case R.id.fun_cos:
183 case R.id.fun_ln:
184 case R.id.fun_log:
185 case R.id.fun_sin:
186 case R.id.fun_tan:
187 // add left paren after functions
188 mFormulaEditText.append(((Button) view).getText() + "(");
189 break;
190 default:
191 mFormulaEditText.append(((Button) view).getText());
192 break;
193 }
194 }
195
196 @Override
197 public boolean onLongClick(View view) {
198 if (view.getId() == R.id.del) {
199 onClear(view);
200 return true;
201 }
202 return false;
203 }
204
205 @Override
206 public void onEvaluate(String expr, String result, String error) {
207 if (mCurrentState == CalculatorState.INPUT) {
208 mResultEditText.setText(result);
209 } else if (!TextUtils.isEmpty(error)) {
210 setState(CalculatorState.ERROR);
211 mResultEditText.setText(error);
212 } else if (!TextUtils.isEmpty(result)) {
213 onResult(result);
214 } else if (mCurrentState == CalculatorState.EVALUATE) {
215 // The current expression cannot be evaluated -> return to the input state.
216 setState(CalculatorState.INPUT);
217 }
218 }
219
220 private void onClear(View sourceView) {
221 final int[] clearLocation = new int[2];
222 sourceView.getLocationInWindow(clearLocation);
223 clearLocation[0] += sourceView.getWidth() / 2;
224 clearLocation[1] += sourceView.getHeight() / 2;
225
226 final int[] revealLocation = new int[2];
227 mRevealView.getLocationInWindow(revealLocation);
228
229 final int revealCenterX = clearLocation[0] - revealLocation[0];
230 final int revealCenterY = clearLocation[1] - revealLocation[1];
231
232 final double x1_2 = Math.pow(mRevealView.getLeft() - revealCenterX, 2);
233 final double x2_2 = Math.pow(mRevealView.getRight() - revealCenterX, 2);
234 final double y_2 = Math.pow(mRevealView.getTop() - revealCenterY, 2);
235 final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
236
ztenghui3d6ecaf2014-06-05 09:56:00 -0700237 final Animator clearAnimator =
238 ViewAnimationUtils.createCircularReveal(mRevealView,
239 revealCenterX, revealCenterY, 0.0f, revealRadius);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700240 clearAnimator.setDuration(
241 getResources().getInteger(android.R.integer.config_longAnimTime));
242 clearAnimator.addListener(new AnimatorListenerAdapter() {
243 @Override
244 public void onAnimationEnd(Animator animation) {
245 // Clear the formula after the reveal is finished, but before it's faded out.
246 mFormulaEditText.getEditableText().clear();
247 }
248 });
249
250 final Animator alphaAnimator = ObjectAnimator.ofFloat(mRevealView, View.ALPHA, 0.0f);
251 alphaAnimator.setDuration(
252 getResources().getInteger(android.R.integer.config_shortAnimTime));
253
254 final AnimatorSet animatorSet = new AnimatorSet();
255 animatorSet.play(clearAnimator).before(alphaAnimator);
256 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
257 animatorSet.addListener(new AnimatorListenerAdapter() {
258 @Override
259 public void onAnimationStart(Animator animator) {
260 mRevealView.setAlpha(1.0f);
261 mRevealView.setVisibility(View.VISIBLE);
262 }
263
264 @Override
265 public void onAnimationEnd(Animator animator) {
266 mRevealView.setVisibility(View.GONE);
267 mCurrentAnimator = null;
268 }
269 });
270
271 mCurrentAnimator = animatorSet;
272 animatorSet.start();
273 }
274
275 private void onResult(final String result) {
276 // Calculate the values needed to perform the scale and translation animations,
277 // accounting for how the scale will affect the final position of the text.
278 final float resultScale =
279 mFormulaEditText.getVariableTextSize(result) / mResultEditText.getTextSize();
280 final float resultTranslationX = (1.0f - resultScale) *
281 (mResultEditText.getWidth() / 2.0f - mResultEditText.getPaddingEnd());
282 final float resultTranslationY = (1.0f - resultScale) *
283 (mResultEditText.getHeight() / 2.0f - mResultEditText.getPaddingBottom()) +
284 (mFormulaEditText.getBottom() - mResultEditText.getBottom()) +
285 (mResultEditText.getPaddingBottom() - mFormulaEditText.getPaddingBottom());
286 final float formulaTranslationY = -mFormulaEditText.getBottom();
287
288 // Use a value animator to fade to the final text color over the course of the animation.
289 final int resultTextColor = mResultEditText.getCurrentTextColor();
290 final int formulaTextColor = mFormulaEditText.getCurrentTextColor();
291 final ValueAnimator textColorAnimator =
292 ValueAnimator.ofObject(new ArgbEvaluator(), resultTextColor, formulaTextColor);
293 textColorAnimator.addUpdateListener(new AnimatorUpdateListener() {
294 @Override
295 public void onAnimationUpdate(ValueAnimator valueAnimator) {
296 mResultEditText.setTextColor((int) valueAnimator.getAnimatedValue());
297 }
298 });
299
300 final AnimatorSet animatorSet = new AnimatorSet();
301 animatorSet.playTogether(
302 textColorAnimator,
303 ObjectAnimator.ofFloat(mResultEditText, View.SCALE_X, resultScale),
304 ObjectAnimator.ofFloat(mResultEditText, View.SCALE_Y, resultScale),
305 ObjectAnimator.ofFloat(mResultEditText, View.TRANSLATION_X, resultTranslationX),
306 ObjectAnimator.ofFloat(mResultEditText, View.TRANSLATION_Y, resultTranslationY),
307 ObjectAnimator.ofFloat(mFormulaEditText, View.TRANSLATION_Y, formulaTranslationY));
308 animatorSet.setDuration(getResources().getInteger(android.R.integer.config_longAnimTime));
309 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
310 animatorSet.addListener(new AnimatorListenerAdapter() {
311 @Override
312 public void onAnimationStart(Animator animation) {
313 mResultEditText.setText(result);
314 }
315
316 @Override
317 public void onAnimationEnd(Animator animation) {
318 // Reset all of the values modified during the animation.
319 mResultEditText.setTextColor(resultTextColor);
320 mResultEditText.setScaleX(1.0f);
321 mResultEditText.setScaleY(1.0f);
322 mResultEditText.setTranslationX(0.0f);
323 mResultEditText.setTranslationY(0.0f);
324 mFormulaEditText.setTranslationY(0.0f);
325
326 // Finally update the formula to use the current result.
327 mFormulaEditText.setText(result);
328 setState(CalculatorState.RESULT);
329
330 mCurrentAnimator = null;
331 }
332 });
333
334 mCurrentAnimator = animatorSet;
335 animatorSet.start();
336 }
337}