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
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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
* 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 @@
// 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 @@
redisplay();
}
if (!mScroller.isFinished()) {
- ViewCompat.postInvalidateOnAnimation(this);
+ postInvalidateOnAnimation();
}
}
}