diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/TextDrawable.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/TextDrawable.java new file mode 100644 index 000000000..c280cc938 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/TextDrawable.java @@ -0,0 +1,242 @@ +package org.joinmastodon.android.ui.utils; + +/* + * Copyright 2016 Ali Muzaffar + *

+ * 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 + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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. + */ + +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 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 + * @param s + */ + 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(), bindToViewsText, bindToViewsPaint); + } + + /** + * 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 + */ + 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(); + } + +}