Java tutorial
/* * 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.example.gaurav.calculator; import android.app.Activity; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.view.KeyEvent; import android.view.View; import android.view.View.OnKeyListener; import android.view.View.OnLongClickListener; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.Button; import android.widget.RelativeLayout; import android.widget.TextView; import com.example.gaurav.calculator.CalculatorEditText.OnTextSizeChangeListener; import com.example.gaurav.calculator.CalculatorExpressionEvaluator.EvaluateCallback; import com.nineoldandroids.animation.AnimatorSet; import com.nineoldandroids.animation.ObjectAnimator; public abstract class Calculator extends Activity implements OnTextSizeChangeListener, EvaluateCallback, OnLongClickListener { private static final String NAME = Calculator.class.getName(); // instance state keys private static final String KEY_CURRENT_STATE = NAME + "_currentState"; private static final String KEY_CURRENT_EXPRESSION = NAME + "_currentExpres sion"; /** * Constant for an invalid resource id. */ public static final int INVALID_RES_ID = -1; protected 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, Calculator.this); } }; private final OnKeyListener mFormulaOnKeyListener = new OnKeyListener() { @Override public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { switch (keyCode) { case KeyEvent.KEYCODE_NUMPAD_ENTER: case KeyEvent.KEYCODE_ENTER: if (keyEvent.getAction() == KeyEvent.ACTION_UP) { mCurrentButton = mEqualButton; onEquals(); } // ignore all other actions return true; } return false; } }; private final Editable.Factory mFormulaEditableFactory = new Editable.Factory() { @Override public Editable newEditable(CharSequence source) { final boolean isEdited = mCurrentState == CalculatorState.INPUT || mCurrentState == CalculatorState.ERROR; return new CalculatorExpressionBuilder(source, mTokenizer, isEdited); } }; private CalculatorState mCurrentState; private CalculatorExpressionTokenizer mTokenizer; private CalculatorExpressionEvaluator mEvaluator; protected RelativeLayout mDisplayView; protected CalculatorEditText mFormulaEditText; protected CalculatorEditText mResultEditText; private NineOldViewPager mPadViewPager; private View mDeleteButton; private View mEqualButton; private View mClearButton; private View mCurrentButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_calculator); mDisplayView = (RelativeLayout) findViewById(R.id.display); mFormulaEditText = (CalculatorEditText) findViewById(R.id.formula); mResultEditText = (CalculatorEditText) findViewById(R.id.result); mPadViewPager = (NineOldViewPager) findViewById(R.id.pad_pager); mDeleteButton = findViewById(R.id.del); mClearButton = findViewById(R.id.clr); mEqualButton = findViewById(R.id.pad_numeric).findViewById(R.id.eq); if (mEqualButton == null || mEqualButton.getVisibility() != View.VISIBLE) { mEqualButton = findViewById(R.id.pad_operator).findViewById(R.id.eq); } mTokenizer = new CalculatorExpressionTokenizer(this); mEvaluator = new CalculatorExpressionEvaluator(mTokenizer); savedInstanceState = savedInstanceState == null ? Bundle.EMPTY : savedInstanceState; setState(CalculatorState.values()[savedInstanceState.getInt(KEY_CURRENT_STATE, CalculatorState.INPUT.ordinal())]); String keyCurrentExpr = savedInstanceState.getString(KEY_CURRENT_EXPRESSION); mFormulaEditText.setText(mTokenizer.getLocalizedExpression(keyCurrentExpr == null ? "" : keyCurrentExpr)); mEvaluator.evaluate(mFormulaEditText.getText(), this); mFormulaEditText.setEditableFactory(mFormulaEditableFactory); mFormulaEditText.addTextChangedListener(mFormulaTextWatcher); mFormulaEditText.setOnKeyListener(mFormulaOnKeyListener); mFormulaEditText.setOnTextSizeChangeListener(this); mDeleteButton.setOnLongClickListener(this); } @Override protected void onSaveInstanceState(@NonNull Bundle outState) { // If there's an animation in progress, cancel it first to ensure our state is up-to-date. cancelAnimation(); super.onSaveInstanceState(outState); outState.putInt(KEY_CURRENT_STATE, mCurrentState.ordinal()); outState.putString(KEY_CURRENT_EXPRESSION, mTokenizer.getNormalizedExpression(mFormulaEditText.getText().toString())); } protected 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); Utils.setStatusBarColorCompat(getWindow(), errorColor); } else { mFormulaEditText.setTextColor(getResources().getColor(R.color.display_formula_text_color)); mResultEditText.setTextColor(getResources().getColor(R.color.display_result_text_color)); Utils.setStatusBarColorCompat(getWindow(), 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 { cancelAnimation(); // 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. cancelAnimation(); } public void onButtonClick(View view) { mCurrentButton = view; switch (view.getId()) { case R.id.eq: onEquals(); break; case R.id.del: onDelete(); 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 parenthesis 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, int errorResourceId) { if (mCurrentState == CalculatorState.INPUT) { mResultEditText.setText(result); } else if (errorResourceId != INVALID_RES_ID) { onError(errorResourceId); } 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); } mFormulaEditText.requestFocus(); } @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 - ViewCompat.getPaddingEnd(textView)); final float translationY = (1.0f - textScale) * (textView.getHeight() / 2.0f - textView.getPaddingBottom()); final AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(ObjectAnimator.ofFloat(textView, "scaleX", textScale, 1.0f), ObjectAnimator.ofFloat(textView, "scaleY", textScale, 1.0f), ObjectAnimator.ofFloat(textView, "translationX", translationX, 0.0f), ObjectAnimator.ofFloat(textView, "translationY", translationY, 0.0f)); animatorSet.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime)); animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); animatorSet.start(); } private void onEquals() { if (mCurrentState == CalculatorState.INPUT) { setState(CalculatorState.EVALUATE); mEvaluator.evaluate(mFormulaEditText.getText(), this); } } private void onDelete() { // Delete works like backspace; remove the last character from the expression. final Editable formulaText = mFormulaEditText.getEditableText(); final int formulaLength = formulaText.length(); if (formulaLength > 0) { formulaText.delete(formulaLength - 1, formulaLength); } } abstract void cancelAnimation(); abstract void reveal(View sourceView, int colorRes, AnimatorListenerWrapper listener); private void onClear() { if (TextUtils.isEmpty(mFormulaEditText.getText())) { return; } reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerWrapper() { @Override public void onAnimationEnd() { mFormulaEditText.getEditableText().clear(); } }); } private void onError(final int errorResourceId) { if (mCurrentState != CalculatorState.EVALUATE) { // Only animate error on evaluate. mResultEditText.setText(errorResourceId); return; } reveal(mCurrentButton, R.color.calculator_error_color, new AnimatorListenerWrapper() { @Override public void onAnimationEnd() { setState(CalculatorState.ERROR); mResultEditText.setText(errorResourceId); } }); } abstract void onResult(final String result); }