/** * Copyright 2016 Ali Muzaffar * <p> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alimuzaffar.lib.widgets; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.text.Editable; import android.text.TextWatcher; import android.widget.TextView; import java.lang.ref.WeakReference; public class TextDrawable extends Drawable implements TextWatcher { private WeakReference<TextView> ref; private String mText; private Paint mPaint; private Rect mHeightBounds; private boolean mBindToViewPaint = false; private float mPrevTextSize = 0; private boolean mInitFitText = false; private boolean mFitTextEnabled = false; /** * Create a TextDrawable using the given paint object and string * * @param paint paint object * @param s the string */ public TextDrawable(Paint paint, String s) { mText = s; mPaint = new Paint(paint); mHeightBounds = new Rect(); init(); } /** * Create a TextDrawable. This uses the given TextView to initialize paint and has initial text * that will be drawn. Initial text can also be useful for reserving space that may otherwise * not be available when setting compound drawables. * * @param tv The TextView / EditText using to initialize this drawable * @param initialText Optional initial text to display * @param bindToViewsText Should this drawable mirror the text in the TextView * @param bindToViewsPaint Should this drawable mirror changes to Paint in the TextView, like textColor, typeface, alpha etc. * Note, this will override any changes made using setColorFilter or setAlpha. */ public TextDrawable(TextView tv, String initialText, boolean bindToViewsText, boolean bindToViewsPaint) { this(tv.getPaint(), initialText); ref = new WeakReference<>(tv); if (bindToViewsText || bindToViewsPaint) { if (bindToViewsText) { tv.addTextChangedListener(this); } mBindToViewPaint = bindToViewsPaint; } } /** * Create a TextDrawable. This uses the given TextView to initialize paint and the text that * will be drawn. * * @param tv The TextView / EditText using to initialize this drawable * @param bindToViewsText Should this drawable mirror the text in the TextView * @param bindToViewsPaint Should this drawable mirror changes to Paint in the TextView, like textColor, typeface, alpha etc. * Note, this will override any changes made using setColorFilter or setAlpha. */ public TextDrawable(TextView tv, boolean bindToViewsText, boolean bindToViewsPaint) { this(tv, tv.getText().toString(), false, false); } /** * Use the provided TextView/EditText to initialize the drawable. * The Drawable will copy the Text and the Paint properties, however it will from that * point on be independant of the TextView. * * @param tv a TextView or EditText or any of their children. */ public TextDrawable(TextView tv) { this(tv, false, false); } /** * Use the provided TextView/EditText to initialize the drawable. * The Drawable will copy the Paint properties, and use the provided text to initialise itself. * * @param tv a TextView or EditText or any of their children. * @param s The String to draw */ public TextDrawable(TextView tv, String s) { this(tv, s, false, false); } @Override public void draw(Canvas canvas) { if (mBindToViewPaint && ref.get() != null) { Paint p = ref.get().getPaint(); canvas.drawText(mText, 0, getBounds().height(), p); } else { if (mInitFitText) { fitTextAndInit(); } canvas.drawText(mText, 0, getBounds().height(), mPaint); } } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter colorFilter) { mPaint.setColorFilter(colorFilter); } @Override public int getOpacity() { int alpha = mPaint.getAlpha(); if (alpha == 0) { return PixelFormat.TRANSPARENT; } else if (alpha == 255) { return PixelFormat.OPAQUE; } else { return PixelFormat.TRANSLUCENT; } } private void init() { Rect bounds = getBounds(); //We want to use some character to determine the max height of the text. //Otherwise if we draw something like "..." they will appear centered //Here I'm just going to use the entire alphabet to determine max height. mPaint.getTextBounds("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 0, 1, mHeightBounds); //This doesn't account for leading or training white spaces. //mPaint.getTextBounds(mText, 0, mText.length(), bounds); float width = mPaint.measureText(mText); bounds.top = mHeightBounds.top; bounds.bottom = mHeightBounds.bottom; bounds.right = (int) width; bounds.left = 0; setBounds(bounds); } public void setPaint(Paint paint) { mPaint = new Paint(paint); //Since this can change the font used, we need to recalculate bounds. if (mFitTextEnabled && !mInitFitText) { fitTextAndInit(); } else { init(); } invalidateSelf(); } public Paint getPaint() { return mPaint; } public void setText(String text) { mText = text; //Since this can change the bounds of the text, we need to recalculate. if (mFitTextEnabled && !mInitFitText) { fitTextAndInit(); } else { init(); } invalidateSelf(); } public String getText() { return mText; } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { setText(s.toString()); } /** * Make the TextDrawable match the width of the View it's associated with. * * Note: While this option will not work if bindToViewPaint is true. * * @param fitText whether to auto resize the text. */ public void setFillText(boolean fitText) { mFitTextEnabled = fitText; if (fitText) { mPrevTextSize = mPaint.getTextSize(); if (ref.get() != null) { if (ref.get().getWidth() > 0) { fitTextAndInit(); } else { mInitFitText = true; } } } else { if (mPrevTextSize > 0) { mPaint.setTextSize(mPrevTextSize); } init(); } } private void fitTextAndInit() { float fitWidth = ref.get().getWidth(); float textWidth = mPaint.measureText(mText); float multi = fitWidth / textWidth; mPaint.setTextSize(mPaint.getTextSize() * multi); mInitFitText = false; init(); } }