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