blob: 117463ab1ae54704288465d06e4a1983b1fc9375 [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;
Alan Viverette398ec032013-08-21 13:43:00 -070021import android.content.res.Resources;
22import android.content.res.TypedArray;
23import android.graphics.Canvas;
24import android.graphics.Color;
25import android.graphics.Paint;
26import android.graphics.Paint.Join;
27import android.graphics.Paint.Style;
28import android.graphics.RectF;
29import android.graphics.Typeface;
30import android.text.Layout.Alignment;
31import android.text.StaticLayout;
32import android.text.TextPaint;
Alan Viverette398ec032013-08-21 13:43:00 -070033import android.util.AttributeSet;
34import android.util.DisplayMetrics;
Alan Viverette398ec032013-08-21 13:43:00 -070035import android.view.View;
36import android.view.accessibility.CaptioningManager.CaptionStyle;
37
38public class SubtitleView extends View {
39 // Ratio of inner padding to font size.
40 private static final float INNER_PADDING_RATIO = 0.125f;
41
Alan Viverettece32ea72013-12-19 13:42:28 -080042 /** Color used for the shadowed edge of a bevel. */
43 private static final int COLOR_BEVEL_DARK = 0x80000000;
44
45 /** Color used for the illuminated edge of a bevel. */
46 private static final int COLOR_BEVEL_LIGHT = 0x80FFFFFF;
47
Alan Viverette398ec032013-08-21 13:43:00 -070048 // Styled dimensions.
49 private final float mCornerRadius;
50 private final float mOutlineWidth;
51 private final float mShadowRadius;
52 private final float mShadowOffsetX;
53 private final float mShadowOffsetY;
54
55 /** Temporary rectangle used for computing line bounds. */
56 private final RectF mLineBounds = new RectF();
57
Alan Viverette398ec032013-08-21 13:43:00 -070058 /** Reusable string builder used for holding text. */
59 private final StringBuilder mText = new StringBuilder();
Alan Viverette398ec032013-08-21 13:43:00 -070060
Alan Viverette7fe420f2013-09-25 12:35:00 -070061 private Alignment mAlignment;
62 private TextPaint mTextPaint;
63 private Paint mPaint;
Alan Viverette398ec032013-08-21 13:43:00 -070064
65 private int mForegroundColor;
66 private int mBackgroundColor;
67 private int mEdgeColor;
68 private int mEdgeType;
69
70 private boolean mHasMeasurements;
71 private int mLastMeasuredWidth;
72 private StaticLayout mLayout;
73
74 private float mSpacingMult = 1;
75 private float mSpacingAdd = 0;
76 private int mInnerPaddingX = 0;
77
Alan Viveretted43daf32013-09-05 16:34:30 -070078 public SubtitleView(Context context) {
79 this(context, null);
80 }
81
Alan Viverette398ec032013-08-21 13:43:00 -070082 public SubtitleView(Context context, AttributeSet attrs) {
83 this(context, attrs, 0);
84 }
85
Alan Viverette617feb92013-09-09 18:09:13 -070086 public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
87 this(context, attrs, defStyleAttr, 0);
88 }
89
90 public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
Alan Viverette398ec032013-08-21 13:43:00 -070091 super(context, attrs);
92
Alan Viverette617feb92013-09-09 18:09:13 -070093 final TypedArray a = context.obtainStyledAttributes(
94 attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes);
Alan Viverette398ec032013-08-21 13:43:00 -070095
96 CharSequence text = "";
97 int textSize = 15;
98
99 final int n = a.getIndexCount();
100 for (int i = 0; i < n; i++) {
101 int attr = a.getIndex(i);
102
103 switch (attr) {
104 case android.R.styleable.TextView_text:
105 text = a.getText(attr);
106 break;
107 case android.R.styleable.TextView_lineSpacingExtra:
108 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
109 break;
110 case android.R.styleable.TextView_lineSpacingMultiplier:
111 mSpacingMult = a.getFloat(attr, mSpacingMult);
112 break;
113 case android.R.styleable.TextAppearance_textSize:
114 textSize = a.getDimensionPixelSize(attr, textSize);
115 break;
116 }
117 }
118
119 // Set up density-dependent properties.
120 // TODO: Move these to a default style.
121 final Resources res = getContext().getResources();
Alan Viverette7b630632013-10-04 11:44:51 -0700122 mCornerRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_corner_radius);
123 mOutlineWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_outline_width);
124 mShadowRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_shadow_radius);
125 mShadowOffsetX = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_shadow_offset);
Alan Viverette398ec032013-08-21 13:43:00 -0700126 mShadowOffsetY = mShadowOffsetX;
127
Alan Viverette7fe420f2013-09-25 12:35:00 -0700128 mTextPaint = new TextPaint();
129 mTextPaint.setAntiAlias(true);
130 mTextPaint.setSubpixelText(true);
Alan Viverette398ec032013-08-21 13:43:00 -0700131
Alan Viverette7fe420f2013-09-25 12:35:00 -0700132 mPaint = new Paint();
133 mPaint.setAntiAlias(true);
Alan Viverette398ec032013-08-21 13:43:00 -0700134
135 setText(text);
136 setTextSize(textSize);
137 }
138
139 public void setText(int resId) {
140 final CharSequence text = getContext().getText(resId);
141 setText(text);
142 }
143
144 public void setText(CharSequence text) {
145 mText.setLength(0);
146 mText.append(text);
147
148 mHasMeasurements = false;
149
150 requestLayout();
151 }
152
153 public void setForegroundColor(int color) {
154 mForegroundColor = color;
155
156 invalidate();
157 }
158
159 @Override
160 public void setBackgroundColor(int color) {
161 mBackgroundColor = color;
162
163 invalidate();
164 }
165
166 public void setEdgeType(int edgeType) {
167 mEdgeType = edgeType;
168
169 invalidate();
170 }
171
172 public void setEdgeColor(int color) {
173 mEdgeColor = color;
174
175 invalidate();
176 }
177
Alan Viverette7b630632013-10-04 11:44:51 -0700178 /**
179 * Sets the text size in pixels.
180 *
181 * @param size the text size in pixels
182 */
Alan Viverette398ec032013-08-21 13:43:00 -0700183 public void setTextSize(float size) {
Alan Viverette7fe420f2013-09-25 12:35:00 -0700184 if (mTextPaint.getTextSize() != size) {
185 mTextPaint.setTextSize(size);
Alan Viverette398ec032013-08-21 13:43:00 -0700186 mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
Alan Viverette398ec032013-08-21 13:43:00 -0700187
Alan Viverette7fe420f2013-09-25 12:35:00 -0700188 mHasMeasurements = false;
Alan Viveretteb8a00572013-10-08 17:24:58 -0700189
190 requestLayout();
191 invalidate();
Alan Viverette398ec032013-08-21 13:43:00 -0700192 }
193 }
194
195 public void setTypeface(Typeface typeface) {
Alan Viverette7fe420f2013-09-25 12:35:00 -0700196 if (mTextPaint.getTypeface() != typeface) {
197 mTextPaint.setTypeface(typeface);
Alan Viverette398ec032013-08-21 13:43:00 -0700198
Alan Viverette7fe420f2013-09-25 12:35:00 -0700199 mHasMeasurements = false;
Alan Viveretteb8a00572013-10-08 17:24:58 -0700200
201 requestLayout();
202 invalidate();
Alan Viverette7fe420f2013-09-25 12:35:00 -0700203 }
204 }
205
206 public void setAlignment(Alignment textAlignment) {
207 if (mAlignment != textAlignment) {
208 mAlignment = textAlignment;
209
210 mHasMeasurements = false;
Alan Viveretteb8a00572013-10-08 17:24:58 -0700211
212 requestLayout();
213 invalidate();
Alan Viverette398ec032013-08-21 13:43:00 -0700214 }
215 }
216
217 @Override
218 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
219 final int widthSpec = MeasureSpec.getSize(widthMeasureSpec);
220
221 if (computeMeasurements(widthSpec)) {
222 final StaticLayout layout = mLayout;
223
224 // Account for padding.
225 final int paddingX = mPaddingLeft + mPaddingRight + mInnerPaddingX * 2;
226 final int width = layout.getWidth() + paddingX;
227 final int height = layout.getHeight() + mPaddingTop + mPaddingBottom;
228 setMeasuredDimension(width, height);
229 } else {
230 setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL);
231 }
232 }
233
234 @Override
235 public void onLayout(boolean changed, int l, int t, int r, int b) {
236 final int width = r - l;
237
238 computeMeasurements(width);
239 }
240
241 private boolean computeMeasurements(int maxWidth) {
242 if (mHasMeasurements && maxWidth == mLastMeasuredWidth) {
243 return true;
244 }
245
246 // Account for padding.
Alan Viverette7fe420f2013-09-25 12:35:00 -0700247 final int paddingX = mPaddingLeft + mPaddingRight + mInnerPaddingX * 2;
Alan Viverette398ec032013-08-21 13:43:00 -0700248 maxWidth -= paddingX;
Alan Viverette398ec032013-08-21 13:43:00 -0700249 if (maxWidth <= 0) {
250 return false;
251 }
252
Alan Viverette7fe420f2013-09-25 12:35:00 -0700253 // TODO: Implement minimum-difference line wrapping. Adding the results
254 // of Paint.getTextWidths() seems to return different values than
255 // StaticLayout.getWidth(), so this is non-trivial.
Alan Viverette398ec032013-08-21 13:43:00 -0700256 mHasMeasurements = true;
257 mLastMeasuredWidth = maxWidth;
Alan Viverette7fe420f2013-09-25 12:35:00 -0700258 mLayout = new StaticLayout(
259 mText, mTextPaint, maxWidth, mAlignment, mSpacingMult, mSpacingAdd, true);
Alan Viverette398ec032013-08-21 13:43:00 -0700260
261 return true;
262 }
263
264 public void setStyle(int styleId) {
265 final Context context = mContext;
266 final ContentResolver cr = context.getContentResolver();
267 final CaptionStyle style;
268 if (styleId == CaptionStyle.PRESET_CUSTOM) {
269 style = CaptionStyle.getCustomStyle(cr);
270 } else {
271 style = CaptionStyle.PRESETS[styleId];
272 }
273
274 mForegroundColor = style.foregroundColor;
275 mBackgroundColor = style.backgroundColor;
276 mEdgeType = style.edgeType;
277 mEdgeColor = style.edgeColor;
278 mHasMeasurements = false;
279
280 final Typeface typeface = style.getTypeface();
281 setTypeface(typeface);
282
283 requestLayout();
284 }
285
286 @Override
287 protected void onDraw(Canvas c) {
288 final StaticLayout layout = mLayout;
289 if (layout == null) {
290 return;
291 }
292
293 final int saveCount = c.save();
294 final int innerPaddingX = mInnerPaddingX;
295 c.translate(mPaddingLeft + innerPaddingX, mPaddingTop);
296
Alan Viverette398ec032013-08-21 13:43:00 -0700297 final int lineCount = layout.getLineCount();
Alan Viverette7fe420f2013-09-25 12:35:00 -0700298 final Paint textPaint = mTextPaint;
299 final Paint paint = mPaint;
300 final RectF bounds = mLineBounds;
Alan Viverette398ec032013-08-21 13:43:00 -0700301
Alan Viverette7fe420f2013-09-25 12:35:00 -0700302 if (Color.alpha(mBackgroundColor) > 0) {
Alan Viverette398ec032013-08-21 13:43:00 -0700303 final float cornerRadius = mCornerRadius;
304 float previousBottom = layout.getLineTop(0);
305
Alan Viverette7fe420f2013-09-25 12:35:00 -0700306 paint.setColor(mBackgroundColor);
307 paint.setStyle(Style.FILL);
308
Alan Viverette398ec032013-08-21 13:43:00 -0700309 for (int i = 0; i < lineCount; i++) {
Alan Viverette7fe420f2013-09-25 12:35:00 -0700310 bounds.left = layout.getLineLeft(i) -innerPaddingX;
Alan Viverette398ec032013-08-21 13:43:00 -0700311 bounds.right = layout.getLineRight(i) + innerPaddingX;
312 bounds.top = previousBottom;
313 bounds.bottom = layout.getLineBottom(i);
Alan Viverette398ec032013-08-21 13:43:00 -0700314 previousBottom = bounds.bottom;
315
316 c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
317 }
318 }
319
Alan Viverettece32ea72013-12-19 13:42:28 -0800320 final int edgeType = mEdgeType;
321 if (edgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
Alan Viverette7fe420f2013-09-25 12:35:00 -0700322 textPaint.setStrokeJoin(Join.ROUND);
323 textPaint.setStrokeWidth(mOutlineWidth);
324 textPaint.setColor(mEdgeColor);
325 textPaint.setStyle(Style.FILL_AND_STROKE);
Alan Viverette398ec032013-08-21 13:43:00 -0700326
327 for (int i = 0; i < lineCount; i++) {
328 layout.drawText(c, i, i);
329 }
Alan Viverettece32ea72013-12-19 13:42:28 -0800330 } else if (edgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
Alan Viverette7fe420f2013-09-25 12:35:00 -0700331 textPaint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mEdgeColor);
Alan Viverettece32ea72013-12-19 13:42:28 -0800332 } else if (edgeType == CaptionStyle.EDGE_TYPE_RAISED
333 || edgeType == CaptionStyle.EDGE_TYPE_DEPRESSED) {
334 final boolean raised = edgeType == CaptionStyle.EDGE_TYPE_RAISED;
335 final int colorUp = raised ? Color.WHITE : mEdgeColor;
336 final int colorDown = raised ? mEdgeColor : Color.WHITE;
337 final float offset = mShadowRadius / 2f;
338
339 textPaint.setColor(mForegroundColor);
340 textPaint.setStyle(Style.FILL);
341 textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
342
343 for (int i = 0; i < lineCount; i++) {
344 layout.drawText(c, i, i);
345 }
346
347 textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown);
Alan Viverette398ec032013-08-21 13:43:00 -0700348 }
349
Alan Viverette7fe420f2013-09-25 12:35:00 -0700350 textPaint.setColor(mForegroundColor);
351 textPaint.setStyle(Style.FILL);
Alan Viverette398ec032013-08-21 13:43:00 -0700352
353 for (int i = 0; i < lineCount; i++) {
354 layout.drawText(c, i, i);
355 }
356
Alan Viverette7fe420f2013-09-25 12:35:00 -0700357 textPaint.setShadowLayer(0, 0, 0, 0);
Alan Viverette398ec032013-08-21 13:43:00 -0700358 c.restoreToCount(saveCount);
359 }
360}