TextDrawable.java

  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}