| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.calculator2; |
| |
| import android.animation.Animator; |
| import android.animation.Animator.AnimatorListener; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.AnimatorSet; |
| import android.animation.ArgbEvaluator; |
| import android.animation.ObjectAnimator; |
| import android.animation.ValueAnimator; |
| import android.animation.ValueAnimator.AnimatorUpdateListener; |
| import android.app.Activity; |
| import android.graphics.Rect; |
| import android.os.Bundle; |
| import android.support.v4.view.ViewPager; |
| import android.text.Editable; |
| import android.text.TextUtils; |
| import android.text.TextWatcher; |
| import android.view.KeyEvent; |
| import android.view.View; |
| import android.view.View.OnLongClickListener; |
| import android.view.ViewAnimationUtils; |
| import android.view.ViewGroupOverlay; |
| import android.view.animation.AccelerateDecelerateInterpolator; |
| import android.widget.Button; |
| import android.widget.TextView; |
| |
| import com.android.calculator2.CalculatorEditText.OnTextSizeChangeListener; |
| import com.android.calculator2.CalculatorExpressionEvaluator.EvaluateCallback; |
| |
| public class CalculatorActivity extends Activity |
| implements OnTextSizeChangeListener, EvaluateCallback, OnLongClickListener { |
| |
| public static final String CALCULATOR_ACTIVITY_CURRENT_STATE = |
| CalculatorActivity.class.getSimpleName() + "_currentState"; |
| |
| private enum CalculatorState { |
| INPUT, EVALUATE, RESULT, ERROR |
| } |
| |
| private final TextWatcher mFormulaTextWatcher = new TextWatcher() { |
| @Override |
| public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { |
| } |
| |
| @Override |
| public void onTextChanged(CharSequence charSequence, int start, int count, int after) { |
| } |
| |
| @Override |
| public void afterTextChanged(Editable editable) { |
| setState(CalculatorState.INPUT); |
| mEvaluator.evaluate(editable, CalculatorActivity.this); |
| } |
| }; |
| |
| private final Editable.Factory mFormulaEditableFactory = new Editable.Factory() { |
| @Override |
| public Editable newEditable(CharSequence source) { |
| return new CalculatorExpressionBuilder(CalculatorActivity.this, source, |
| mCurrentState == CalculatorState.INPUT); |
| } |
| }; |
| |
| private CalculatorState mCurrentState; |
| private CalculatorExpressionEvaluator mEvaluator; |
| |
| private CalculatorEditText mFormulaEditText; |
| private CalculatorEditText mResultEditText; |
| |
| private ViewPager mPadViewPager; |
| |
| private View mDeleteButton; |
| private View mClearButton; |
| |
| private View mCurrentButton; |
| private Animator mCurrentAnimator; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setContentView(R.layout.activity_calculator); |
| |
| mFormulaEditText = (CalculatorEditText) findViewById(R.id.formula); |
| mResultEditText = (CalculatorEditText) findViewById(R.id.result); |
| |
| mPadViewPager = (ViewPager) findViewById(R.id.pad_pager); |
| |
| mDeleteButton = findViewById(R.id.del); |
| mClearButton = findViewById(R.id.clr); |
| |
| savedInstanceState = savedInstanceState == null ? Bundle.EMPTY : savedInstanceState; |
| setState(CalculatorState.values()[savedInstanceState.getInt( |
| CALCULATOR_ACTIVITY_CURRENT_STATE, CalculatorState.INPUT.ordinal())]); |
| mEvaluator = new CalculatorExpressionEvaluator(this); |
| |
| mFormulaEditText.setEditableFactory(mFormulaEditableFactory); |
| mFormulaEditText.addTextChangedListener(mFormulaTextWatcher); |
| mFormulaEditText.setOnTextSizeChangeListener(this); |
| |
| mDeleteButton.setOnLongClickListener(this); |
| } |
| |
| @Override |
| protected void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| outState.putInt(CALCULATOR_ACTIVITY_CURRENT_STATE, mCurrentState.ordinal()); |
| } |
| |
| private void setState(CalculatorState state) { |
| if (mCurrentState != state) { |
| mCurrentState = state; |
| |
| if (state == CalculatorState.RESULT || state == CalculatorState.ERROR) { |
| mDeleteButton.setVisibility(View.GONE); |
| mClearButton.setVisibility(View.VISIBLE); |
| } else { |
| mDeleteButton.setVisibility(View.VISIBLE); |
| mClearButton.setVisibility(View.GONE); |
| } |
| |
| if (state == CalculatorState.ERROR) { |
| final int errorColor = getResources().getColor(R.color.calculator_error_color); |
| mFormulaEditText.setTextColor(errorColor); |
| mResultEditText.setTextColor(errorColor); |
| getWindow().setStatusBarColor(errorColor); |
| } else { |
| mFormulaEditText.setTextColor( |
| getResources().getColor(R.color.display_formula_text_color)); |
| mResultEditText.setTextColor( |
| getResources().getColor(R.color.display_result_text_color)); |
| getWindow().setStatusBarColor( |
| getResources().getColor(R.color.calculator_accent_color)); |
| } |
| } |
| } |
| |
| @Override |
| public void onBackPressed() { |
| if (mPadViewPager == null || mPadViewPager.getCurrentItem() == 0) { |
| // If the user is currently looking at the first pad (or the pad is not paged), |
| // allow the system to handle the Back button. |
| super.onBackPressed(); |
| } else { |
| // Otherwise, select the previous pad. |
| mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1); |
| } |
| } |
| |
| @Override |
| public void onUserInteraction() { |
| super.onUserInteraction(); |
| |
| // If there's an animation in progress, cancel it so the user interaction can be handled |
| // immediately. |
| if (mCurrentAnimator != null) { |
| mCurrentAnimator.cancel(); |
| } |
| } |
| |
| public void onButtonClick(View view) { |
| mCurrentButton = view; |
| |
| switch (view.getId()) { |
| case R.id.eq: |
| if (mCurrentState == CalculatorState.INPUT) { |
| setState(CalculatorState.EVALUATE); |
| mEvaluator.evaluate(mFormulaEditText.getText(), this); |
| } |
| break; |
| case R.id.del: |
| mFormulaEditText.dispatchKeyEvent( |
| new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); |
| break; |
| case R.id.clr: |
| onClear(); |
| break; |
| case R.id.fun_cos: |
| case R.id.fun_ln: |
| case R.id.fun_log: |
| case R.id.fun_sin: |
| case R.id.fun_tan: |
| // add left paren after functions |
| mFormulaEditText.append(((Button) view).getText() + "("); |
| break; |
| default: |
| mFormulaEditText.append(((Button) view).getText()); |
| break; |
| } |
| } |
| |
| @Override |
| public boolean onLongClick(View view) { |
| mCurrentButton = view; |
| |
| if (view.getId() == R.id.del) { |
| onClear(); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public void onEvaluate(String expr, String result, String error) { |
| if (mCurrentState == CalculatorState.INPUT) { |
| mResultEditText.setText(result); |
| } else if (!TextUtils.isEmpty(error)) { |
| onError(error); |
| } else if (!TextUtils.isEmpty(result)) { |
| onResult(result); |
| } else if (mCurrentState == CalculatorState.EVALUATE) { |
| // The current expression cannot be evaluated -> return to the input state. |
| setState(CalculatorState.INPUT); |
| } |
| } |
| |
| @Override |
| public void onTextSizeChanged(final TextView textView, float oldSize) { |
| if (mCurrentState != CalculatorState.INPUT) { |
| // Only animate text changes that occur from user input. |
| return; |
| } |
| |
| // Calculate the values needed to perform the scale and translation animations, |
| // maintaining the same apparent baseline for the displayed text. |
| final float textScale = oldSize / textView.getTextSize(); |
| final float translationX = (1.0f - textScale) * |
| (textView.getWidth() / 2.0f - textView.getPaddingEnd()); |
| final float translationY = (1.0f - textScale) * |
| (textView.getHeight() / 2.0f - textView.getPaddingBottom()); |
| |
| final AnimatorSet animatorSet = new AnimatorSet(); |
| animatorSet.playTogether( |
| ObjectAnimator.ofFloat(textView, View.SCALE_X, textScale, 1.0f), |
| ObjectAnimator.ofFloat(textView, View.SCALE_Y, textScale, 1.0f), |
| ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, translationX, 0.0f), |
| ObjectAnimator.ofFloat(textView, View.TRANSLATION_Y, translationY, 0.0f)); |
| animatorSet.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime)); |
| animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); |
| animatorSet.start(); |
| } |
| |
| private void reveal(View sourceView, int colorRes, AnimatorListener listener) { |
| final View displayView = findViewById(R.id.display); |
| final View decorView = getWindow().getDecorView(); |
| |
| final Rect displayRect = new Rect(); |
| displayView.getGlobalVisibleRect(displayRect); |
| |
| // Make reveal cover the display and status bar. |
| final View revealView = new View(this); |
| revealView.setBottom(displayRect.bottom); |
| revealView.setLeft(displayRect.left); |
| revealView.setRight(displayRect.right); |
| revealView.setBackgroundColor(getResources().getColor(colorRes)); |
| |
| final int[] clearLocation = new int[2]; |
| sourceView.getLocationInWindow(clearLocation); |
| clearLocation[0] += sourceView.getWidth() / 2; |
| clearLocation[1] += sourceView.getHeight() / 2; |
| |
| final int revealCenterX = clearLocation[0] - revealView.getLeft(); |
| final int revealCenterY = clearLocation[1] - revealView.getTop(); |
| |
| final double x1_2 = Math.pow(revealView.getLeft() - revealCenterX, 2); |
| final double x2_2 = Math.pow(revealView.getRight() - revealCenterX, 2); |
| final double y_2 = Math.pow(revealView.getTop() - revealCenterY, 2); |
| final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2)); |
| |
| final Animator revealAnimator = |
| ViewAnimationUtils.createCircularReveal(revealView, |
| revealCenterX, revealCenterY, 0.0f, revealRadius); |
| revealAnimator.setDuration( |
| getResources().getInteger(android.R.integer.config_longAnimTime)); |
| revealAnimator.addListener(listener); |
| |
| final Animator alphaAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f); |
| alphaAnimator.setDuration( |
| getResources().getInteger(android.R.integer.config_mediumAnimTime)); |
| |
| final ViewGroupOverlay groupOverlay = (ViewGroupOverlay) decorView.getOverlay(); |
| final AnimatorSet animatorSet = new AnimatorSet(); |
| animatorSet.play(revealAnimator).before(alphaAnimator); |
| animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); |
| animatorSet.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationStart(Animator animation) { |
| groupOverlay.add(revealView); |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animator) { |
| groupOverlay.remove(revealView); |
| mCurrentAnimator = null; |
| } |
| }); |
| |
| mCurrentAnimator = animatorSet; |
| animatorSet.start(); |
| } |
| |
| private void onClear() { |
| if (TextUtils.isEmpty(mFormulaEditText.getText())) { |
| return; |
| } |
| |
| reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| mFormulaEditText.getEditableText().clear(); |
| } |
| }); |
| } |
| |
| private void onError(final String error) { |
| reveal(mCurrentButton, R.color.calculator_error_color, new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| setState(CalculatorState.ERROR); |
| mResultEditText.setText(error); |
| } |
| }); |
| } |
| |
| private void onResult(final String result) { |
| // Calculate the values needed to perform the scale and translation animations, |
| // accounting for how the scale will affect the final position of the text. |
| final float resultScale = |
| mFormulaEditText.getVariableTextSize(result) / mResultEditText.getTextSize(); |
| final float resultTranslationX = (1.0f - resultScale) * |
| (mResultEditText.getWidth() / 2.0f - mResultEditText.getPaddingEnd()); |
| final float resultTranslationY = (1.0f - resultScale) * |
| (mResultEditText.getHeight() / 2.0f - mResultEditText.getPaddingBottom()) + |
| (mFormulaEditText.getBottom() - mResultEditText.getBottom()) + |
| (mResultEditText.getPaddingBottom() - mFormulaEditText.getPaddingBottom()); |
| final float formulaTranslationY = -mFormulaEditText.getBottom(); |
| |
| // Use a value animator to fade to the final text color over the course of the animation. |
| final int resultTextColor = mResultEditText.getCurrentTextColor(); |
| final int formulaTextColor = mFormulaEditText.getCurrentTextColor(); |
| final ValueAnimator textColorAnimator = |
| ValueAnimator.ofObject(new ArgbEvaluator(), resultTextColor, formulaTextColor); |
| textColorAnimator.addUpdateListener(new AnimatorUpdateListener() { |
| @Override |
| public void onAnimationUpdate(ValueAnimator valueAnimator) { |
| mResultEditText.setTextColor((int) valueAnimator.getAnimatedValue()); |
| } |
| }); |
| |
| final AnimatorSet animatorSet = new AnimatorSet(); |
| animatorSet.playTogether( |
| textColorAnimator, |
| ObjectAnimator.ofFloat(mResultEditText, View.SCALE_X, resultScale), |
| ObjectAnimator.ofFloat(mResultEditText, View.SCALE_Y, resultScale), |
| ObjectAnimator.ofFloat(mResultEditText, View.TRANSLATION_X, resultTranslationX), |
| ObjectAnimator.ofFloat(mResultEditText, View.TRANSLATION_Y, resultTranslationY), |
| ObjectAnimator.ofFloat(mFormulaEditText, View.TRANSLATION_Y, formulaTranslationY)); |
| animatorSet.setDuration(getResources().getInteger(android.R.integer.config_longAnimTime)); |
| animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); |
| animatorSet.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationStart(Animator animation) { |
| mResultEditText.setText(result); |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| // Reset all of the values modified during the animation. |
| mResultEditText.setTextColor(resultTextColor); |
| mResultEditText.setScaleX(1.0f); |
| mResultEditText.setScaleY(1.0f); |
| mResultEditText.setTranslationX(0.0f); |
| mResultEditText.setTranslationY(0.0f); |
| mFormulaEditText.setTranslationY(0.0f); |
| |
| // Finally update the formula to use the current result. |
| mFormulaEditText.setText(result); |
| setState(CalculatorState.RESULT); |
| |
| mCurrentAnimator = null; |
| } |
| }); |
| |
| mCurrentAnimator = animatorSet; |
| animatorSet.start(); |
| } |
| } |