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