ResizingTextView.java revision 7fa663ffa812ebc8cb2bb492eecc40485b145eba
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.widget;
15
16import android.content.Context;
17import android.content.res.TypedArray;
18import android.text.Layout;
19import android.util.AttributeSet;
20import android.util.TypedValue;
21import android.widget.TextView;
22
23import android.support.v17.leanback.R;
24
25/**
26 * <p>A {@link android.widget.TextView} that adjusts text size automatically in response
27 * to certain trigger conditions, such as text that wraps over multiple lines.</p>
28 * @hide
29 */
30class ResizingTextView extends TextView {
31
32    /**
33     * Trigger text resize when text flows into the last line of a multi-line text view.
34     */
35    public static final int TRIGGER_MAX_LINES = 0x01;
36
37    private int mTriggerConditions; // Union of trigger conditions
38    private int mResizedTextSize;
39    private boolean mMaintainLineSpacing;
40    private int mResizedPaddingAdjustmentTop;
41    private int mResizedPaddingAdjustmentBottom;
42
43    private boolean mIsResized = false;
44    // Remember default properties in case we need to restore them
45    private boolean mDefaultsInitialized = false;
46    private int mDefaultTextSize;
47    private float mDefaultLineSpacingExtra;
48    private int mDefaultPaddingTop;
49    private int mDefaultPaddingBottom;
50
51    public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
52        super(ctx, attrs, defStyleAttr);
53        TypedArray a = ctx.obtainStyledAttributes(attrs, R.styleable.lbResizingTextView,
54                defStyleAttr, defStyleRes);
55
56        try {
57            mTriggerConditions = a.getInt(
58                    R.styleable.lbResizingTextView_resizeTrigger, TRIGGER_MAX_LINES);
59            mResizedTextSize = a.getDimensionPixelSize(
60                    R.styleable.lbResizingTextView_resizedTextSize, -1);
61            mMaintainLineSpacing = a.getBoolean(
62                    R.styleable.lbResizingTextView_maintainLineSpacing, true);
63            mResizedPaddingAdjustmentTop = a.getDimensionPixelOffset(
64                    R.styleable.lbResizingTextView_resizedPaddingAdjustmentTop, 0);
65            mResizedPaddingAdjustmentBottom = a.getDimensionPixelOffset(
66                    R.styleable.lbResizingTextView_resizedPaddingAdjustmentBottom, 0);
67        } finally {
68            a.recycle();
69        }
70    }
71
72    public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr) {
73        this(ctx, attrs, defStyleAttr, 0);
74    }
75
76    public ResizingTextView(Context ctx, AttributeSet attrs) {
77        this(ctx, attrs, 0);
78    }
79
80    public ResizingTextView(Context ctx) {
81        this(ctx, null);
82    }
83
84    /**
85     * @return the trigger conditions used to determine whether resize occurs
86     */
87    public int getTriggerConditions() {
88        return mTriggerConditions;
89    }
90
91    /**
92     * Set the trigger conditions used to determine whether resize occurs. Pass
93     * a union of trigger condition constants, such as {@link ResizingTextView#TRIGGER_MAX_LINES}.
94     *
95     * @param conditions A union of trigger condition constants
96     */
97    public void setTriggerConditions(int conditions) {
98        if (mTriggerConditions != conditions) {
99            mTriggerConditions = conditions;
100            // Always request a layout when trigger conditions change
101            requestLayout();
102        }
103    }
104
105    /**
106     * @return the resized text size
107     */
108    public int getResizedTextSize() {
109        return mResizedTextSize;
110    }
111
112    /**
113     * Set the text size for resized text.
114     *
115     * @param size The text size for resized text
116     */
117    public void setResizedTextSize(int size) {
118        if (mResizedTextSize != size) {
119            mResizedTextSize = size;
120            resizeParamsChanged();
121        }
122    }
123
124    /**
125     * @return whether or not to maintain line spacing when resizing text.
126     * The default is true.
127     */
128    public boolean getMaintainLineSpacing() {
129        return mMaintainLineSpacing;
130    }
131
132    /**
133     * Set whether or not to maintain line spacing when resizing text.
134     * The default is true.
135     *
136     * @param maintain Whether or not to maintain line spacing
137     */
138    public void setMaintainLineSpacing(boolean maintain) {
139        if (mMaintainLineSpacing != maintain) {
140            mMaintainLineSpacing = maintain;
141            resizeParamsChanged();
142        }
143    }
144
145    /**
146     * @return desired adjustment to top padding for resized text
147     */
148    public int getResizedPaddingAdjustmentTop() {
149        return mResizedPaddingAdjustmentTop;
150    }
151
152    /**
153     * Set the desired adjustment to top padding for resized text.
154     *
155     * @param adjustment The adjustment to top padding, in pixels
156     */
157    public void setResizedPaddingAdjustmentTop(int adjustment) {
158        if (mResizedPaddingAdjustmentTop != adjustment) {
159            mResizedPaddingAdjustmentTop = adjustment;
160            resizeParamsChanged();
161        }
162    }
163
164    /**
165     * @return desired adjustment to bottom padding for resized text
166     */
167    public int getResizedPaddingAdjustmentBottom() {
168        return mResizedPaddingAdjustmentBottom;
169    }
170
171    /**
172     * Set the desired adjustment to bottom padding for resized text.
173     *
174     * @param adjustment The adjustment to bottom padding, in pixels
175     */
176    public void setResizedPaddingAdjustmentBottom(int adjustment) {
177        if (mResizedPaddingAdjustmentBottom != adjustment) {
178            mResizedPaddingAdjustmentBottom = adjustment;
179            resizeParamsChanged();
180        }
181    }
182
183    private void resizeParamsChanged() {
184        // If we're not resized, then changing resize parameters doesn't
185        // affect layout, so don't bother requesting.
186        if (mIsResized) {
187            requestLayout();
188        }
189    }
190
191    @Override
192    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
193        if (!mDefaultsInitialized) {
194            mDefaultTextSize = (int) getTextSize();
195            mDefaultLineSpacingExtra = getLineSpacingExtra();
196            mDefaultPaddingTop = getPaddingTop();
197            mDefaultPaddingBottom = getPaddingBottom();
198            mDefaultsInitialized = true;
199        }
200
201        // Always try first to measure with defaults. Otherwise, we may think we can get away
202        // with larger text sizes later when we actually can't.
203        setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize);
204        setLineSpacing(mDefaultLineSpacingExtra, getLineSpacingMultiplier());
205        setPaddingTopAndBottom(mDefaultPaddingTop, mDefaultPaddingBottom);
206
207        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
208
209        boolean resizeText = false;
210
211        final Layout layout = getLayout();
212        if (layout != null) {
213            if ((mTriggerConditions & TRIGGER_MAX_LINES) > 0) {
214                final int lineCount = layout.getLineCount();
215                final int maxLines = getMaxLines();
216                if (maxLines > 1) {
217                    resizeText = lineCount == maxLines;
218                }
219            }
220        }
221
222        final int currentSizePx = (int) getTextSize();
223        boolean remeasure = false;
224        if (resizeText) {
225            if (mResizedTextSize != -1 && currentSizePx != mResizedTextSize) {
226                setTextSize(TypedValue.COMPLEX_UNIT_PX, mResizedTextSize);
227                remeasure = true;
228            }
229            // Check for other desired adjustments in addition to the text size
230            final float targetLineSpacingExtra = mDefaultLineSpacingExtra +
231                    mDefaultTextSize - mResizedTextSize;
232            if (mMaintainLineSpacing && getLineSpacingExtra() != targetLineSpacingExtra) {
233                setLineSpacing(targetLineSpacingExtra, getLineSpacingMultiplier());
234                remeasure = true;
235            }
236            final int paddingTop = mDefaultPaddingTop + mResizedPaddingAdjustmentTop;
237            final int paddingBottom = mDefaultPaddingBottom + mResizedPaddingAdjustmentBottom;
238            if (getPaddingTop() != paddingTop || getPaddingBottom() != paddingBottom) {
239                setPaddingTopAndBottom(paddingTop, paddingBottom);
240                remeasure = true;
241            }
242        } else {
243            // Use default size, line spacing, and padding
244            if (mResizedTextSize != -1 && currentSizePx != mDefaultTextSize) {
245                setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize);
246                remeasure = true;
247            }
248            if (mMaintainLineSpacing && getLineSpacingExtra() != mDefaultLineSpacingExtra) {
249                setLineSpacing(mDefaultLineSpacingExtra, getLineSpacingMultiplier());
250                remeasure = true;
251            }
252            if (getPaddingTop() != mDefaultPaddingTop ||
253                    getPaddingBottom() != mDefaultPaddingBottom) {
254                setPaddingTopAndBottom(mDefaultPaddingTop, mDefaultPaddingBottom);
255                remeasure = true;
256            }
257        }
258        mIsResized = resizeText;
259        if (remeasure) {
260            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
261        }
262    }
263
264    private void setPaddingTopAndBottom(int paddingTop, int paddingBottom) {
265        if (isPaddingRelative()) {
266            setPaddingRelative(getPaddingStart(), paddingTop, getPaddingEnd(), paddingBottom);
267        } else {
268            setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), paddingBottom);
269        }
270    }
271}
272