/* * Copyright (C) 2014 The Android Open Source Project * * 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. */ package android.support.v17.leanback.widget; import android.content.Context; import android.content.res.TypedArray; import android.text.Layout; import android.util.AttributeSet; import android.util.TypedValue; import android.widget.TextView; import android.support.v17.leanback.R; /** *

A {@link android.widget.TextView} that adjusts text size automatically in response * to certain trigger conditions, such as text that wraps over multiple lines.

* @hide */ class ResizingTextView extends TextView { /** * Trigger text resize when text flows into the last line of a multi-line text view. */ public static final int TRIGGER_MAX_LINES = 0x01; private int mTriggerConditions; // Union of trigger conditions private int mResizedTextSize; // Note: Maintaining line spacing turned out not to be useful, and will be removed in // the next round of design for this class (b/18736630). For now it simply defaults to false. private boolean mMaintainLineSpacing; private int mResizedPaddingAdjustmentTop; private int mResizedPaddingAdjustmentBottom; private boolean mIsResized = false; // Remember default properties in case we need to restore them private boolean mDefaultsInitialized = false; private int mDefaultTextSize; private float mDefaultLineSpacingExtra; private int mDefaultPaddingTop; private int mDefaultPaddingBottom; public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(ctx, attrs, defStyleAttr); TypedArray a = ctx.obtainStyledAttributes(attrs, R.styleable.lbResizingTextView, defStyleAttr, defStyleRes); try { mTriggerConditions = a.getInt( R.styleable.lbResizingTextView_resizeTrigger, TRIGGER_MAX_LINES); mResizedTextSize = a.getDimensionPixelSize( R.styleable.lbResizingTextView_resizedTextSize, -1); mMaintainLineSpacing = a.getBoolean( R.styleable.lbResizingTextView_maintainLineSpacing, false); mResizedPaddingAdjustmentTop = a.getDimensionPixelOffset( R.styleable.lbResizingTextView_resizedPaddingAdjustmentTop, 0); mResizedPaddingAdjustmentBottom = a.getDimensionPixelOffset( R.styleable.lbResizingTextView_resizedPaddingAdjustmentBottom, 0); } finally { a.recycle(); } } public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr) { this(ctx, attrs, defStyleAttr, 0); } public ResizingTextView(Context ctx, AttributeSet attrs) { // TODO We should define our own style that inherits from TextViewStyle, to set defaults // for new styleables, We then pass the appropriate R.attr up the constructor chain here. this(ctx, attrs, android.R.attr.textViewStyle); } public ResizingTextView(Context ctx) { this(ctx, null); } /** * @return the trigger conditions used to determine whether resize occurs */ public int getTriggerConditions() { return mTriggerConditions; } /** * Set the trigger conditions used to determine whether resize occurs. Pass * a union of trigger condition constants, such as {@link ResizingTextView#TRIGGER_MAX_LINES}. * * @param conditions A union of trigger condition constants */ public void setTriggerConditions(int conditions) { if (mTriggerConditions != conditions) { mTriggerConditions = conditions; // Always request a layout when trigger conditions change requestLayout(); } } /** * @return the resized text size */ public int getResizedTextSize() { return mResizedTextSize; } /** * Set the text size for resized text. * * @param size The text size for resized text */ public void setResizedTextSize(int size) { if (mResizedTextSize != size) { mResizedTextSize = size; resizeParamsChanged(); } } /** * @return whether or not to maintain line spacing when resizing text. * The default is true. */ public boolean getMaintainLineSpacing() { return mMaintainLineSpacing; } /** * Set whether or not to maintain line spacing when resizing text. * The default is true. * * @param maintain Whether or not to maintain line spacing */ public void setMaintainLineSpacing(boolean maintain) { if (mMaintainLineSpacing != maintain) { mMaintainLineSpacing = maintain; resizeParamsChanged(); } } /** * @return desired adjustment to top padding for resized text */ public int getResizedPaddingAdjustmentTop() { return mResizedPaddingAdjustmentTop; } /** * Set the desired adjustment to top padding for resized text. * * @param adjustment The adjustment to top padding, in pixels */ public void setResizedPaddingAdjustmentTop(int adjustment) { if (mResizedPaddingAdjustmentTop != adjustment) { mResizedPaddingAdjustmentTop = adjustment; resizeParamsChanged(); } } /** * @return desired adjustment to bottom padding for resized text */ public int getResizedPaddingAdjustmentBottom() { return mResizedPaddingAdjustmentBottom; } /** * Set the desired adjustment to bottom padding for resized text. * * @param adjustment The adjustment to bottom padding, in pixels */ public void setResizedPaddingAdjustmentBottom(int adjustment) { if (mResizedPaddingAdjustmentBottom != adjustment) { mResizedPaddingAdjustmentBottom = adjustment; resizeParamsChanged(); } } private void resizeParamsChanged() { // If we're not resized, then changing resize parameters doesn't // affect layout, so don't bother requesting. if (mIsResized) { requestLayout(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (!mDefaultsInitialized) { mDefaultTextSize = (int) getTextSize(); mDefaultLineSpacingExtra = getLineSpacingExtra(); mDefaultPaddingTop = getPaddingTop(); mDefaultPaddingBottom = getPaddingBottom(); mDefaultsInitialized = true; } // Always try first to measure with defaults. Otherwise, we may think we can get away // with larger text sizes later when we actually can't. setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize); setLineSpacing(mDefaultLineSpacingExtra, getLineSpacingMultiplier()); setPaddingTopAndBottom(mDefaultPaddingTop, mDefaultPaddingBottom); super.onMeasure(widthMeasureSpec, heightMeasureSpec); boolean resizeText = false; final Layout layout = getLayout(); if (layout != null) { if ((mTriggerConditions & TRIGGER_MAX_LINES) > 0) { final int lineCount = layout.getLineCount(); final int maxLines = getMaxLines(); if (maxLines > 1) { resizeText = lineCount == maxLines; } } } final int currentSizePx = (int) getTextSize(); boolean remeasure = false; if (resizeText) { if (mResizedTextSize != -1 && currentSizePx != mResizedTextSize) { setTextSize(TypedValue.COMPLEX_UNIT_PX, mResizedTextSize); remeasure = true; } // Check for other desired adjustments in addition to the text size final float targetLineSpacingExtra = mDefaultLineSpacingExtra + mDefaultTextSize - mResizedTextSize; if (mMaintainLineSpacing && getLineSpacingExtra() != targetLineSpacingExtra) { setLineSpacing(targetLineSpacingExtra, getLineSpacingMultiplier()); remeasure = true; } final int paddingTop = mDefaultPaddingTop + mResizedPaddingAdjustmentTop; final int paddingBottom = mDefaultPaddingBottom + mResizedPaddingAdjustmentBottom; if (getPaddingTop() != paddingTop || getPaddingBottom() != paddingBottom) { setPaddingTopAndBottom(paddingTop, paddingBottom); remeasure = true; } } else { // Use default size, line spacing, and padding if (mResizedTextSize != -1 && currentSizePx != mDefaultTextSize) { setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize); remeasure = true; } if (mMaintainLineSpacing && getLineSpacingExtra() != mDefaultLineSpacingExtra) { setLineSpacing(mDefaultLineSpacingExtra, getLineSpacingMultiplier()); remeasure = true; } if (getPaddingTop() != mDefaultPaddingTop || getPaddingBottom() != mDefaultPaddingBottom) { setPaddingTopAndBottom(mDefaultPaddingTop, mDefaultPaddingBottom); remeasure = true; } } mIsResized = resizeText; if (remeasure) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } private void setPaddingTopAndBottom(int paddingTop, int paddingBottom) { if (isPaddingRelative()) { setPaddingRelative(getPaddingStart(), paddingTop, getPaddingEnd(), paddingBottom); } else { setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), paddingBottom); } } }