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