• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.widget;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.res.Resources.Theme;
22 import android.content.res.Resources;
23 import android.content.res.TypedArray;
24 import android.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.Paint;
27 import android.graphics.Paint.Join;
28 import android.graphics.Paint.Style;
29 import android.graphics.RectF;
30 import android.graphics.Typeface;
31 import android.text.Layout.Alignment;
32 import android.text.StaticLayout;
33 import android.text.TextPaint;
34 import android.util.AttributeSet;
35 import android.util.DisplayMetrics;
36 import android.util.TypedValue;
37 import android.view.View;
38 import android.view.accessibility.CaptioningManager.CaptionStyle;
39 
40 public class SubtitleView extends View {
41     // Ratio of inner padding to font size.
42     private static final float INNER_PADDING_RATIO = 0.125f;
43 
44     // Styled dimensions.
45     private final float mCornerRadius;
46     private final float mOutlineWidth;
47     private final float mShadowRadius;
48     private final float mShadowOffsetX;
49     private final float mShadowOffsetY;
50 
51     /** Temporary rectangle used for computing line bounds. */
52     private final RectF mLineBounds = new RectF();
53 
54     /** Reusable string builder used for holding text. */
55     private final StringBuilder mText = new StringBuilder();
56 
57     private Alignment mAlignment;
58     private TextPaint mTextPaint;
59     private Paint mPaint;
60 
61     private int mForegroundColor;
62     private int mBackgroundColor;
63     private int mEdgeColor;
64     private int mEdgeType;
65 
66     private boolean mHasMeasurements;
67     private int mLastMeasuredWidth;
68     private StaticLayout mLayout;
69 
70     private float mSpacingMult = 1;
71     private float mSpacingAdd = 0;
72     private int mInnerPaddingX = 0;
73 
SubtitleView(Context context)74     public SubtitleView(Context context) {
75         this(context, null);
76     }
77 
SubtitleView(Context context, AttributeSet attrs)78     public SubtitleView(Context context, AttributeSet attrs) {
79         this(context, attrs, 0);
80     }
81 
SubtitleView(Context context, AttributeSet attrs, int defStyle)82     public SubtitleView(Context context, AttributeSet attrs, int defStyle) {
83         super(context, attrs);
84 
85         final Theme theme = context.getTheme();
86         final TypedArray a = theme.obtainStyledAttributes(
87                     attrs, android.R.styleable.TextView, defStyle, 0);
88 
89         CharSequence text = "";
90         int textSize = 15;
91 
92         final int n = a.getIndexCount();
93         for (int i = 0; i < n; i++) {
94             int attr = a.getIndex(i);
95 
96             switch (attr) {
97                 case android.R.styleable.TextView_text:
98                     text = a.getText(attr);
99                     break;
100                 case android.R.styleable.TextView_lineSpacingExtra:
101                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
102                     break;
103                 case android.R.styleable.TextView_lineSpacingMultiplier:
104                     mSpacingMult = a.getFloat(attr, mSpacingMult);
105                     break;
106                 case android.R.styleable.TextAppearance_textSize:
107                     textSize = a.getDimensionPixelSize(attr, textSize);
108                     break;
109             }
110         }
111 
112         // Set up density-dependent properties.
113         // TODO: Move these to a default style.
114         final Resources res = getContext().getResources();
115         final DisplayMetrics m = res.getDisplayMetrics();
116         mCornerRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_corner_radius);
117         mOutlineWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_outline_width);
118         mShadowRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_shadow_radius);
119         mShadowOffsetX = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_shadow_offset);
120         mShadowOffsetY = mShadowOffsetX;
121 
122         mTextPaint = new TextPaint();
123         mTextPaint.setAntiAlias(true);
124         mTextPaint.setSubpixelText(true);
125 
126         mPaint = new Paint();
127         mPaint.setAntiAlias(true);
128 
129         setText(text);
130         setTextSize(textSize);
131     }
132 
setText(int resId)133     public void setText(int resId) {
134         final CharSequence text = getContext().getText(resId);
135         setText(text);
136     }
137 
setText(CharSequence text)138     public void setText(CharSequence text) {
139         mText.setLength(0);
140         mText.append(text);
141 
142         mHasMeasurements = false;
143 
144         requestLayout();
145     }
146 
setForegroundColor(int color)147     public void setForegroundColor(int color) {
148         mForegroundColor = color;
149 
150         invalidate();
151     }
152 
153     @Override
setBackgroundColor(int color)154     public void setBackgroundColor(int color) {
155         mBackgroundColor = color;
156 
157         invalidate();
158     }
159 
setEdgeType(int edgeType)160     public void setEdgeType(int edgeType) {
161         mEdgeType = edgeType;
162 
163         invalidate();
164     }
165 
setEdgeColor(int color)166     public void setEdgeColor(int color) {
167         mEdgeColor = color;
168 
169         invalidate();
170     }
171 
172     /**
173      * Sets the text size in pixels.
174      *
175      * @param size the text size in pixels
176      */
setTextSize(float size)177     public void setTextSize(float size) {
178         if (mTextPaint.getTextSize() != size) {
179             mTextPaint.setTextSize(size);
180             mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
181 
182             mHasMeasurements = false;
183 
184             requestLayout();
185             invalidate();
186         }
187     }
188 
setTypeface(Typeface typeface)189     public void setTypeface(Typeface typeface) {
190         if (mTextPaint.getTypeface() != typeface) {
191             mTextPaint.setTypeface(typeface);
192 
193             mHasMeasurements = false;
194 
195             requestLayout();
196             invalidate();
197         }
198     }
199 
setAlignment(Alignment textAlignment)200     public void setAlignment(Alignment textAlignment) {
201         if (mAlignment != textAlignment) {
202             mAlignment = textAlignment;
203 
204             mHasMeasurements = false;
205 
206             requestLayout();
207             invalidate();
208         }
209     }
210 
211     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)212     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
213         final int widthSpec = MeasureSpec.getSize(widthMeasureSpec);
214 
215         if (computeMeasurements(widthSpec)) {
216             final StaticLayout layout = mLayout;
217 
218             // Account for padding.
219             final int paddingX = mPaddingLeft + mPaddingRight + mInnerPaddingX * 2;
220             final int width = layout.getWidth() + paddingX;
221             final int height = layout.getHeight() + mPaddingTop + mPaddingBottom;
222             setMeasuredDimension(width, height);
223         } else {
224             setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL);
225         }
226     }
227 
228     @Override
onLayout(boolean changed, int l, int t, int r, int b)229     public void onLayout(boolean changed, int l, int t, int r, int b) {
230         final int width = r - l;
231 
232         computeMeasurements(width);
233     }
234 
computeMeasurements(int maxWidth)235     private boolean computeMeasurements(int maxWidth) {
236         if (mHasMeasurements && maxWidth == mLastMeasuredWidth) {
237             return true;
238         }
239 
240         // Account for padding.
241         final int paddingX = mPaddingLeft + mPaddingRight + mInnerPaddingX * 2;
242         maxWidth -= paddingX;
243         if (maxWidth <= 0) {
244             return false;
245         }
246 
247         // TODO: Implement minimum-difference line wrapping. Adding the results
248         // of Paint.getTextWidths() seems to return different values than
249         // StaticLayout.getWidth(), so this is non-trivial.
250         mHasMeasurements = true;
251         mLastMeasuredWidth = maxWidth;
252         mLayout = new StaticLayout(
253                 mText, mTextPaint, maxWidth, mAlignment, mSpacingMult, mSpacingAdd, true);
254 
255         return true;
256     }
257 
setStyle(int styleId)258     public void setStyle(int styleId) {
259         final Context context = mContext;
260         final ContentResolver cr = context.getContentResolver();
261         final CaptionStyle style;
262         if (styleId == CaptionStyle.PRESET_CUSTOM) {
263             style = CaptionStyle.getCustomStyle(cr);
264         } else {
265             style = CaptionStyle.PRESETS[styleId];
266         }
267 
268         mForegroundColor = style.foregroundColor;
269         mBackgroundColor = style.backgroundColor;
270         mEdgeType = style.edgeType;
271         mEdgeColor = style.edgeColor;
272         mHasMeasurements = false;
273 
274         final Typeface typeface = style.getTypeface();
275         setTypeface(typeface);
276 
277         requestLayout();
278     }
279 
280     @Override
onDraw(Canvas c)281     protected void onDraw(Canvas c) {
282         final StaticLayout layout = mLayout;
283         if (layout == null) {
284             return;
285         }
286 
287         final int saveCount = c.save();
288         final int innerPaddingX = mInnerPaddingX;
289         c.translate(mPaddingLeft + innerPaddingX, mPaddingTop);
290 
291         final int lineCount = layout.getLineCount();
292         final Paint textPaint = mTextPaint;
293         final Paint paint = mPaint;
294         final RectF bounds = mLineBounds;
295 
296         if (Color.alpha(mBackgroundColor) > 0) {
297             final float cornerRadius = mCornerRadius;
298             float previousBottom = layout.getLineTop(0);
299 
300             paint.setColor(mBackgroundColor);
301             paint.setStyle(Style.FILL);
302 
303             for (int i = 0; i < lineCount; i++) {
304                 bounds.left = layout.getLineLeft(i) -innerPaddingX;
305                 bounds.right = layout.getLineRight(i) + innerPaddingX;
306                 bounds.top = previousBottom;
307                 bounds.bottom = layout.getLineBottom(i);
308                 previousBottom = bounds.bottom;
309 
310                 c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
311             }
312         }
313 
314         if (mEdgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
315             textPaint.setStrokeJoin(Join.ROUND);
316             textPaint.setStrokeWidth(mOutlineWidth);
317             textPaint.setColor(mEdgeColor);
318             textPaint.setStyle(Style.FILL_AND_STROKE);
319 
320             for (int i = 0; i < lineCount; i++) {
321                 layout.drawText(c, i, i);
322             }
323         } else if (mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
324             textPaint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mEdgeColor);
325         }
326 
327         textPaint.setColor(mForegroundColor);
328         textPaint.setStyle(Style.FILL);
329 
330         for (int i = 0; i < lineCount; i++) {
331             layout.drawText(c, i, i);
332         }
333 
334         textPaint.setShadowLayer(0, 0, 0, 0);
335         c.restoreToCount(saveCount);
336     }
337 }
338