blob: 5a0d8bafea5adb573daf53aad572662a37547c82 [file] [log] [blame]
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -08001/*
Justin Klaassen4b3af052014-05-27 17:53:10 -07002 * Copyright (C) 2014 The Android Open Source Project
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -08003 *
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 *
Justin Klaassen4b3af052014-05-27 17:53:10 -07008 * http://www.apache.org/licenses/LICENSE-2.0
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -08009 *
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
17package com.android.calculator2;
18
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080019import android.content.Context;
Justin Klaassen4b3af052014-05-27 17:53:10 -070020import android.content.res.TypedArray;
Hongwei Wang245925e2014-05-11 14:38:47 -070021import android.graphics.Paint;
Justin Klaassen4b3af052014-05-27 17:53:10 -070022import android.graphics.Paint.FontMetricsInt;
23import android.graphics.Rect;
Justin Klaassenbfc4e4d2014-08-27 10:56:49 -070024import android.os.Parcelable;
Justin Klaassen4b3af052014-05-27 17:53:10 -070025import android.text.method.ScrollingMovementMethod;
26import android.text.TextPaint;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080027import android.util.AttributeSet;
Hongwei Wang245925e2014-05-11 14:38:47 -070028import android.util.TypedValue;
Gilles Debunnef57b8b42011-01-27 10:54:07 -080029import android.view.ActionMode;
30import android.view.Menu;
31import android.view.MenuItem;
Gilles Debunne79525c32011-01-25 08:25:41 -080032import android.view.MotionEvent;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080033import android.widget.EditText;
Justin Klaassenfed941a2014-06-09 18:42:40 +010034import android.widget.TextView;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080035
Alan Viverette461992d2014-03-07 13:29:56 -080036public class CalculatorEditText extends EditText {
Alan Viverette461992d2014-03-07 13:29:56 -080037
Justin Klaassenbfc4e4d2014-08-27 10:56:49 -070038 private final static ActionMode.Callback NO_SELECTION_ACTION_MODE_CALLBACK =
39 new ActionMode.Callback() {
Gilles Debunnef57b8b42011-01-27 10:54:07 -080040 @Override
41 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
42 return false;
43 }
44
45 @Override
46 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Gilles Debunnef57b8b42011-01-27 10:54:07 -080047 // Prevents the selection action mode on double tap.
48 return false;
49 }
50
51 @Override
Alan Viverette461992d2014-03-07 13:29:56 -080052 public void onDestroyActionMode(ActionMode mode) {
53 }
Gilles Debunnef57b8b42011-01-27 10:54:07 -080054
55 @Override
56 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
57 return false;
58 }
Justin Klaassen4b3af052014-05-27 17:53:10 -070059 };
60
61 private final float mMaximumTextSize;
62 private final float mMinimumTextSize;
63 private final float mStepTextSize;
64
Justin Klaassen2be4fdb2014-08-06 19:54:09 -070065 // Temporary objects for use in layout methods.
66 private final Paint mTempPaint = new TextPaint();
67 private final Rect mTempRect = new Rect();
68
Justin Klaassen4b3af052014-05-27 17:53:10 -070069 private int mWidthConstraint = -1;
Justin Klaassenfed941a2014-06-09 18:42:40 +010070 private OnTextSizeChangeListener mOnTextSizeChangeListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -070071
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
Justin Klaassenbfc4e4d2014-08-27 10:56:49 -070094 setCustomSelectionActionModeCallback(NO_SELECTION_ACTION_MODE_CALLBACK);
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -070095 if (isFocusable()) {
96 setMovementMethod(ScrollingMovementMethod.getInstance());
97 }
Justin Klaassen4b3af052014-05-27 17:53:10 -070098 setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize);
99 setMinHeight(getLineHeight() + getCompoundPaddingBottom() + getCompoundPaddingTop());
100 }
101
102 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700103 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
Justin Klaassenbfc4e4d2014-08-27 10:56:49 -0700121 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
Justin Klaassen4b3af052014-05-27 17:53:10 -0700130 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
131 super.onTextChanged(text, start, lengthBefore, lengthAfter);
Justin Klaassenbfc4e4d2014-08-27 10:56:49 -0700132
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 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700138 setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(text.toString()));
139 }
140
Justin Klaassenfed941a2014-06-09 18:42:40 +0100141 @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
Justin Klaassen4b3af052014-05-27 17:53:10 -0700155 public float getVariableTextSize(String text) {
156 if (mWidthConstraint < 0 || mMaximumTextSize <= mMinimumTextSize) {
157 // Not measured, bail early.
158 return getTextSize();
159 }
160
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700161 // Capture current paint state.
162 mTempPaint.set(getPaint());
163
164 // Step through increasing text sizes until the text would no longer fit.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700165 float lastFitTextSize = mMinimumTextSize;
166 while (lastFitTextSize < mMaximumTextSize) {
167 final float nextSize = Math.min(lastFitTextSize + mStepTextSize, mMaximumTextSize);
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700168 mTempPaint.setTextSize(nextSize);
169 if (mTempPaint.measureText(text) > mWidthConstraint) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700170 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.
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700183 getPaint().getTextBounds("H", 0, 1, mTempRect);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700184
185 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700186 final int paddingOffset = -(fontMetrics.ascent + mTempRect.height());
Justin Klaassen4b3af052014-05-27 17:53:10 -0700187 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);
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -0800196 }
Justin Klaassenfed941a2014-06-09 18:42:40 +0100197
198 public interface OnTextSizeChangeListener {
199 void onTextSizeChanged(TextView textView, float oldSize);
200 }
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -0800201}