blob: 63a5ae5396d4eb6d3f6cb022b0efdf9f71f991f0 [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;
24import android.text.method.ScrollingMovementMethod;
25import android.text.TextPaint;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080026import android.util.AttributeSet;
Hongwei Wang245925e2014-05-11 14:38:47 -070027import android.util.TypedValue;
Gilles Debunnef57b8b42011-01-27 10:54:07 -080028import android.view.ActionMode;
29import android.view.Menu;
30import android.view.MenuItem;
Gilles Debunne79525c32011-01-25 08:25:41 -080031import android.view.MotionEvent;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080032import android.widget.EditText;
Justin Klaassenfed941a2014-06-09 18:42:40 +010033import android.widget.TextView;
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -080034
Alan Viverette461992d2014-03-07 13:29:56 -080035public class CalculatorEditText extends EditText {
Alan Viverette461992d2014-03-07 13:29:56 -080036
Justin Klaassen4b3af052014-05-27 17:53:10 -070037 private final ActionMode.Callback mNoSelectionActionModeCallback = new ActionMode.Callback() {
Gilles Debunnef57b8b42011-01-27 10:54:07 -080038 @Override
39 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
40 return false;
41 }
42
43 @Override
44 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Gilles Debunnef57b8b42011-01-27 10:54:07 -080045 // Prevents the selection action mode on double tap.
46 return false;
47 }
48
49 @Override
Alan Viverette461992d2014-03-07 13:29:56 -080050 public void onDestroyActionMode(ActionMode mode) {
51 }
Gilles Debunnef57b8b42011-01-27 10:54:07 -080052
53 @Override
54 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
55 return false;
56 }
Justin Klaassen4b3af052014-05-27 17:53:10 -070057 };
58
59 private final float mMaximumTextSize;
60 private final float mMinimumTextSize;
61 private final float mStepTextSize;
62
63 private int mWidthConstraint = -1;
Justin Klaassenfed941a2014-06-09 18:42:40 +010064 private OnTextSizeChangeListener mOnTextSizeChangeListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -070065
66 public CalculatorEditText(Context context) {
67 this(context, null);
68 }
69
70 public CalculatorEditText(Context context, AttributeSet attrs) {
71 this(context, attrs, 0);
72 }
73
74 public CalculatorEditText(Context context, AttributeSet attrs, int defStyle) {
75 super(context, attrs, defStyle);
76
77 final TypedArray a = context.obtainStyledAttributes(
78 attrs, R.styleable.CalculatorEditText, defStyle, 0);
79 mMaximumTextSize = a.getDimension(
80 R.styleable.CalculatorEditText_maxTextSize, getTextSize());
81 mMinimumTextSize = a.getDimension(
82 R.styleable.CalculatorEditText_minTextSize, getTextSize());
83 mStepTextSize = a.getDimension(R.styleable.CalculatorEditText_stepTextSize,
84 (mMaximumTextSize - mMinimumTextSize) / 3);
85
86 a.recycle();
87
88 setCustomSelectionActionModeCallback(mNoSelectionActionModeCallback);
89 setMovementMethod(ScrollingMovementMethod.getInstance());
90 setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize);
91 setMinHeight(getLineHeight() + getCompoundPaddingBottom() + getCompoundPaddingTop());
92 }
93
94 @Override
95 protected void onSelectionChanged(int selStart, int selEnd) {
96 final int textLength = getText() == null ? 0 : getText().length();
97 if (selStart != textLength || selEnd != textLength) {
98 // Pin the selection to the end of the current text.
99 setSelection(textLength);
100 }
101
102 super.onSelectionChanged(selStart, selEnd);
103 }
104
105 @Override
106 public boolean onTouchEvent(MotionEvent event) {
107 if (event.getActionMasked() == MotionEvent.ACTION_UP) {
108 // Hack to prevent keyboard and insertion handle from showing.
109 cancelLongPress();
110 }
111 return super.onTouchEvent(event);
112 }
113
114 @Override
115 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
116 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
117
118 mWidthConstraint =
119 MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
120 setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(getText().toString()));
121 }
122
123 @Override
124 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
125 super.onTextChanged(text, start, lengthBefore, lengthAfter);
126
127 setSelection(text.length());
128 setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(text.toString()));
129 }
130
Justin Klaassenfed941a2014-06-09 18:42:40 +0100131 @Override
132 public void setTextSize(int unit, float size) {
133 final float oldTextSize = getTextSize();
134 super.setTextSize(unit, size);
135
136 if (mOnTextSizeChangeListener != null && getTextSize() != oldTextSize) {
137 mOnTextSizeChangeListener.onTextSizeChanged(this, oldTextSize);
138 }
139 }
140
141 public void setOnTextSizeChangeListener(OnTextSizeChangeListener listener) {
142 mOnTextSizeChangeListener = listener;
143 }
144
Justin Klaassen4b3af052014-05-27 17:53:10 -0700145 public float getVariableTextSize(String text) {
146 if (mWidthConstraint < 0 || mMaximumTextSize <= mMinimumTextSize) {
147 // Not measured, bail early.
148 return getTextSize();
149 }
150
151 final Paint paint = new TextPaint(getPaint());
152 float lastFitTextSize = mMinimumTextSize;
153 while (lastFitTextSize < mMaximumTextSize) {
154 final float nextSize = Math.min(lastFitTextSize + mStepTextSize, mMaximumTextSize);
155 paint.setTextSize(nextSize);
156 if (paint.measureText(text) > mWidthConstraint) {
157 break;
158 } else {
159 lastFitTextSize = nextSize;
160 }
161 }
162
163 return lastFitTextSize;
164 }
165
166 @Override
167 public int getCompoundPaddingTop() {
168 // Measure the top padding from the capital letter height of the text instead of the top,
169 // but don't remove more than the available top padding otherwise clipping may occur.
170 final Rect capBounds = new Rect();
171 getPaint().getTextBounds("H", 0, 1, capBounds);
172
173 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
174 final int paddingOffset = -(fontMetrics.ascent + capBounds.height());
175
176 return super.getCompoundPaddingTop() - Math.min(getPaddingTop(), paddingOffset);
177 }
178
179 @Override
180 public int getCompoundPaddingBottom() {
181 // Measure the bottom padding from the baseline of the text instead of the bottom, but don't
182 // remove more than the available bottom padding otherwise clipping may occur.
183 final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
184 return super.getCompoundPaddingBottom() - Math.min(getPaddingBottom(), fontMetrics.descent);
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -0800185 }
Justin Klaassenfed941a2014-06-09 18:42:40 +0100186
187 public interface OnTextSizeChangeListener {
188 void onTextSizeChanged(TextView textView, float oldSize);
189 }
Dmitri Plotnikovde3eec22011-01-17 18:23:37 -0800190}