1e96017ffe206b3212a9573982290f9faf0e371baChristine Franks/*
2e96017ffe206b3212a9573982290f9faf0e371baChristine Franks * Copyright (C) 2016 The Android Open Source Project
3e96017ffe206b3212a9573982290f9faf0e371baChristine Franks *
4e96017ffe206b3212a9573982290f9faf0e371baChristine Franks * Licensed under the Apache License, Version 2.0 (the "License");
5e96017ffe206b3212a9573982290f9faf0e371baChristine Franks * you may not use this file except in compliance with the License.
6e96017ffe206b3212a9573982290f9faf0e371baChristine Franks * You may obtain a copy of the License at
7e96017ffe206b3212a9573982290f9faf0e371baChristine Franks *
8e96017ffe206b3212a9573982290f9faf0e371baChristine Franks *      http://www.apache.org/licenses/LICENSE-2.0
9e96017ffe206b3212a9573982290f9faf0e371baChristine Franks *
10e96017ffe206b3212a9573982290f9faf0e371baChristine Franks * Unless required by applicable law or agreed to in writing, software
11e96017ffe206b3212a9573982290f9faf0e371baChristine Franks * distributed under the License is distributed on an "AS IS" BASIS,
12e96017ffe206b3212a9573982290f9faf0e371baChristine Franks * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e96017ffe206b3212a9573982290f9faf0e371baChristine Franks * See the License for the specific language governing permissions and
14e96017ffe206b3212a9573982290f9faf0e371baChristine Franks * limitations under the License.
15e96017ffe206b3212a9573982290f9faf0e371baChristine Franks */
16e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
17e96017ffe206b3212a9573982290f9faf0e371baChristine Frankspackage com.android.deskclock.widget;
18e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
19e96017ffe206b3212a9573982290f9faf0e371baChristine Franksimport android.text.Layout;
20e96017ffe206b3212a9573982290f9faf0e371baChristine Franksimport android.text.TextPaint;
21e96017ffe206b3212a9573982290f9faf0e371baChristine Franksimport android.util.TypedValue;
22e96017ffe206b3212a9573982290f9faf0e371baChristine Franksimport android.view.View;
23e96017ffe206b3212a9573982290f9faf0e371baChristine Franksimport android.widget.TextView;
24e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
25e96017ffe206b3212a9573982290f9faf0e371baChristine Franksimport static java.lang.Integer.MAX_VALUE;
26e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
27e96017ffe206b3212a9573982290f9faf0e371baChristine Franks/**
28e96017ffe206b3212a9573982290f9faf0e371baChristine Franks * A TextView which automatically re-sizes its text to fit within its boundaries.
29e96017ffe206b3212a9573982290f9faf0e371baChristine Franks */
30e96017ffe206b3212a9573982290f9faf0e371baChristine Frankspublic final class TextSizeHelper {
31e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
32e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    // The text view whose size this class controls.
33e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    private final TextView mTextView;
34e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
35e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    // Text paint used for measuring.
36e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    private final TextPaint mMeasurePaint = new TextPaint();
37e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
38e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    // The maximum size the text is allowed to be (in pixels).
39e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    private float mMaxTextSize;
40e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
41e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    // The maximum width the text is allowed to be (in pixels).
42e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    private int mWidthConstraint = MAX_VALUE;
43e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
44e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    // The maximum height the text is allowed to be (in pixels).
45e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    private int mHeightConstraint = MAX_VALUE;
46e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
47e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    // When {@code true} calls to {@link #requestLayout()} should be ignored.
48e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    private boolean mIgnoreRequestLayout;
49e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
50e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    public TextSizeHelper(TextView view) {
51e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        mTextView = view;
52e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        mMaxTextSize = view.getTextSize();
53e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    }
54e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
55e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
56e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        int widthConstraint = MAX_VALUE;
57e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        if (View.MeasureSpec.getMode(widthMeasureSpec) != View.MeasureSpec.UNSPECIFIED) {
58e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            widthConstraint = View.MeasureSpec.getSize(widthMeasureSpec)
59e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                    - mTextView.getCompoundPaddingLeft() - mTextView.getCompoundPaddingRight();
60e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        }
61e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
62e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        int heightConstraint = MAX_VALUE;
63e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        if (View.MeasureSpec.getMode(heightMeasureSpec) != View.MeasureSpec.UNSPECIFIED) {
64e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            heightConstraint = View.MeasureSpec.getSize(heightMeasureSpec)
65e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                    - mTextView.getCompoundPaddingTop() - mTextView.getCompoundPaddingBottom();
66e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        }
67e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
68e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        if (mTextView.isLayoutRequested() || mWidthConstraint != widthConstraint
69e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                || mHeightConstraint != heightConstraint) {
70e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            mWidthConstraint = widthConstraint;
71e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            mHeightConstraint = heightConstraint;
72e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
73e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            adjustTextSize();
74e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        }
75e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    }
76e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
77e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    public void onTextChanged(int lengthBefore, int lengthAfter) {
78e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        // The length of the text has changed, request layout to recalculate the current text
79e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        // size. This is necessary to workaround an optimization in TextView#checkForRelayout()
80e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        // which will avoid re-layout when the view has a fixed layout width.
81e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        if (lengthBefore != lengthAfter) {
82e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            mTextView.requestLayout();
83e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        }
84e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    }
85e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
86e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    public boolean shouldIgnoreRequestLayout() {
87e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        return mIgnoreRequestLayout;
88e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    }
89e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
90e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    private void adjustTextSize() {
91e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        final CharSequence text = mTextView.getText();
92e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        float textSize = mMaxTextSize;
93e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        if (text.length() > 0 && (mWidthConstraint < MAX_VALUE || mHeightConstraint < MAX_VALUE)) {
94e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            mMeasurePaint.set(mTextView.getPaint());
95e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
96e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            float minTextSize = 1f;
97e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            float maxTextSize = mMaxTextSize;
98e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            while (maxTextSize >= minTextSize) {
99e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                final float midTextSize = Math.round((maxTextSize + minTextSize) / 2f);
100e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                mMeasurePaint.setTextSize(midTextSize);
101e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
102e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                final float width = Layout.getDesiredWidth(text, mMeasurePaint);
103e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                final float height = mMeasurePaint.getFontMetricsInt(null);
104e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                if (width > mWidthConstraint || height > mHeightConstraint) {
105e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                    maxTextSize = midTextSize - 1f;
106e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                } else {
107e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                    textSize = midTextSize;
108e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                    minTextSize = midTextSize + 1f;
109e96017ffe206b3212a9573982290f9faf0e371baChristine Franks                }
110e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            }
111e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        }
112e96017ffe206b3212a9573982290f9faf0e371baChristine Franks
113e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        if (mTextView.getTextSize() != textSize) {
114e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            mIgnoreRequestLayout = true;
115e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
116e96017ffe206b3212a9573982290f9faf0e371baChristine Franks            mIgnoreRequestLayout = false;
117e96017ffe206b3212a9573982290f9faf0e371baChristine Franks        }
118e96017ffe206b3212a9573982290f9faf0e371baChristine Franks    }
119e96017ffe206b3212a9573982290f9faf0e371baChristine Franks}