package com.hardsoftstudio.real.textview.views; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.os.Handler; import android.text.Html; import android.text.method.LinkMovementMethod; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import com.hardsoftstudio.real.textview.utils.AutofitHelper; import com.hardsoftstudio.real.textview.utils.FontManager; import com.hardsoftstudio.real.textview.utils.HtmlTagHandler; import com.hardsoftstudio.real.textview.utils.LocalImageGetter; import com.hardsoftstudio.real.textview.utils.UrlImageGetter; import java.io.InputStream; public class RealTextView extends BaseTextView implements AutofitHelper.OnTextSizeChangeListener { public static final boolean DEBUG = false; private static final long DEFAULT_ANIMATION_SPEED = 100; private Handler mAnimateHandler; private CharSequence mCurrentText; private int mCurrentIndex; private long mAnimationSpeed = DEFAULT_ANIMATION_SPEED; private boolean isReverseMode = true; public boolean isForwardAnim = true; private boolean mAttachedToWindow = false; public RealTextView(Context context) { this(context, null, 0); } public RealTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RealTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); if (isInEditMode()) return; init(context, attrs, defStyle); } private void init(Context context, AttributeSet attrs, int defStyle) { mHelper = AutofitHelper.create(this, attrs, defStyle).addOnTextSizeChangeListener(this); FontManager.getInstance().setFont(this, attrs); } public void setFont(String fontPath) { FontManager.getInstance().setFont(this, fontPath); } public void setFont(int resId) { String fontPath = getContext().getString(resId); setFont(fontPath); } // Getters and Setters /** * {@inheritDoc} */ @Override public void setTextSize(int unit, float size) { super.setTextSize(unit, size); if (mHelper != null) { mHelper.setTextSize(unit, size); } } /** * {@inheritDoc} */ @Override public void setLines(int lines) { super.setLines(lines); if (mHelper != null) { mHelper.setMaxLines(lines); } } /** * {@inheritDoc} */ @Override public void setMaxLines(int maxLines) { super.setMaxLines(maxLines); if (mHelper != null) { mHelper.setMaxLines(maxLines); } } /** * @return the {@link AutofitHelper} for this View. */ public AutofitHelper getAutofitHelper() { return mHelper; } /** * @return whether or not the text will be automatically re-sized to fit its constraints. */ public boolean isSizeToFit() { return mHelper.isEnabled(); } /** * Sets the property of this field (sizeToFit), to automatically resize the text to fit its * constraints. */ public void setSizeToFit() { setSizeToFit(true); } /** * If true, the text will automatically be re-sized to fit its constraints; if false, it will * act like a normal TextView. */ public void setSizeToFit(boolean sizeToFit) { mHelper.setEnabled(sizeToFit); } /** * @return the maximum size (in pixels) of the text in this View. */ public float getMaxTextSize() { return mHelper.getMaxTextSize(); } /** * Set the maximum text size to the given value, interpreted as "scaled pixel" units. This size * is adjusted based on the current density and user font size preference. * * @param size The scaled pixel size. * @attr ref android.R.styleable#TextView_textSize */ public void setMaxTextSize(float size) { mHelper.setMaxTextSize(size); } /** * Set the maximum text size to a given unit and value. See TypedValue for the possible * dimension units. * * @param unit The desired dimension unit. * @param size The desired size in the given units. * @attr ref android.R.styleable#TextView_textSize */ public void setMaxTextSize(int unit, float size) { mHelper.setMaxTextSize(unit, size); } /** * @return the minimum size (in pixels) of the text in this View. */ public float getMinTextSize() { return mHelper.getMinTextSize(); } /** * Set the minimum text size to the given value, interpreted as "scaled pixel" units. This size * is adjusted based on the current density and user font size preference. * * @param minSize The scaled pixel size. * @attr ref me.grantland.R.styleable#AutofitTextView_minTextSize */ public void setMinTextSize(int minSize) { mHelper.setMinTextSize(TypedValue.COMPLEX_UNIT_SP, minSize); } /** * Set the minimum text size to a given unit and value. See TypedValue for the possible * dimension units. * * @param unit The desired dimension unit. * @param minSize The desired size in the given units. * @attr ref me.grantland.R.styleable#AutofitTextView_minTextSize */ public void setMinTextSize(int unit, float minSize) { mHelper.setMinTextSize(unit, minSize); } /** * @return the amount of precision used to calculate the correct text size to fit within its * bounds. */ public float getPrecision() { return mHelper.getPrecision(); } /** * Set the amount of precision used to calculate the correct text size to fit within its * bounds. Lower precision is more precise and takes more time. * * @param precision The amount of precision. */ public void setPrecision(float precision) { mHelper.setPrecision(precision); } @Override public void onTextSizeChange(float textSize, float oldTextSize) { // do nothing } /** * http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string */ static private String convertStreamToString(java.io.InputStream is) { java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } /** * Loads HTML from a raw resource, i.e., a HTML file in res/raw/. * This allows translatable resource (e.g., res/raw-de/ for german). * The containing HTML is parsed to Android's Spannable format and then displayed. * * @param id for example: R.raw.help */ public void setHtmlFromRawResource(Context context, int id, boolean useLocalDrawables) { setAutoFit(false); // load html from html file from /res/raw InputStream inputStreamText = context.getResources().openRawResource(id); setHtmlFromString(convertStreamToString(inputStreamText), useLocalDrawables); } /** * Parses String containing HTML to Android's Spannable format and displays it in this TextView. * * @param html String containing HTML, for example: "<b>Hello world!</b>" */ public void setHtmlFromString(String html, boolean useLocalDrawables) { setAutoFit(false); Html.ImageGetter imgGetter; if (useLocalDrawables) { imgGetter = new LocalImageGetter(getContext()); } else { imgGetter = new UrlImageGetter(this, getContext()); } // this uses Android's Html class for basic parsing, and HtmlTagHandler setText(Html.fromHtml(html, imgGetter, new HtmlTagHandler())); // make links work setMovementMethod(LinkMovementMethod.getInstance()); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mAttachedToWindow = false; Log.v("MPB", "Detached from win"); if (mAnimateHandler != null) mAnimateHandler.removeCallbacksAndMessages(null); } public void setIndeterminateLoadingTextView(boolean anim) { if (mAnimateHandler == null) { if (!anim) return; mAnimateHandler = new Handler(); } mCurrentText = getText(); mCurrentIndex = 0; setText("" + mCurrentText.charAt(0)); mAnimateHandler.postDelayed(isReverseMode ? ReverseLoadingRunnable : LoadingRunnable, mAnimationSpeed); } Runnable LoadingRunnable = new Runnable() { @Override public void run() { if (++mCurrentIndex >= mCurrentText.length()) { mCurrentIndex = 0; setText("" + mCurrentText.charAt(0)); } else { setText(getText().toString() + mCurrentText.charAt(mCurrentIndex)); } if (isAttachedToWindow() && getVisibility() == VISIBLE) { mAnimateHandler.postDelayed(this, mAnimationSpeed); } } }; Runnable ReverseLoadingRunnable = new Runnable() { @Override public void run() { mCurrentIndex = isForwardAnim ? mCurrentIndex + 1 : mCurrentIndex - 1; if (mCurrentIndex >= mCurrentText.length()) { isForwardAnim = false; --mCurrentIndex; } else if (mCurrentIndex < 0) { isForwardAnim = true; ++mCurrentIndex; } if (isForwardAnim) { setText(getText().toString() + mCurrentText.charAt(mCurrentIndex)); } else { setText(getText().toString().substring(0, mCurrentIndex)); } if (isAttachedToWindow() && getVisibility() == VISIBLE) { mAnimateHandler.postDelayed(this, mAnimationSpeed); } } }; public void setAnimationSpeed(long speed) { this.mAnimationSpeed = speed; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mAttachedToWindow = true; } @TargetApi(19) public boolean isAttachedToWindow() { if (Build.VERSION.SDK_INT < 19) { return mAttachedToWindow; } else { return super.isAttachedToWindow(); } } }