blob: 1600e3d0a45e960d3f4c5f7fbe849e897ac42119 [file] [log] [blame]
/*
* 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.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.os.Bundle;
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.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 CalculatorState mCurrentState;
private CalculatorExpressionEvaluator mEvaluator;
private CalculatorEditText mFormulaEditText;
private CalculatorEditText mResultEditText;
private View mRevealView;
private View mDeleteButton;
private View mClearButton;
private Animator mCurrentAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calculator);
mCurrentState = CalculatorState.INPUT;
mEvaluator = new CalculatorExpressionEvaluator(this);
mFormulaEditText = (CalculatorEditText) findViewById(R.id.formula);
mResultEditText = (CalculatorEditText) findViewById(R.id.result);
mRevealView = findViewById(R.id.reveal);
mDeleteButton = findViewById(R.id.del);
mClearButton = findViewById(R.id.clr);
mFormulaEditText.setEditableFactory(new CalculatorExpressionBuilder.Factory(this));
mFormulaEditText.addTextChangedListener(mFormulaTextWatcher);
mFormulaEditText.setOnTextSizeChangeListener(this);
mDeleteButton.setOnLongClickListener(this);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
setState(CalculatorState.values()[savedInstanceState.getInt(
CALCULATOR_ACTIVITY_CURRENT_STATE, CalculatorState.INPUT.ordinal())]);
}
@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 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) {
switch (view.getId()) {
case R.id.eq:
if (mCurrentState != CalculatorState.INPUT) {
mFormulaEditText.getEditableText().clear();
} else {
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(view);
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) {
if (view.getId() == R.id.del) {
onClear(view);
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)) {
setState(CalculatorState.ERROR);
mResultEditText.setText(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_shortAnimTime));
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.start();
}
private void onClear(View sourceView) {
final int[] clearLocation = new int[2];
sourceView.getLocationInWindow(clearLocation);
clearLocation[0] += sourceView.getWidth() / 2;
clearLocation[1] += sourceView.getHeight() / 2;
final int[] revealLocation = new int[2];
mRevealView.getLocationInWindow(revealLocation);
final int revealCenterX = clearLocation[0] - revealLocation[0];
final int revealCenterY = clearLocation[1] - revealLocation[1];
final double x1_2 = Math.pow(mRevealView.getLeft() - revealCenterX, 2);
final double x2_2 = Math.pow(mRevealView.getRight() - revealCenterX, 2);
final double y_2 = Math.pow(mRevealView.getTop() - revealCenterY, 2);
final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
final Animator clearAnimator = mRevealView.createRevealAnimator(
revealCenterX, revealCenterY, 0.0f, revealRadius);
clearAnimator.setDuration(
getResources().getInteger(android.R.integer.config_longAnimTime));
clearAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Clear the formula after the reveal is finished, but before it's faded out.
mFormulaEditText.getEditableText().clear();
}
});
final Animator alphaAnimator = ObjectAnimator.ofFloat(mRevealView, View.ALPHA, 0.0f);
alphaAnimator.setDuration(
getResources().getInteger(android.R.integer.config_shortAnimTime));
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(clearAnimator).before(alphaAnimator);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
mRevealView.setAlpha(1.0f);
mRevealView.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animator) {
mRevealView.setVisibility(View.GONE);
mCurrentAnimator = null;
}
});
mCurrentAnimator = animatorSet;
animatorSet.start();
}
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();
}
}