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
 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}