Home | History | Annotate | Download | only in calculator2
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *   http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.calculator2;
     18 
     19 import android.content.Context;
     20 import android.content.res.TypedArray;
     21 import android.graphics.Paint;
     22 import android.graphics.Paint.FontMetricsInt;
     23 import android.graphics.Rect;
     24 import android.os.Parcelable;
     25 import android.text.method.ScrollingMovementMethod;
     26 import android.text.TextPaint;
     27 import android.util.AttributeSet;
     28 import android.util.TypedValue;
     29 import android.view.ActionMode;
     30 import android.view.Menu;
     31 import android.view.MenuItem;
     32 import android.view.MotionEvent;
     33 import android.widget.EditText;
     34 import android.widget.TextView;
     35 
     36 public class CalculatorEditText extends EditText {
     37 
     38     private final static ActionMode.Callback NO_SELECTION_ACTION_MODE_CALLBACK =
     39             new ActionMode.Callback() {
     40         @Override
     41         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
     42             return false;
     43         }
     44 
     45         @Override
     46         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
     47             // Prevents the selection action mode on double tap.
     48             return false;
     49         }
     50 
     51         @Override
     52         public void onDestroyActionMode(ActionMode mode) {
     53         }
     54 
     55         @Override
     56         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
     57             return false;
     58         }
     59     };
     60 
     61     private final float mMaximumTextSize;
     62     private final float mMinimumTextSize;
     63     private final float mStepTextSize;
     64 
     65     // Temporary objects for use in layout methods.
     66     private final Paint mTempPaint = new TextPaint();
     67     private final Rect mTempRect = new Rect();
     68 
     69     private int mWidthConstraint = -1;
     70     private OnTextSizeChangeListener mOnTextSizeChangeListener;
     71 
     72     public CalculatorEditText(Context context) {
     73         this(context, null);
     74     }
     75 
     76     public CalculatorEditText(Context context, AttributeSet attrs) {
     77         this(context, attrs, 0);
     78     }
     79 
     80     public CalculatorEditText(Context context, AttributeSet attrs, int defStyle) {
     81         super(context, attrs, defStyle);
     82 
     83         final TypedArray a = context.obtainStyledAttributes(
     84                 attrs, R.styleable.CalculatorEditText, defStyle, 0);
     85         mMaximumTextSize = a.getDimension(
     86                 R.styleable.CalculatorEditText_maxTextSize, getTextSize());
     87         mMinimumTextSize = a.getDimension(
     88                 R.styleable.CalculatorEditText_minTextSize, getTextSize());
     89         mStepTextSize = a.getDimension(R.styleable.CalculatorEditText_stepTextSize,
     90                 (mMaximumTextSize - mMinimumTextSize) / 3);
     91 
     92         a.recycle();
     93 
     94         setCustomSelectionActionModeCallback(NO_SELECTION_ACTION_MODE_CALLBACK);
     95         if (isFocusable()) {
     96             setMovementMethod(ScrollingMovementMethod.getInstance());
     97         }
     98         setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize);
     99         setMinHeight(getLineHeight() + getCompoundPaddingBottom() + getCompoundPaddingTop());
    100     }
    101 
    102     @Override
    103     public boolean onTouchEvent(MotionEvent event) {
    104         if (event.getActionMasked() == MotionEvent.ACTION_UP) {
    105             // Hack to prevent keyboard and insertion handle from showing.
    106             cancelLongPress();
    107         }
    108         return super.onTouchEvent(event);
    109     }
    110 
    111     @Override
    112     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    113         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    114 
    115         mWidthConstraint =
    116                 MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
    117         setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(getText().toString()));
    118     }
    119 
    120     @Override
    121     public Parcelable onSaveInstanceState() {
    122         super.onSaveInstanceState();
    123 
    124         // EditText will freeze any text with a selection regardless of getFreezesText() ->
    125         // return null to prevent any state from being preserved at the instance level.
    126         return null;
    127     }
    128 
    129     @Override
    130     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
    131         super.onTextChanged(text, start, lengthBefore, lengthAfter);
    132 
    133         final int textLength = text.length();
    134         if (getSelectionStart() != textLength || getSelectionEnd() != textLength) {
    135             // Pin the selection to the end of the current text.
    136             setSelection(textLength);
    137         }
    138         setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(text.toString()));
    139     }
    140 
    141     @Override
    142     public void setTextSize(int unit, float size) {
    143         final float oldTextSize = getTextSize();
    144         super.setTextSize(unit, size);
    145 
    146         if (mOnTextSizeChangeListener != null && getTextSize() != oldTextSize) {
    147             mOnTextSizeChangeListener.onTextSizeChanged(this, oldTextSize);
    148         }
    149     }
    150 
    151     public void setOnTextSizeChangeListener(OnTextSizeChangeListener listener) {
    152         mOnTextSizeChangeListener = listener;
    153     }
    154 
    155     public float getVariableTextSize(String text) {
    156         if (mWidthConstraint < 0 || mMaximumTextSize <= mMinimumTextSize) {
    157             // Not measured, bail early.
    158             return getTextSize();
    159         }
    160 
    161         // Capture current paint state.
    162         mTempPaint.set(getPaint());
    163 
    164         // Step through increasing text sizes until the text would no longer fit.
    165         float lastFitTextSize = mMinimumTextSize;
    166         while (lastFitTextSize < mMaximumTextSize) {
    167             final float nextSize = Math.min(lastFitTextSize + mStepTextSize, mMaximumTextSize);
    168             mTempPaint.setTextSize(nextSize);
    169             if (mTempPaint.measureText(text) > mWidthConstraint) {
    170                 break;
    171             } else {
    172                 lastFitTextSize = nextSize;
    173             }
    174         }
    175 
    176         return lastFitTextSize;
    177     }
    178 
    179     @Override
    180     public int getCompoundPaddingTop() {
    181         // Measure the top padding from the capital letter height of the text instead of the top,
    182         // but don't remove more than the available top padding otherwise clipping may occur.
    183         getPaint().getTextBounds("H", 0, 1, mTempRect);
    184 
    185         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
    186         final int paddingOffset = -(fontMetrics.ascent + mTempRect.height());
    187         return super.getCompoundPaddingTop() - Math.min(getPaddingTop(), paddingOffset);
    188     }
    189 
    190     @Override
    191     public int getCompoundPaddingBottom() {
    192         // Measure the bottom padding from the baseline of the text instead of the bottom, but don't
    193         // remove more than the available bottom padding otherwise clipping may occur.
    194         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
    195         return super.getCompoundPaddingBottom() - Math.min(getPaddingBottom(), fontMetrics.descent);
    196     }
    197 
    198     public interface OnTextSizeChangeListener {
    199         void onTextSizeChanged(TextView textView, float oldSize);
    200     }
    201 }
    202