1 package com.android.tv.settings.widget; 2 3 import android.content.Context; 4 import android.content.res.TypedArray; 5 import android.text.Layout; 6 import android.util.AttributeSet; 7 import android.util.TypedValue; 8 import android.widget.TextView; 9 10 import java.text.BreakIterator; 11 12 import com.android.tv.settings.R; 13 14 /** 15 * <p>A {@link android.widget.TextView} that changes the font size depending on the text width.</p> 16 * 17 * <p>We check for two signals that indicates that the text view is too small for the given text: 18 * 1. whether there is an ellipsis, and 2. whether a word spans two lines. If either of these 19 * conditions are met, we move down to a smaller font size. If the text is still too big after 20 * resizing, we give up.</p> 21 * 22 * <p>This method is not completely fool-proof because the string tokenizing method used by 23 * StaticLayout is not the same as we use here. So some things (e.g. email addresses) might be 24 * wrapped without shrinking.</p> 25 */ 26 public class ResizingTextView extends TextView { 27 ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr, int defStyleRes)28 public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 29 super(ctx, attrs, defStyleAttr, defStyleRes); 30 } 31 ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr)32 public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr) { 33 super(ctx, attrs, defStyleAttr); 34 } 35 ResizingTextView(Context ctx, AttributeSet attrs)36 public ResizingTextView(Context ctx, AttributeSet attrs) { 37 super(ctx, attrs); 38 } 39 ResizingTextView(Context ctx)40 public ResizingTextView(Context ctx) { 41 super(ctx); 42 } 43 44 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)45 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 46 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 47 boolean switchToSmallFont = false; 48 49 final Layout layout = getLayout(); 50 if (layout != null) { 51 final int lineCount = layout.getLineCount(); 52 if (lineCount > 0) { 53 final int ellipsisCount = layout.getEllipsisCount(lineCount - 1); 54 if (ellipsisCount > 0) { 55 // Always just go to small font if we have to ellipsize. 56 switchToSmallFont = true; 57 } else { 58 // No ellipsis, but we could have words that span lines. 59 BreakIterator iter = BreakIterator.getWordInstance(); 60 iter.setText(getText().toString()); 61 int breakIndex = iter.next(); 62 for (int line = 0; line < layout.getLineCount() - 1; line++) { 63 iter.following(layout.getLineStart(line)); 64 if (breakIndex > layout.getLineVisibleEnd(line)) { 65 // Line breaks before a word boundary. 66 switchToSmallFont = true; 67 break; 68 } 69 } 70 } 71 } 72 } 73 74 if (switchToSmallFont) { 75 final int normalSizePx = mContext.getResources().getDimensionPixelSize( 76 R.dimen.browse_item_title_font_size); 77 final int smallSizePx = mContext.getResources().getDimensionPixelSize( 78 R.dimen.browse_item_title_font_size_small); 79 80 // In addition to setting the text size, we fudge the line spacing and padding so it 81 // doesn't look so strange next to non-resized text. 82 setTextSize(TypedValue.COMPLEX_UNIT_PX, smallSizePx); 83 setLineSpacing(normalSizePx - smallSizePx, 1.0f); 84 setPaddingRelative(0, 0, 0, (normalSizePx - smallSizePx) / 2); 85 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 86 } 87 } 88 } 89