summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2015-05-28 17:55:20 -0700
committerJustin Klaassen <justinklaassen@google.com>2015-05-29 16:19:59 -0700
commit4459516a2c116ddf80725d6a96a69186ccddc329 (patch)
tree7c606670ae5740cb05d08f4150a5ba2d44a28f05 /src
parent721ec84263a26b859c57eb9fb4eb66939fe94272 (diff)
downloadandroid_packages_apps_ExactCalculator-4459516a2c116ddf80725d6a96a69186ccddc329.tar.gz
android_packages_apps_ExactCalculator-4459516a2c116ddf80725d6a96a69186ccddc329.tar.bz2
android_packages_apps_ExactCalculator-4459516a2c116ddf80725d6a96a69186ccddc329.zip
Polish display and evaluate animation
Bug: 20915670 Bug: 21489377 - Adjust font metrics across all supported device configurations to support font scaling and min touch size requirements. - Support proper font scaling for non-scrollable results when performing the evaluate animation. - Remove restriction for only using 4/5 of the width of the result display (NOTE: the result's textSize must match the formula's minTextSize). - Add AlignedTextView base class to ensure formula/result padding is based on the displayed text's ascent/baseline. Change-Id: Id53e9bdc6e699fb05fdf331a6a472ecc170edf38
Diffstat (limited to 'src')
-rw-r--r--src/com/android/calculator2/AlignedTextView.java86
-rw-r--r--src/com/android/calculator2/Calculator.java167
-rw-r--r--src/com/android/calculator2/CalculatorResult.java81
-rw-r--r--src/com/android/calculator2/CalculatorText.java59
-rw-r--r--src/com/android/calculator2/Evaluator.java25
5 files changed, 236 insertions, 182 deletions
diff --git a/src/com/android/calculator2/AlignedTextView.java b/src/com/android/calculator2/AlignedTextView.java
new file mode 100644
index 0000000..1c4b78f
--- /dev/null
+++ b/src/com/android/calculator2/AlignedTextView.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+
+/**
+ * Extended {@link TextView} that supports ascent/baseline alignment.
+ */
+public class AlignedTextView extends TextView {
+
+ private static final String LATIN_CAPITAL_LETTER = "H";
+ private static final CharsetEncoder LATIN_CHARSET_ENCODER =
+ Charset.forName("ISO-8859-1").newEncoder();
+
+ // temporary rect for use during layout
+ private final Rect mTempRect = new Rect();
+
+ private int mTopPaddingOffset;
+ private int mBottomPaddingOffset;
+
+ public AlignedTextView(Context context) {
+ this(context, null /* attrs */);
+ }
+
+ public AlignedTextView(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.textViewStyle);
+ }
+
+ public AlignedTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ // Disable any included font padding by default.
+ setIncludeFontPadding(false);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ CharSequence text = getText();
+ if (TextUtils.isEmpty(text) || LATIN_CHARSET_ENCODER.canEncode(text)) {
+ // For latin text align to the default capital letter height.
+ text = LATIN_CAPITAL_LETTER;
+ }
+ getPaint().getTextBounds(text.toString(), 0, text.length(), mTempRect);
+
+ final Paint textPaint = getPaint();
+ mTopPaddingOffset = Math.min(getPaddingTop(),
+ (int) Math.floor(mTempRect.top - textPaint.ascent()));
+ mBottomPaddingOffset = Math.min(getPaddingBottom(),
+ (int) Math.floor(textPaint.descent()));
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public int getCompoundPaddingTop() {
+ return super.getCompoundPaddingTop() - mTopPaddingOffset;
+ }
+
+ @Override
+ public int getCompoundPaddingBottom() {
+ return super.getCompoundPaddingBottom() - mBottomPaddingOffset;
+ }
+}
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index 3a4ac6d..0970174 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 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.
@@ -17,8 +17,6 @@
// FIXME: Menu handling, particularly for cut/paste, is very ugly
// and not the way it was intended.
// Other menus are not handled brilliantly either.
-// TODO: Revisit handling of "Help" menu, so that it's more consistent
-// with our conventions.
// TODO: Better indication of when the result is known to be exact.
// TODO: Check and possibly fix accessability issues.
// TODO: Copy & more general paste in formula? Note that this requires
@@ -37,6 +35,7 @@ import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
@@ -51,6 +50,7 @@ import android.support.v4.view.ViewPager;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
+import android.util.Property;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.Menu;
@@ -110,10 +110,21 @@ public class Calculator extends Activity
// TODO: Possibly save a bit more information, e.g. its initial display string
// or most significant digit position, to speed up restart.
+ private final Property<TextView, Integer> TEXT_COLOR =
+ new Property<TextView, Integer>(Integer.class, "textColor") {
+ @Override
+ public Integer get(TextView textView) {
+ return textView.getCurrentTextColor();
+ }
+
+ @Override
+ public void set(TextView textView, Integer textColor) {
+ textView.setTextColor(textColor);
+ }
+ };
+
// We currently assume that the formula does not change out from under us in
// any way. We explicitly handle all input to the formula here.
- // TODO: Perhaps the formula should not be editable at all?
-
private final OnKeyListener mFormulaOnKeyListener = new OnKeyListener() {
@Override
public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
@@ -132,17 +143,17 @@ public class Calculator extends Activity
return true;
default:
final int raw = keyEvent.getKeyCharacterMap()
- .get(keyCode, keyEvent.getMetaState());
+ .get(keyCode, keyEvent.getMetaState());
if ((raw & KeyCharacterMap.COMBINING_ACCENT) != 0) {
return true; // discard
}
// Try to discard non-printing characters and the like.
// The user will have to explicitly delete other junk that gets past us.
if (Character.isIdentifierIgnorable(raw)
- || Character.isWhitespace(raw)) {
+ || Character.isWhitespace(raw)) {
return true;
}
- char c = (char)raw;
+ char c = (char) raw;
if (c == '=') {
mCurrentButton = mEqualButton;
onEquals();
@@ -168,7 +179,7 @@ public class Calculator extends Activity
private View mDisplayView;
private TextView mModeView;
private CalculatorText mFormulaText;
- private CalculatorResult mResult;
+ private CalculatorResult mResultText;
private ViewPager mPadViewPager;
private View mDeleteButton;
@@ -200,7 +211,7 @@ public class Calculator extends Activity
mDisplayView = findViewById(R.id.display);
mModeView = (TextView) findViewById(R.id.mode);
mFormulaText = (CalculatorText) findViewById(R.id.formula);
- mResult = (CalculatorResult) findViewById(R.id.result);
+ mResultText = (CalculatorResult) findViewById(R.id.result);
mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
mDeleteButton = findViewById(R.id.del);
@@ -224,8 +235,8 @@ public class Calculator extends Activity
findViewById(R.id.fun_arctan)
};
- mEvaluator = new Evaluator(this, mResult);
- mResult.setEvaluator(mEvaluator);
+ mEvaluator = new Evaluator(this, mResultText);
+ mResultText.setEvaluator(mEvaluator);
KeyMaps.setActivity(this);
if (savedInstanceState != null) {
@@ -313,17 +324,14 @@ public class Calculator extends Activity
}
if (mCurrentState == CalculatorState.ERROR) {
- final int errorColor = getResources().getColor(R.color.calculator_error_color);
+ final int errorColor = getColor(R.color.calculator_error_color);
mFormulaText.setTextColor(errorColor);
- mResult.setTextColor(errorColor);
+ mResultText.setTextColor(errorColor);
getWindow().setStatusBarColor(errorColor);
- } else {
- mFormulaText.setTextColor(
- getResources().getColor(R.color.display_formula_text_color));
- mResult.setTextColor(
- getResources().getColor(R.color.display_result_text_color));
- getWindow().setStatusBarColor(
- getResources().getColor(R.color.calculator_accent_color));
+ } else if (mCurrentState != CalculatorState.RESULT) {
+ mFormulaText.setTextColor(getColor(R.color.display_formula_text_color));
+ mResultText.setTextColor(getColor(R.color.display_result_text_color));
+ getWindow().setStatusBarColor(getColor(R.color.calculator_accent_color));
}
invalidateOptionsMenu();
@@ -332,7 +340,7 @@ public class Calculator extends Activity
// Stop any active ActionMode. Return true if there was one.
private boolean stopActionMode() {
- if (mResult.stopActionMode()) {
+ if (mResultText.stopActionMode()) {
return true;
}
if (mFormulaText.stopActionMode()) {
@@ -437,7 +445,7 @@ public class Calculator extends Activity
if (mEvaluator.getExpr().hasInterestingOps()) {
mEvaluator.evaluateAndShowResult();
} else {
- mResult.clear();
+ mResultText.clear();
}
}
@@ -482,7 +490,7 @@ public class Calculator extends Activity
onModeChanged(mode);
setState(CalculatorState.INPUT);
- mResult.clear();
+ mResultText.clear();
if (mEvaluator.getExpr().hasInterestingOps()) {
mEvaluator.evaluateAndShowResult();
}
@@ -525,7 +533,7 @@ public class Calculator extends Activity
// Invalidate any options that may depend on the current result.
invalidateOptionsMenu();
- mResult.displayResult(initDisplayPrec, leastDigPos, truncatedWholeNumber);
+ mResultText.displayResult(initDisplayPrec, leastDigPos, truncatedWholeNumber);
if (mCurrentState != CalculatorState.INPUT) { // in EVALUATE or INIT state
onResult(mCurrentState != CalculatorState.INIT);
}
@@ -535,13 +543,13 @@ public class Calculator extends Activity
// We should be in EVALUATE state.
// Display is still in input state.
setState(CalculatorState.INPUT);
- mResult.clear();
+ mResultText.clear();
}
// Reevaluation completed; ask result to redisplay current value.
public void onReevaluate()
{
- mResult.redisplay();
+ mResultText.redisplay();
}
@Override
@@ -660,7 +668,7 @@ public class Calculator extends Activity
@Override
public void onAnimationEnd(Animator animation) {
mUnprocessedChars = null;
- mResult.clear();
+ mResultText.clear();
mEvaluator.clear();
setState(CalculatorState.INPUT);
redisplayFormula();
@@ -677,14 +685,14 @@ public class Calculator extends Activity
@Override
public void onAnimationEnd(Animator animation) {
setState(CalculatorState.ERROR);
- mResult.displayError(errorResourceId);
+ mResultText.displayError(errorResourceId);
}
});
} else if (mCurrentState == CalculatorState.INIT) {
setState(CalculatorState.ERROR);
- mResult.displayError(errorResourceId);
+ mResultText.displayError(errorResourceId);
} else {
- mResult.clear();
+ mResultText.clear();
}
}
@@ -692,50 +700,49 @@ public class Calculator extends Activity
// Animate movement of result into the top formula slot.
// Result window now remains translated in the top slot while the result is displayed.
// (We convert it back to formula use only when the user provides new input.)
- // Historical note: In the Lollipop version, this invisibly and instantaeously moved
+ // Historical note: In the Lollipop version, this invisibly and instantaneously moved
// formula and result displays back at the end of the animation. We no longer do that,
// so that we can continue to properly support scrolling of the result.
// We assume the result already contains the text to be expanded.
private void onResult(boolean animate) {
- // Calculate the values needed to perform the scale and translation animations.
- // The nominal font size in the result display is fixed. But the magnification we
- // use when the user hits "=" is variable, with a scrollable result always getting
- // minimum magnification.
- // Display.xml is designed to ensure that a 5/4 increase is always possible.
- // More is possible if the display is not fully occupied.
- // Pivot the result around the bottom of the text.
- final float resultScale = (float)Math.min(1.25f / mResult.getOccupancy(), 2.0);
- // Keep the right end of text fixed as we scale.
- mResult.setPivotX(mResult.getWidth() - mResult.getPaddingRight());
- // Move result up to take place of formula. Scale around top of formula.
- mResult.setPivotY(mResult.getPaddingTop());
- float resultTranslationY = -mFormulaText.getHeight();
- // Move formula off screen.
+ // Calculate the textSize that would be used to display the result in the formula.
+ // For scrollable results just use the minimum textSize to maximize the number of digits
+ // that are visible on screen.
+ float textSize = mFormulaText.getMinimumTextSize();
+ if (!mResultText.isScrollable()) {
+ textSize = mFormulaText.getVariableTextSize(mResultText.getText().toString());
+ }
+
+ // Scale the result to match the calculated textSize, minimizing the jump-cut transition
+ // when a result is reused in a subsequent expression.
+ final float resultScale = textSize / mResultText.getTextSize();
+
+ // Set the result's pivot to match its gravity.
+ mResultText.setPivotX(mResultText.getWidth() - mResultText.getPaddingRight());
+ mResultText.setPivotY(mResultText.getHeight() - mResultText.getPaddingBottom());
+
+ // Calculate the necessary translations so the result takes the place of the formula and
+ // the formula moves off the top of the screen.
+ final float resultTranslationY = (mFormulaText.getBottom() - mResultText.getBottom())
+ - (mFormulaText.getPaddingBottom() - mResultText.getPaddingBottom());
final float formulaTranslationY = -mFormulaText.getBottom();
- // TODO: Reintroduce textColorAnimator?
- // The initial and final colors seemed to be the same in L.
- // With the new model, the result logically changes back to a formula
- // only when we switch back to INPUT state, so it's unclear that animating
- // a color change here makes sense.
+ // Change the result's textColor to match the formula.
+ final int formulaTextColor = mFormulaText.getCurrentTextColor();
+
if (animate) {
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
- ObjectAnimator.ofFloat(mResult, View.SCALE_X, resultScale),
- ObjectAnimator.ofFloat(mResult, View.SCALE_Y, resultScale),
- ObjectAnimator.ofFloat(mResult, View.TRANSLATION_Y, resultTranslationY),
- ObjectAnimator.ofFloat(mFormulaText, View.TRANSLATION_Y,
- formulaTranslationY));
- animatorSet.setDuration(
- getResources().getInteger(android.R.integer.config_longAnimTime));
- animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ ObjectAnimator.ofPropertyValuesHolder(mResultText,
+ PropertyValuesHolder.ofFloat(View.SCALE_X, resultScale),
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, resultScale),
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, resultTranslationY)),
+ ObjectAnimator.ofArgb(mResultText, TEXT_COLOR, formulaTextColor),
+ ObjectAnimator.ofFloat(mFormulaText, View.TRANSLATION_Y, formulaTranslationY));
+ animatorSet.setDuration(getResources().getInteger(
+ android.R.integer.config_longAnimTime));
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationStart(Animator animation) {
- // Result should already be displayed; no need to do anything.
- }
-
- @Override
public void onAnimationEnd(Animator animation) {
setState(CalculatorState.RESULT);
mCurrentAnimator = null;
@@ -745,9 +752,10 @@ public class Calculator extends Activity
mCurrentAnimator = animatorSet;
animatorSet.start();
} else /* No animation desired; get there fast, e.g. when restarting */ {
- mResult.setScaleX(resultScale);
- mResult.setScaleY(resultScale);
- mResult.setTranslationY(resultTranslationY);
+ mResultText.setScaleX(resultScale);
+ mResultText.setScaleY(resultScale);
+ mResultText.setTranslationY(resultTranslationY);
+ mResultText.setTextColor(formulaTextColor);
mFormulaText.setTranslationY(formulaTranslationY);
setState(CalculatorState.RESULT);
}
@@ -757,12 +765,12 @@ public class Calculator extends Activity
// pre-animation state.
private void restoreDisplayPositions() {
// Clear result.
- mResult.setText("");
+ mResultText.setText("");
// Reset all of the values modified during the animation.
- mResult.setScaleX(1.0f);
- mResult.setScaleY(1.0f);
- mResult.setTranslationX(0.0f);
- mResult.setTranslationY(0.0f);
+ mResultText.setScaleX(1.0f);
+ mResultText.setScaleY(1.0f);
+ mResultText.setTranslationX(0.0f);
+ mResultText.setTranslationY(0.0f);
mFormulaText.setTranslationY(0.0f);
mFormulaText.requestFocus();
@@ -808,13 +816,10 @@ public class Calculator extends Activity
}
private void displayMessage(String s) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setMessage(s)
- .setNegativeButton(R.string.dismiss,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface d, int which) { }
- })
- .show();
+ new AlertDialog.Builder(this)
+ .setMessage(s)
+ .setNegativeButton(R.string.dismiss, null /* listener */)
+ .show();
}
private void displayFraction() {
@@ -825,8 +830,8 @@ public class Calculator extends Activity
// Display full result to currently evaluated precision
private void displayFull() {
Resources res = getResources();
- String msg = mResult.getFullText() + " ";
- if (mResult.fullTextIsExact()) {
+ String msg = mResultText.getFullText() + " ";
+ if (mResultText.fullTextIsExact()) {
msg += res.getString(R.string.exact);
} else {
msg += res.getString(R.string.approximate);
diff --git a/src/com/android/calculator2/CalculatorResult.java b/src/com/android/calculator2/CalculatorResult.java
index a916c30..5b4fb86 100644
--- a/src/com/android/calculator2/CalculatorResult.java
+++ b/src/com/android/calculator2/CalculatorResult.java
@@ -16,23 +16,16 @@
package com.android.calculator2;
-import android.content.ClipboardManager;
import android.content.ClipData;
import android.content.ClipDescription;
+import android.content.ClipboardManager;
import android.content.Context;
-import android.graphics.Typeface;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Color;
-import android.net.Uri;
-import android.widget.TextView;
-import android.widget.OverScroller;
-import android.text.Editable;
+import android.text.Layout;
import android.text.SpannableString;
import android.text.Spanned;
+import android.text.TextPaint;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.ActionMode;
import android.view.GestureDetector;
import android.view.Menu;
@@ -40,14 +33,12 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
+import android.widget.OverScroller;
import android.widget.Toast;
-import android.support.v4.view.ViewCompat;
-
-
// A text widget that is "infinitely" scrollable to the right,
// and obtains the text to display via a callback to Logic.
-public class CalculatorResult extends TextView {
+public class CalculatorResult extends AlignedTextView {
static final int MAX_RIGHT_SCROLL = 10000000;
static final int INVALID = MAX_RIGHT_SCROLL + 10000;
// A larger value is unlikely to avoid running out of space
@@ -56,8 +47,7 @@ public class CalculatorResult extends TextView {
class MyTouchListener implements View.OnTouchListener {
@Override
public boolean onTouch(View v, MotionEvent event) {
- boolean res = mGestureDetector.onTouchEvent(event);
- return res;
+ return mGestureDetector.onTouchEvent(event);
}
}
final MyTouchListener mTouchListener = new MyTouchListener();
@@ -75,11 +65,11 @@ public class CalculatorResult extends TextView {
private int mMinPos; // Minimum position before all digits disappear off the right. Pixels.
private int mMaxPos; // Maximum position before we start displaying the infinite
// sequence of trailing zeroes on the right. Pixels.
- private Object mWidthLock = new Object();
+ private final Object mWidthLock = new Object();
// Protects the next two fields.
private int mWidthConstraint = -1;
// Our total width in pixels.
- private int mCharWidth = 1;
+ private float mCharWidth = 1;
// Maximum character width. For now we pretend that all characters
// have this width.
// TODO: We're not really using a fixed width font. But it appears
@@ -112,7 +102,7 @@ public class CalculatorResult extends TextView {
if (!mScrollable) return true;
mScroller.fling(mCurrentPos, 0, - (int) velocityX, 0 /* horizontal only */,
mMinPos, mMaxPos, 0, 0);
- ViewCompat.postInvalidateOnAnimation(CalculatorResult.this);
+ postInvalidateOnAnimation();
return true;
}
@Override
@@ -134,7 +124,7 @@ public class CalculatorResult extends TextView {
int duration = (int)(e2.getEventTime() - e1.getEventTime());
if (duration < 1 || duration > 100) duration = 10;
mScroller.startScroll(mCurrentPos, 0, distance, 0, (int)duration);
- ViewCompat.postInvalidateOnAnimation(CalculatorResult.this);
+ postInvalidateOnAnimation();
return true;
}
@Override
@@ -163,21 +153,11 @@ public class CalculatorResult extends TextView {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- char testChar = KeyMaps.translateResult("5").charAt(0);
- // TODO: Redo on Locale change? Doesn't seem to matter?
- // We try to determine the maximal size of a digit plus corresponding inter-character
- // space. We assume that "5" has maximal width. Since any string includes one fewer
- // inter-character space than characters, me measure one that's longer than any real
- // display string, and then divide by the number of characters. This should bound
- // the per-character space we need for any real string.
- StringBuilder sb = new StringBuilder(MAX_WIDTH);
- for (int i = 0; i < MAX_WIDTH; ++i) {
- sb.append(testChar);
- }
- final int newWidthConstraint =
- MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
- final int newCharWidth =
- (int)Math.ceil(getPaint().measureText(sb.toString()) / MAX_WIDTH);
+ final TextPaint paint = getPaint();
+ final int newWidthConstraint = MeasureSpec.getSize(widthMeasureSpec)
+ - (getPaddingLeft() + getPaddingRight())
+ - (int) Math.ceil(Layout.getDesiredWidth(KeyMaps.ELLIPSIS, paint));
+ final float newCharWidth = Layout.getDesiredWidth("\u2007", paint);
synchronized(mWidthLock) {
mWidthConstraint = newWidthConstraint;
mCharWidth = newCharWidth;
@@ -208,15 +188,16 @@ public class CalculatorResult extends TextView {
void displayResult(int initPrec, int leastDigPos, String truncatedWholePart) {
mLastPos = INVALID;
synchronized(mWidthLock) {
- mCurrentPos = initPrec * mCharWidth;
+ mCurrentPos = (int) Math.ceil(initPrec * mCharWidth);
}
// Should logically be
// mMinPos = - (int) Math.ceil(getPaint().measureText(truncatedWholePart)), but
// we eventually transalate to a character position by dividing by mCharWidth.
// To avoid rounding issues, we use the analogous computation here.
- mMinPos = - truncatedWholePart.length() * mCharWidth;
+ mMinPos = - (int) Math.ceil(truncatedWholePart.length() * mCharWidth);
if (leastDigPos < MAX_RIGHT_SCROLL) {
- mMaxPos = Math.min(addExpSpace(leastDigPos) * mCharWidth, MAX_RIGHT_SCROLL);
+ mMaxPos = Math.min((int) Math.ceil(addExpSpace(leastDigPos) * mCharWidth),
+ MAX_RIGHT_SCROLL);
} else {
mMaxPos = MAX_RIGHT_SCROLL;
}
@@ -350,11 +331,9 @@ public class CalculatorResult extends TextView {
* May be called asynchronously from non-UI thread.
*/
int getMaxChars() {
- // We only use 4/5 of the available space, since at least the left 4/5 of the result
- // is not visible when it is shown in large size.
int result;
synchronized(mWidthLock) {
- result = 4 * mWidthConstraint / (5 * mCharWidth);
+ result = (int) Math.floor(mWidthConstraint / mCharWidth);
// We can apparently finish evaluating before onMeasure in CalculatorText has been
// called, in which case we get 0 or -1 as the width constraint.
}
@@ -362,26 +341,22 @@ public class CalculatorResult extends TextView {
// Return something conservatively big, to force sufficient evaluation.
return MAX_WIDTH;
} else {
- return result;
+ // Always allow for the ellipsis character which already accounted for in the width
+ // constraint.
+ return result + 1;
}
}
/**
- * Return the fraction of the available character space occupied by the
- * current result.
- * Should be called only with a valid result displayed.
+ * @return {@code true} if the currently displayed result is scrollable
*/
- float getOccupancy() {
- if (mScrollable) {
- return 1.0f;
- } else {
- return (float)getText().length() / getMaxChars();
- }
+ public boolean isScrollable() {
+ return mScrollable;
}
int getCurrentCharPos() {
synchronized(mWidthLock) {
- return mCurrentPos/mCharWidth;
+ return (int) Math.ceil(mCurrentPos / mCharWidth);
}
}
@@ -419,7 +394,7 @@ public class CalculatorResult extends TextView {
redisplay();
}
if (!mScroller.isFinished()) {
- ViewCompat.postInvalidateOnAnimation(this);
+ postInvalidateOnAnimation();
}
}
}
diff --git a/src/com/android/calculator2/CalculatorText.java b/src/com/android/calculator2/CalculatorText.java
index 1b16bca..c6f38ae 100644
--- a/src/com/android/calculator2/CalculatorText.java
+++ b/src/com/android/calculator2/CalculatorText.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 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.
@@ -16,39 +16,31 @@
package com.android.calculator2;
-import android.content.ClipboardManager;
import android.content.ClipData;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
-import android.graphics.Paint.FontMetricsInt;
-import android.graphics.Rect;
import android.net.Uri;
-import android.os.Parcelable;
-import android.text.method.ScrollingMovementMethod;
import android.text.TextPaint;
+import android.text.method.ScrollingMovementMethod;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.TypedValue;
import android.view.ActionMode;
-import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
/**
* TextView adapted for Calculator display.
*/
-
-public class CalculatorText extends TextView implements View.OnLongClickListener{
+public class CalculatorText extends AlignedTextView implements View.OnLongClickListener {
private ActionMode mActionMode;
- private final ActionMode.Callback mPasteActionModeCallback =
- new ActionMode.Callback() {
+ private final ActionMode.Callback mPasteActionModeCallback = new ActionMode.Callback() {
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
@@ -109,9 +101,8 @@ public class CalculatorText extends TextView implements View.OnLongClickListener
private final float mMinimumTextSize;
private final float mStepTextSize;
- // Temporary objects for use in layout methods.
+ // Temporary paint for use in layout methods.
private final Paint mTempPaint = new TextPaint();
- private final Rect mTempRect = new Rect();
private int mWidthConstraint = -1;
private OnTextSizeChangeListener mOnTextSizeChangeListener;
@@ -146,7 +137,6 @@ public class CalculatorText extends TextView implements View.OnLongClickListener
setMovementMethod(ScrollingMovementMethod.getInstance());
setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize);
- setMinHeight(getLineHeight() + getCompoundPaddingBottom() + getCompoundPaddingTop());
}
@Override
@@ -159,8 +149,13 @@ public class CalculatorText extends TextView implements View.OnLongClickListener
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mWidthConstraint =
- MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
+ // Prevent shrinking/resizing with our variable textSize.
+ if (!isLaidOut()) {
+ setMinHeight(getLineHeight() + getCompoundPaddingBottom() + getCompoundPaddingTop());
+ }
+
+ mWidthConstraint = MeasureSpec.getSize(widthMeasureSpec)
+ - getPaddingLeft() - getPaddingRight();
setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(getText().toString()));
}
@@ -170,7 +165,6 @@ public class CalculatorText extends TextView implements View.OnLongClickListener
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
- final int textLength = text.length();
setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(text.toString()));
}
@@ -188,6 +182,14 @@ public class CalculatorText extends TextView implements View.OnLongClickListener
mOnTextSizeChangeListener = listener;
}
+ public float getMinimumTextSize() {
+ return mMinimumTextSize;
+ }
+
+ public float getMaximumTextSize() {
+ return mMaximumTextSize;
+ }
+
public float getVariableTextSize(String text) {
if (mWidthConstraint < 0 || mMaximumTextSize <= mMinimumTextSize) {
// Not measured, bail early.
@@ -212,25 +214,6 @@ public class CalculatorText extends TextView implements View.OnLongClickListener
return lastFitTextSize;
}
- @Override
- public int getCompoundPaddingTop() {
- // Measure the top padding from the capital letter height of the text instead of the top,
- // but don't remove more than the available top padding otherwise clipping may occur.
- getPaint().getTextBounds("H", 0, 1, mTempRect);
-
- final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
- final int paddingOffset = -(fontMetrics.ascent + mTempRect.height());
- return super.getCompoundPaddingTop() - Math.min(getPaddingTop(), paddingOffset);
- }
-
- @Override
- public int getCompoundPaddingBottom() {
- // Measure the bottom padding from the baseline of the text instead of the bottom, but don't
- // remove more than the available bottom padding otherwise clipping may occur.
- final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
- return super.getCompoundPaddingBottom() - Math.min(getPaddingBottom(), fontMetrics.descent);
- }
-
public boolean stopActionMode() {
if (mActionMode != null) {
mActionMode.finish();
diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java
index 6362efe..4ddaf15 100644
--- a/src/com/android/calculator2/Evaluator.java
+++ b/src/com/android/calculator2/Evaluator.java
@@ -796,6 +796,14 @@ class Evaluator {
return mDegreeMode;
}
+ /**
+ * @return the {@link CalculatorExpr} representation of the current result
+ */
+ CalculatorExpr getResultExpr() {
+ final BigInteger intVal = BoundedRational.asBigInteger(mRatVal);
+ return mExpr.abbreviate(mVal, mRatVal, mDegreeMode, getShortString(mCache, intVal));
+ }
+
// Abbreviate the current expression to a pre-evaluated
// expression node, which will display as a short number.
// This should not be called unless the expression was
@@ -805,11 +813,8 @@ class Evaluator {
// diverges. Subsequent re-evaluation will also not diverge,
// though it may generate errors of various kinds.
// E.g. sqrt(-10^-1000)
- void collapse () {
- BigInteger intVal = BoundedRational.asBigInteger(mRatVal);
- CalculatorExpr abbrvExpr = mExpr.abbreviate(
- mVal, mRatVal, mDegreeMode,
- getShortString(mCache, intVal));
+ void collapse() {
+ final CalculatorExpr abbrvExpr = getResultExpr();
clear();
mExpr.append(abbrvExpr);
}
@@ -817,11 +822,11 @@ class Evaluator {
// Same as above, but put result in mSaved, leaving mExpr alone.
// Return false if result is unavailable.
boolean collapseToSaved() {
- if (mCache == null) return false;
- BigInteger intVal = BoundedRational.asBigInteger(mRatVal);
- CalculatorExpr abbrvExpr = mExpr.abbreviate(
- mVal, mRatVal, mDegreeMode,
- getShortString(mCache, intVal));
+ if (mCache == null) {
+ return false;
+ }
+
+ final CalculatorExpr abbrvExpr = getResultExpr();
mSaved.clear();
mSaved.append(abbrvExpr);
return true;