2013年4月22日 星期一

[Android] single line TextView auto reszie text size to match area height

Sometimes we want use a single line text witch can just match the height of TextView.
In http://developer.android.com/guide/practices/screens_support.html#screen-independence told us use dimen to adjust text size in different screen size, but we cant really achieve our goal by this way.

Search by Google can find http://stackoverflow.com/questions/5033012/auto-scale-textview-text-to-fit-within-bounds, but this TextView is set to avoid text size out of display bound,
,so I modified the code to make it:

PS. Because it's base on  "for", for better performance.
 I suggest that you also use dimen to set the default value.





package com.stan.libs.textview;

import android.content.Context;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class AutoResizeToFillHeightTextView extends TextView {

 // Minimum text size for this text view
 public static final float MAX_TEXT_SIZE = 999;
 public static final float MIN_TEXT_SIZE = 8;

 // Interface for resize notifications
 public interface OnTextResizeListener {
  public void onTextResize(TextView textView, float oldSize, float newSize);
 }

 // Registered resize listener
 private OnTextResizeListener mTextResizeListener;

 // Flag for text and/or size changes to force a resize
 private boolean mNeedsResize = false;

 // Text size that is set from code. This acts as a starting point for
 // resizing
 private float mTextSize;

 // Temporary upper bounds on the starting text size
 private float mMaxTextSize = MAX_TEXT_SIZE;

 // Lower bounds for text size
 private float mMinTextSize = MIN_TEXT_SIZE;

 // Text view line spacing multiplier
 private float mSpacingMult = 1.0f;

 // Text view additional line spacing
 private float mSpacingAdd = 0.0f;

 // Default constructor override
 public AutoResizeToFillHeightTextView(Context context) {
  this(context, null);
 }

 // Default constructor when inflating from XML file
 public AutoResizeToFillHeightTextView(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 }

 // Default constructor override
 public AutoResizeToFillHeightTextView(Context context, AttributeSet attrs,
   int defStyle) {
  super(context, attrs, defStyle);
  mTextSize = getTextSize();
 }

 /**
  * If the text view size changed, set the force resize flag to true
  */
 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  if (w != oldw || h != oldh) {
   mNeedsResize = true;
  }
 }

 /**
  * Register listener to receive resize notifications
  * 
  * @param listener
  */
 public void setOnResizeListener(OnTextResizeListener listener) {
  mTextResizeListener = listener;
 }

 /**
  * Override the set text size to update our internal reference values
  */
 @Override
 public void setTextSize(float size) {
  super.setTextSize(size);
  mTextSize = getTextSize();
 }

 /**
  * Override the set text size to update our internal reference values
  */
 @Override
 public void setTextSize(int unit, float size) {
  super.setTextSize(unit, size);
  mTextSize = getTextSize();
 }

 /**
  * Override the set line spacing to update our internal reference values
  */
 @Override
 public void setLineSpacing(float add, float mult) {
  super.setLineSpacing(add, mult);
  mSpacingMult = mult;
  mSpacingAdd = add;
 }

 /**
  * Set the upper text size limit and invalidate the view
  * 
  * @param maxTextSize
  */
 public void setMaxTextSize(float maxTextSize) {
  mMaxTextSize = maxTextSize;
  requestLayout();
  invalidate();
 }

 /**
  * Return upper text size limit
  * 
  * @return
  */
 public float getMaxTextSize() {
  return mMaxTextSize;
 }

 /**
  * Set the lower text size limit and invalidate the view
  * 
  * @param minTextSize
  */
 public void setMinTextSize(float minTextSize) {
  mMinTextSize = minTextSize;
  requestLayout();
  invalidate();
 }

 /**
  * Return lower text size limit
  * 
  * @return
  */
 public float getMinTextSize() {
  return mMinTextSize;
 }

 /**
  * Reset the text to the original size
  */
 public void resetTextSize() {
  if (mTextSize > 0) {
   super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
  }
 }

 /**
  * Resize text after measuring
  */
 @Override
 protected void onLayout(boolean changed, int left, int top, int right,
   int bottom) {
  if (changed || mNeedsResize) {
   int widthLimit = (right - left) - getCompoundPaddingLeft()
     - getCompoundPaddingRight();
   int heightLimit = (bottom - top) - getCompoundPaddingBottom()
     - getCompoundPaddingTop();
   resizeText(widthLimit, heightLimit);
  }
  super.onLayout(changed, left, top, right, bottom);
 }

 /**
  * Resize the text size with default width and height
  */
 public void resizeText() {
  int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
  int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
  resizeText(widthLimit, heightLimit);
 }

 /**
  * Resize the text size with specified width and height
  * 
  * @param width
  * @param height
  */
 public void resizeText(int width, int height) {
  CharSequence text = getText();
  // Do not resize if the view does not have dimensions or there is no
  // text
  if (text == null || text.length() == 0 || height <= 0 || width <= 0
    || mTextSize == 0) {
   return;
  }

  // Get the text view's paint object
  TextPaint textPaint = getPaint();

  // Store the current text size
  float oldTextSize = textPaint.getTextSize();
  // If there is a max text size set, use the lesser of that and the
  // default text size
  float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize,
    mMaxTextSize) : mTextSize;

  // Get the required text height
  int textHeight = getTextHeight(text, textPaint, width, targetTextSize);

  // Until we either fit within our text view or we had reached our min
  // text size, incrementally try smaller sizes
  while (textHeight > height && targetTextSize > mMinTextSize) {
   targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
   textHeight = getTextHeight(text, textPaint, width, targetTextSize);
  }

  // Until we either fit within our text view or we had reached our max
  // text size, incrementally try bigger sizes
  while (textHeight < height && targetTextSize < mMaxTextSize) {
   targetTextSize = Math.min(targetTextSize + 2, mMaxTextSize);
   textHeight = getTextHeight(text, textPaint, width, targetTextSize);
  }

  // Some devices try to auto adjust line spacing, so force default line
  // spacing
  // and invalidate the layout as a side effect
  textPaint.setTextSize(targetTextSize);
  setLineSpacing(mSpacingAdd, mSpacingMult);

  // Notify the listener if registered
  if (mTextResizeListener != null) {
   mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
  }

  // Reset force resize flag
  mNeedsResize = false;
 }

 // Set the text size of the text paint object and use a static layout to
 // render text off screen before measuring
 private int getTextHeight(CharSequence source, TextPaint paint, int width,
   float textSize) {
  // Update the text paint object
  paint.setTextSize(textSize);
  // Measure using a static layout
  StaticLayout layout = new StaticLayout(source, paint, width,
    Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
  return layout.getHeight();
 }
}

沒有留言:

張貼留言