blob: a2bc4fd1e136daea4a5d9ca892b5d28bcda4f91c [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;
Justin Klaassenfed941a2014-06-09 18:42:40 +010038import android.widget.TextView;
39
40import com.android.calculator2.CalculatorEditText.OnTextSizeChangeListener;
41import com.android.calculator2.CalculatorExpressionEvaluator.EvaluateCallback;
Justin Klaassen4b3af052014-05-27 17:53:10 -070042
43public class CalculatorActivity extends Activity
Justin Klaassenfed941a2014-06-09 18:42:40 +010044 implements OnTextSizeChangeListener, EvaluateCallback, OnLongClickListener {
Justin Klaassen4b3af052014-05-27 17:53:10 -070045
46 public static final String CALCULATOR_ACTIVITY_CURRENT_STATE =
47 CalculatorActivity.class.getSimpleName() + "_currentState";
48
49 private enum CalculatorState {
50 INPUT, EVALUATE, RESULT, ERROR
51 }
52
53 private final TextWatcher mFormulaTextWatcher = new TextWatcher() {
54 @Override
55 public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
56 }
57
58 @Override
59 public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
60 }
61
62 @Override
63 public void afterTextChanged(Editable editable) {
64 setState(CalculatorState.INPUT);
65 mEvaluator.evaluate(editable, CalculatorActivity.this);
66 }
67 };
68
Justin Klaassen741471e2014-06-11 09:43:44 -070069 private final Editable.Factory mFormulaEditableFactory = new Editable.Factory() {
70 @Override
71 public Editable newEditable(CharSequence source) {
72 return new CalculatorExpressionBuilder(CalculatorActivity.this, source,
73 mCurrentState == CalculatorState.INPUT);
74 }
75 };
76
Justin Klaassen4b3af052014-05-27 17:53:10 -070077 private CalculatorState mCurrentState;
78 private CalculatorExpressionEvaluator mEvaluator;
79
80 private CalculatorEditText mFormulaEditText;
81 private CalculatorEditText mResultEditText;
82
Justin Klaassen3b4d13d2014-06-06 18:18:37 +010083 private ViewPager mPadViewPager;
84
Justin Klaassen4b3af052014-05-27 17:53:10 -070085 private View mRevealView;
86 private View mDeleteButton;
87 private View mClearButton;
88
89 private Animator mCurrentAnimator;
90
91 @Override
92 protected void onCreate(Bundle savedInstanceState) {
93 super.onCreate(savedInstanceState);
94 setContentView(R.layout.activity_calculator);
95
Justin Klaassen741471e2014-06-11 09:43:44 -070096 if (savedInstanceState == null) {
97 savedInstanceState = Bundle.EMPTY;
98 }
Justin Klaassen4b3af052014-05-27 17:53:10 -070099
100 mFormulaEditText = (CalculatorEditText) findViewById(R.id.formula);
101 mResultEditText = (CalculatorEditText) findViewById(R.id.result);
102
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100103 mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
104
Justin Klaassen4b3af052014-05-27 17:53:10 -0700105 mRevealView = findViewById(R.id.reveal);
106 mDeleteButton = findViewById(R.id.del);
107 mClearButton = findViewById(R.id.clr);
108
Justin Klaassen741471e2014-06-11 09:43:44 -0700109 setState(CalculatorState.values()[savedInstanceState.getInt(
110 CALCULATOR_ACTIVITY_CURRENT_STATE, CalculatorState.INPUT.ordinal())]);
111 mEvaluator = new CalculatorExpressionEvaluator(this);
112
113 mFormulaEditText.setEditableFactory(mFormulaEditableFactory);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700114 mFormulaEditText.addTextChangedListener(mFormulaTextWatcher);
Justin Klaassenfed941a2014-06-09 18:42:40 +0100115 mFormulaEditText.setOnTextSizeChangeListener(this);
116
Justin Klaassen4b3af052014-05-27 17:53:10 -0700117 mDeleteButton.setOnLongClickListener(this);
118 }
119
120 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700121 protected void onSaveInstanceState(Bundle outState) {
122 super.onSaveInstanceState(outState);
123 outState.putInt(CALCULATOR_ACTIVITY_CURRENT_STATE, mCurrentState.ordinal());
124 }
125
126 private void setState(CalculatorState state) {
127 if (mCurrentState != state) {
128 mCurrentState = state;
129
130 if (state == CalculatorState.RESULT || state == CalculatorState.ERROR) {
131 mDeleteButton.setVisibility(View.GONE);
132 mClearButton.setVisibility(View.VISIBLE);
133 } else {
134 mDeleteButton.setVisibility(View.VISIBLE);
135 mClearButton.setVisibility(View.GONE);
136 }
137
138 if (state == CalculatorState.ERROR) {
139 final int errorColor = getResources().getColor(R.color.calculator_error_color);
140 mFormulaEditText.setTextColor(errorColor);
141 mResultEditText.setTextColor(errorColor);
142 getWindow().setStatusBarColor(errorColor);
143 } else {
144 mFormulaEditText.setTextColor(
145 getResources().getColor(R.color.display_formula_text_color));
146 mResultEditText.setTextColor(
147 getResources().getColor(R.color.display_result_text_color));
148 getWindow().setStatusBarColor(
149 getResources().getColor(R.color.calculator_accent_color));
150 }
151 }
152 }
153
154 @Override
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100155 public void onBackPressed() {
156 if (mPadViewPager == null || mPadViewPager.getCurrentItem() == 0) {
157 // If the user is currently looking at the first pad (or the pad is not paged),
158 // allow the system to handle the Back button.
159 super.onBackPressed();
160 } else {
161 // Otherwise, select the previous pad.
162 mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
163 }
164 }
165
166 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700167 public void onUserInteraction() {
168 super.onUserInteraction();
169
170 // If there's an animation in progress, cancel it so the user interaction can be handled
171 // immediately.
172 if (mCurrentAnimator != null) {
173 mCurrentAnimator.cancel();
174 }
175 }
176
177 public void onButtonClick(View view) {
178 switch (view.getId()) {
179 case R.id.eq:
180 if (mCurrentState != CalculatorState.INPUT) {
181 mFormulaEditText.getEditableText().clear();
182 } else {
183 setState(CalculatorState.EVALUATE);
184 mEvaluator.evaluate(mFormulaEditText.getText(), this);
185 }
186 break;
187 case R.id.del:
188 mFormulaEditText.dispatchKeyEvent(
189 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
190 break;
191 case R.id.clr:
192 onClear(view);
193 break;
194 case R.id.fun_cos:
195 case R.id.fun_ln:
196 case R.id.fun_log:
197 case R.id.fun_sin:
198 case R.id.fun_tan:
199 // add left paren after functions
200 mFormulaEditText.append(((Button) view).getText() + "(");
201 break;
202 default:
203 mFormulaEditText.append(((Button) view).getText());
204 break;
205 }
206 }
207
208 @Override
209 public boolean onLongClick(View view) {
210 if (view.getId() == R.id.del) {
211 onClear(view);
212 return true;
213 }
214 return false;
215 }
216
217 @Override
218 public void onEvaluate(String expr, String result, String error) {
219 if (mCurrentState == CalculatorState.INPUT) {
220 mResultEditText.setText(result);
221 } else if (!TextUtils.isEmpty(error)) {
222 setState(CalculatorState.ERROR);
223 mResultEditText.setText(error);
224 } 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));
253 animatorSet.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime));
254 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
255 animatorSet.start();
256 }
257
Justin Klaassen4b3af052014-05-27 17:53:10 -0700258 private void onClear(View sourceView) {
259 final int[] clearLocation = new int[2];
260 sourceView.getLocationInWindow(clearLocation);
261 clearLocation[0] += sourceView.getWidth() / 2;
262 clearLocation[1] += sourceView.getHeight() / 2;
263
264 final int[] revealLocation = new int[2];
265 mRevealView.getLocationInWindow(revealLocation);
266
267 final int revealCenterX = clearLocation[0] - revealLocation[0];
268 final int revealCenterY = clearLocation[1] - revealLocation[1];
269
270 final double x1_2 = Math.pow(mRevealView.getLeft() - revealCenterX, 2);
271 final double x2_2 = Math.pow(mRevealView.getRight() - revealCenterX, 2);
272 final double y_2 = Math.pow(mRevealView.getTop() - revealCenterY, 2);
273 final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
274
ztenghui3d6ecaf2014-06-05 09:56:00 -0700275 final Animator clearAnimator =
276 ViewAnimationUtils.createCircularReveal(mRevealView,
277 revealCenterX, revealCenterY, 0.0f, revealRadius);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700278 clearAnimator.setDuration(
279 getResources().getInteger(android.R.integer.config_longAnimTime));
280 clearAnimator.addListener(new AnimatorListenerAdapter() {
281 @Override
282 public void onAnimationEnd(Animator animation) {
283 // Clear the formula after the reveal is finished, but before it's faded out.
284 mFormulaEditText.getEditableText().clear();
285 }
286 });
287
288 final Animator alphaAnimator = ObjectAnimator.ofFloat(mRevealView, View.ALPHA, 0.0f);
289 alphaAnimator.setDuration(
290 getResources().getInteger(android.R.integer.config_shortAnimTime));
291
292 final AnimatorSet animatorSet = new AnimatorSet();
293 animatorSet.play(clearAnimator).before(alphaAnimator);
294 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
295 animatorSet.addListener(new AnimatorListenerAdapter() {
296 @Override
297 public void onAnimationStart(Animator animator) {
298 mRevealView.setAlpha(1.0f);
299 mRevealView.setVisibility(View.VISIBLE);
300 }
301
302 @Override
303 public void onAnimationEnd(Animator animator) {
304 mRevealView.setVisibility(View.GONE);
305 mCurrentAnimator = null;
306 }
307 });
308
309 mCurrentAnimator = animatorSet;
310 animatorSet.start();
311 }
312
313 private void onResult(final String result) {
314 // Calculate the values needed to perform the scale and translation animations,
315 // accounting for how the scale will affect the final position of the text.
316 final float resultScale =
317 mFormulaEditText.getVariableTextSize(result) / mResultEditText.getTextSize();
318 final float resultTranslationX = (1.0f - resultScale) *
319 (mResultEditText.getWidth() / 2.0f - mResultEditText.getPaddingEnd());
320 final float resultTranslationY = (1.0f - resultScale) *
321 (mResultEditText.getHeight() / 2.0f - mResultEditText.getPaddingBottom()) +
322 (mFormulaEditText.getBottom() - mResultEditText.getBottom()) +
323 (mResultEditText.getPaddingBottom() - mFormulaEditText.getPaddingBottom());
324 final float formulaTranslationY = -mFormulaEditText.getBottom();
325
326 // Use a value animator to fade to the final text color over the course of the animation.
327 final int resultTextColor = mResultEditText.getCurrentTextColor();
328 final int formulaTextColor = mFormulaEditText.getCurrentTextColor();
329 final ValueAnimator textColorAnimator =
330 ValueAnimator.ofObject(new ArgbEvaluator(), resultTextColor, formulaTextColor);
331 textColorAnimator.addUpdateListener(new AnimatorUpdateListener() {
332 @Override
333 public void onAnimationUpdate(ValueAnimator valueAnimator) {
334 mResultEditText.setTextColor((int) valueAnimator.getAnimatedValue());
335 }
336 });
337
338 final AnimatorSet animatorSet = new AnimatorSet();
339 animatorSet.playTogether(
340 textColorAnimator,
341 ObjectAnimator.ofFloat(mResultEditText, View.SCALE_X, resultScale),
342 ObjectAnimator.ofFloat(mResultEditText, View.SCALE_Y, resultScale),
343 ObjectAnimator.ofFloat(mResultEditText, View.TRANSLATION_X, resultTranslationX),
344 ObjectAnimator.ofFloat(mResultEditText, View.TRANSLATION_Y, resultTranslationY),
345 ObjectAnimator.ofFloat(mFormulaEditText, View.TRANSLATION_Y, formulaTranslationY));
346 animatorSet.setDuration(getResources().getInteger(android.R.integer.config_longAnimTime));
347 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
348 animatorSet.addListener(new AnimatorListenerAdapter() {
349 @Override
350 public void onAnimationStart(Animator animation) {
351 mResultEditText.setText(result);
352 }
353
354 @Override
355 public void onAnimationEnd(Animator animation) {
356 // Reset all of the values modified during the animation.
357 mResultEditText.setTextColor(resultTextColor);
358 mResultEditText.setScaleX(1.0f);
359 mResultEditText.setScaleY(1.0f);
360 mResultEditText.setTranslationX(0.0f);
361 mResultEditText.setTranslationY(0.0f);
362 mFormulaEditText.setTranslationY(0.0f);
363
364 // Finally update the formula to use the current result.
365 mFormulaEditText.setText(result);
366 setState(CalculatorState.RESULT);
367
368 mCurrentAnimator = null;
369 }
370 });
371
372 mCurrentAnimator = animatorSet;
373 animatorSet.start();
374 }
375}