blob: 071193cef162a7e10aac9953b930cffcfc3b91f9 [file] [log] [blame]
Alan Viverette398ec032013-08-21 13:43:00 -07001/*
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
17package com.android.internal.widget;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.res.Resources.Theme;
22import android.content.res.Resources;
23import android.content.res.TypedArray;
24import android.graphics.Canvas;
25import android.graphics.Color;
26import android.graphics.Paint;
27import android.graphics.Paint.Join;
28import android.graphics.Paint.Style;
29import android.graphics.RectF;
30import android.graphics.Typeface;
31import android.text.Layout.Alignment;
32import android.text.StaticLayout;
33import android.text.TextPaint;
Alan Viverette398ec032013-08-21 13:43:00 -070034import android.util.AttributeSet;
35import android.util.DisplayMetrics;
36import android.util.TypedValue;
37import android.view.View;
38import android.view.accessibility.CaptioningManager.CaptionStyle;
39
40public 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
Alan Viverette398ec032013-08-21 13:43:00 -070054 /** Reusable string builder used for holding text. */
55 private final StringBuilder mText = new StringBuilder();
Alan Viverette398ec032013-08-21 13:43:00 -070056
Alan Viverette7fe420f2013-09-25 12:35:00 -070057 private Alignment mAlignment;
58 private TextPaint mTextPaint;
59 private Paint mPaint;
Alan Viverette398ec032013-08-21 13:43:00 -070060
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
Alan Viveretted43daf32013-09-05 16:34:30 -070074 public SubtitleView(Context context) {
75 this(context, null);
76 }
77
Alan Viverette398ec032013-08-21 13:43:00 -070078 public SubtitleView(Context context, AttributeSet attrs) {
79 this(context, attrs, 0);
80 }
81
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();
Alan Viverette7b630632013-10-04 11:44:51 -0700116 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);
Alan Viverette398ec032013-08-21 13:43:00 -0700120 mShadowOffsetY = mShadowOffsetX;
121
Alan Viverette7fe420f2013-09-25 12:35:00 -0700122 mTextPaint = new TextPaint();
123 mTextPaint.setAntiAlias(true);
124 mTextPaint.setSubpixelText(true);
Alan Viverette398ec032013-08-21 13:43:00 -0700125
Alan Viverette7fe420f2013-09-25 12:35:00 -0700126 mPaint = new Paint();
127 mPaint.setAntiAlias(true);
Alan Viverette398ec032013-08-21 13:43:00 -0700128
129 setText(text);
130 setTextSize(textSize);
131 }
132
133 public void setText(int resId) {
134 final CharSequence text = getContext().getText(resId);
135 setText(text);
136 }
137
138 public void setText(CharSequence text) {
139 mText.setLength(0);
140 mText.append(text);
141
142 mHasMeasurements = false;
143
144 requestLayout();
145 }
146
147 public void setForegroundColor(int color) {
148 mForegroundColor = color;
149
150 invalidate();
151 }
152
153 @Override
154 public void setBackgroundColor(int color) {
155 mBackgroundColor = color;
156
157 invalidate();
158 }
159
160 public void setEdgeType(int edgeType) {
161 mEdgeType = edgeType;
162
163 invalidate();
164 }
165
166 public void setEdgeColor(int color) {
167 mEdgeColor = color;
168
169 invalidate();
170 }
171
Alan Viverette7b630632013-10-04 11:44:51 -0700172 /**
173 * Sets the text size in pixels.
174 *
175 * @param size the text size in pixels
176 */
Alan Viverette398ec032013-08-21 13:43:00 -0700177 public void setTextSize(float size) {
Alan Viverette7fe420f2013-09-25 12:35:00 -0700178 if (mTextPaint.getTextSize() != size) {
179 mTextPaint.setTextSize(size);
Alan Viverette398ec032013-08-21 13:43:00 -0700180 mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
Alan Viverette398ec032013-08-21 13:43:00 -0700181
Alan Viverette7fe420f2013-09-25 12:35:00 -0700182 mHasMeasurements = false;
Alan Viveretteb8a00572013-10-08 17:24:58 -0700183
184 requestLayout();
185 invalidate();
Alan Viverette398ec032013-08-21 13:43:00 -0700186 }
187 }
188
189 public void setTypeface(Typeface typeface) {
Alan Viverette7fe420f2013-09-25 12:35:00 -0700190 if (mTextPaint.getTypeface() != typeface) {
191 mTextPaint.setTypeface(typeface);
Alan Viverette398ec032013-08-21 13:43:00 -0700192
Alan Viverette7fe420f2013-09-25 12:35:00 -0700193 mHasMeasurements = false;
Alan Viveretteb8a00572013-10-08 17:24:58 -0700194
195 requestLayout();
196 invalidate();
Alan Viverette7fe420f2013-09-25 12:35:00 -0700197 }
198 }
199
200 public void setAlignment(Alignment textAlignment) {
201 if (mAlignment != textAlignment) {
202 mAlignment = textAlignment;
203
204 mHasMeasurements = false;
Alan Viveretteb8a00572013-10-08 17:24:58 -0700205
206 requestLayout();
207 invalidate();
Alan Viverette398ec032013-08-21 13:43:00 -0700208 }
209 }
210
211 @Override
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
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
235 private boolean computeMeasurements(int maxWidth) {
236 if (mHasMeasurements && maxWidth == mLastMeasuredWidth) {
237 return true;
238 }
239
240 // Account for padding.
Alan Viverette7fe420f2013-09-25 12:35:00 -0700241 final int paddingX = mPaddingLeft + mPaddingRight + mInnerPaddingX * 2;
Alan Viverette398ec032013-08-21 13:43:00 -0700242 maxWidth -= paddingX;
Alan Viverette398ec032013-08-21 13:43:00 -0700243 if (maxWidth <= 0) {
244 return false;
245 }
246
Alan Viverette7fe420f2013-09-25 12:35:00 -0700247 // 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.
Alan Viverette398ec032013-08-21 13:43:00 -0700250 mHasMeasurements = true;
251 mLastMeasuredWidth = maxWidth;
Alan Viverette7fe420f2013-09-25 12:35:00 -0700252 mLayout = new StaticLayout(
253 mText, mTextPaint, maxWidth, mAlignment, mSpacingMult, mSpacingAdd, true);
Alan Viverette398ec032013-08-21 13:43:00 -0700254
255 return true;
256 }
257
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
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
Alan Viverette398ec032013-08-21 13:43:00 -0700291 final int lineCount = layout.getLineCount();
Alan Viverette7fe420f2013-09-25 12:35:00 -0700292 final Paint textPaint = mTextPaint;
293 final Paint paint = mPaint;
294 final RectF bounds = mLineBounds;
Alan Viverette398ec032013-08-21 13:43:00 -0700295
Alan Viverette7fe420f2013-09-25 12:35:00 -0700296 if (Color.alpha(mBackgroundColor) > 0) {
Alan Viverette398ec032013-08-21 13:43:00 -0700297 final float cornerRadius = mCornerRadius;
298 float previousBottom = layout.getLineTop(0);
299
Alan Viverette7fe420f2013-09-25 12:35:00 -0700300 paint.setColor(mBackgroundColor);
301 paint.setStyle(Style.FILL);
302
Alan Viverette398ec032013-08-21 13:43:00 -0700303 for (int i = 0; i < lineCount; i++) {
Alan Viverette7fe420f2013-09-25 12:35:00 -0700304 bounds.left = layout.getLineLeft(i) -innerPaddingX;
Alan Viverette398ec032013-08-21 13:43:00 -0700305 bounds.right = layout.getLineRight(i) + innerPaddingX;
306 bounds.top = previousBottom;
307 bounds.bottom = layout.getLineBottom(i);
Alan Viverette398ec032013-08-21 13:43:00 -0700308 previousBottom = bounds.bottom;
309
310 c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
311 }
312 }
313
Alan Viverette7fe420f2013-09-25 12:35:00 -0700314 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);
Alan Viverette398ec032013-08-21 13:43:00 -0700319
320 for (int i = 0; i < lineCount; i++) {
321 layout.drawText(c, i, i);
322 }
Alan Viverette7fe420f2013-09-25 12:35:00 -0700323 } else if (mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
324 textPaint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mEdgeColor);
Alan Viverette398ec032013-08-21 13:43:00 -0700325 }
326
Alan Viverette7fe420f2013-09-25 12:35:00 -0700327 textPaint.setColor(mForegroundColor);
328 textPaint.setStyle(Style.FILL);
Alan Viverette398ec032013-08-21 13:43:00 -0700329
330 for (int i = 0; i < lineCount; i++) {
331 layout.drawText(c, i, i);
332 }
333
Alan Viverette7fe420f2013-09-25 12:35:00 -0700334 textPaint.setShadowLayer(0, 0, 0, 0);
Alan Viverette398ec032013-08-21 13:43:00 -0700335 c.restoreToCount(saveCount);
336 }
337}