1package eu.siacs.conversations.ui.drawable; /**
2 * Copyright 2016 Ali Muzaffar
3 * <p/>
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 * <p/>
8 * http://www.apache.org/licenses/LICENSE-2.0
9 * <p/>
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
17import android.graphics.Canvas;
18import android.graphics.ColorFilter;
19import android.graphics.Paint;
20import android.graphics.PixelFormat;
21import android.graphics.Rect;
22import android.graphics.drawable.Drawable;
23import android.text.Editable;
24import android.text.TextWatcher;
25import android.widget.TextView;
26
27import java.lang.ref.WeakReference;
28
29public class TextDrawable extends Drawable implements TextWatcher {
30 private WeakReference<TextView> ref;
31 private String mText;
32 private Paint mPaint;
33 private Rect mHeightBounds;
34 private boolean mBindToViewPaint = false;
35 private float mPrevTextSize = 0;
36 private boolean mInitFitText = false;
37 private boolean mFitTextEnabled = false;
38
39 /**
40 * Create a TextDrawable using the given paint object and string
41 *
42 * @param paint
43 * @param s
44 */
45 public TextDrawable(Paint paint, String s) {
46 mText = s;
47 mPaint = new Paint(paint);
48 mHeightBounds = new Rect();
49 init();
50 }
51
52 /**
53 * Create a TextDrawable. This uses the given TextView to initialize paint and has initial text
54 * that will be drawn. Initial text can also be useful for reserving space that may otherwise
55 * not be available when setting compound drawables.
56 *
57 * @param tv The TextView / EditText using to initialize this drawable
58 * @param initialText Optional initial text to display
59 * @param bindToViewsText Should this drawable mirror the text in the TextView
60 * @param bindToViewsPaint Should this drawable mirror changes to Paint in the TextView, like textColor, typeface, alpha etc.
61 * Note, this will override any changes made using setColorFilter or setAlpha.
62 */
63 public TextDrawable(TextView tv, String initialText, boolean bindToViewsText, boolean bindToViewsPaint) {
64 this(tv.getPaint(), initialText);
65 ref = new WeakReference<>(tv);
66 if (bindToViewsText || bindToViewsPaint) {
67 if (bindToViewsText) {
68 tv.addTextChangedListener(this);
69 }
70 mBindToViewPaint = bindToViewsPaint;
71 }
72 }
73
74 /**
75 * Create a TextDrawable. This uses the given TextView to initialize paint and the text that
76 * will be drawn.
77 *
78 * @param tv The TextView / EditText using to initialize this drawable
79 * @param bindToViewsText Should this drawable mirror the text in the TextView
80 * @param bindToViewsPaint Should this drawable mirror changes to Paint in the TextView, like textColor, typeface, alpha etc.
81 * Note, this will override any changes made using setColorFilter or setAlpha.
82 */
83 public TextDrawable(TextView tv, boolean bindToViewsText, boolean bindToViewsPaint) {
84 this(tv, tv.getText().toString(), false, false);
85 }
86
87 /**
88 * Use the provided TextView/EditText to initialize the drawable.
89 * The Drawable will copy the Text and the Paint properties, however it will from that
90 * point on be independant of the TextView.
91 *
92 * @param tv a TextView or EditText or any of their children.
93 */
94 public TextDrawable(TextView tv) {
95 this(tv, false, false);
96 }
97
98 /**
99 * Use the provided TextView/EditText to initialize the drawable.
100 * The Drawable will copy the Paint properties, and use the provided text to initialise itself.
101 *
102 * @param tv a TextView or EditText or any of their children.
103 * @param s The String to draw
104 */
105 public TextDrawable(TextView tv, String s) {
106 this(tv, s, false, false);
107 }
108
109 @Override
110 public void draw(Canvas canvas) {
111 if (mBindToViewPaint && ref.get() != null) {
112 Paint p = ref.get().getPaint();
113 canvas.drawText(mText, 0, getBounds().height(), p);
114 } else {
115 if (mInitFitText) {
116 fitTextAndInit();
117 }
118 canvas.drawText(mText, 0, getBounds().height(), mPaint);
119 }
120 }
121
122 @Override
123 public void setAlpha(int alpha) {
124 mPaint.setAlpha(alpha);
125 }
126
127 @Override
128 public void setColorFilter(ColorFilter colorFilter) {
129 mPaint.setColorFilter(colorFilter);
130 }
131
132 @Override
133 public int getOpacity() {
134 int alpha = mPaint.getAlpha();
135 if (alpha == 0) {
136 return PixelFormat.TRANSPARENT;
137 } else if (alpha == 255) {
138 return PixelFormat.OPAQUE;
139 } else {
140 return PixelFormat.TRANSLUCENT;
141 }
142 }
143
144 private void init() {
145 Rect bounds = getBounds();
146 //We want to use some character to determine the max height of the text.
147 //Otherwise if we draw something like "..." they will appear centered
148 //Here I'm just going to use the entire alphabet to determine max height.
149 mPaint.getTextBounds("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+", 0, 1, mHeightBounds);
150 //This doesn't account for leading or training white spaces.
151 //mPaint.getTextBounds(mText, 0, mText.length(), bounds);
152 float width = mPaint.measureText(mText);
153 bounds.top = mHeightBounds.top;
154 bounds.bottom = mHeightBounds.bottom;
155 bounds.right = (int) width;
156 bounds.left = 0;
157 setBounds(bounds);
158 }
159
160 public void setPaint(Paint paint) {
161 mPaint = new Paint(paint);
162 //Since this can change the font used, we need to recalculate bounds.
163 if (mFitTextEnabled && !mInitFitText) {
164 fitTextAndInit();
165 } else {
166 init();
167 }
168 invalidateSelf();
169 }
170
171 public Paint getPaint() {
172 return mPaint;
173 }
174
175 public void setText(String text) {
176 mText = text;
177 //Since this can change the bounds of the text, we need to recalculate.
178 if (mFitTextEnabled && !mInitFitText) {
179 fitTextAndInit();
180 } else {
181 init();
182 }
183 invalidateSelf();
184 }
185
186 public String getText() {
187 return mText;
188 }
189
190 @Override
191 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
192
193 }
194
195 @Override
196 public void onTextChanged(CharSequence s, int start, int before, int count) {
197
198 }
199
200 @Override
201 public void afterTextChanged(Editable s) {
202 setText(s.toString());
203 }
204
205 /**
206 * Make the TextDrawable match the width of the View it's associated with.
207 * <p/>
208 * Note: While this option will not work if bindToViewPaint is true.
209 *
210 * @param fitText
211 */
212 public void setFillText(boolean fitText) {
213 mFitTextEnabled = fitText;
214 if (fitText) {
215 mPrevTextSize = mPaint.getTextSize();
216 if (ref.get() != null) {
217 if (ref.get().getWidth() > 0) {
218 fitTextAndInit();
219 } else {
220 mInitFitText = true;
221 }
222 }
223 } else {
224 if (mPrevTextSize > 0) {
225 mPaint.setTextSize(mPrevTextSize);
226 }
227 init();
228 }
229 }
230
231 private void fitTextAndInit() {
232 float fitWidth = ref.get().getWidth();
233 float textWidth = mPaint.measureText(mText);
234 float multi = fitWidth / textWidth;
235 mPaint.setTextSize(mPaint.getTextSize() * multi);
236 mInitFitText = false;
237 init();
238 }
239
240}