1 /* 2 * Copyright (C) 2014 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 package com.google.android.exoplayer.text; 17 18 import android.annotation.TargetApi; 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.content.res.TypedArray; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.graphics.Paint; 25 import android.graphics.Paint.Join; 26 import android.graphics.Paint.Style; 27 import android.graphics.RectF; 28 import android.graphics.Typeface; 29 import android.text.Layout.Alignment; 30 import android.text.StaticLayout; 31 import android.text.TextPaint; 32 import android.util.AttributeSet; 33 import android.util.DisplayMetrics; 34 import android.view.View; 35 36 import com.google.android.exoplayer.util.Util; 37 38 /** 39 * Since this class does not exist in recent version of ExoPlayer and used by 40 * {@link com.android.usbtuner.cc.CaptionWindowLayout}, this class is copied from 41 * older version of ExoPlayer. 42 * A view for rendering a single caption. 43 */ 44 @Deprecated 45 public class SubtitleView extends View { 46 // TODO: Change usage of this class to up-to-date class of ExoPlayer. 47 48 /** 49 * Ratio of inner padding to font size. 50 */ 51 private static final float INNER_PADDING_RATIO = 0.125f; 52 53 /** 54 * Temporary rectangle used for computing line bounds. 55 */ 56 private final RectF mLineBounds = new RectF(); 57 58 // Styled dimensions. 59 private final float mCornerRadius; 60 private final float mOutlineWidth; 61 private final float mShadowRadius; 62 private final float mShadowOffset; 63 64 private TextPaint mTextPaint; 65 private Paint mPaint; 66 67 private CharSequence mText; 68 69 private int mForegroundColor; 70 private int mBackgroundColor; 71 private int mEdgeColor; 72 private int mEdgeType; 73 74 private boolean mHasMeasurements; 75 private int mLastMeasuredWidth; 76 private StaticLayout mLayout; 77 78 private Alignment mAlignment; 79 private float mSpacingMult; 80 private float mSpacingAdd; 81 private int mInnerPaddingX; 82 SubtitleView(Context context)83 public SubtitleView(Context context) { 84 this(context, null); 85 } 86 SubtitleView(Context context, AttributeSet attrs)87 public SubtitleView(Context context, AttributeSet attrs) { 88 this(context, attrs, 0); 89 } 90 SubtitleView(Context context, AttributeSet attrs, int defStyleAttr)91 public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) { 92 super(context, attrs, defStyleAttr); 93 94 int[] viewAttr = {android.R.attr.text, android.R.attr.textSize, 95 android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier}; 96 TypedArray a = context.obtainStyledAttributes(attrs, viewAttr, defStyleAttr, 0); 97 CharSequence text = a.getText(0); 98 int textSize = a.getDimensionPixelSize(1, 15); 99 mSpacingAdd = a.getDimensionPixelSize(2, 0); 100 mSpacingMult = a.getFloat(3, 1); 101 a.recycle(); 102 103 Resources resources = getContext().getResources(); 104 DisplayMetrics displayMetrics = resources.getDisplayMetrics(); 105 int twoDpInPx = 106 Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT); 107 mCornerRadius = twoDpInPx; 108 mOutlineWidth = twoDpInPx; 109 mShadowRadius = twoDpInPx; 110 mShadowOffset = twoDpInPx; 111 112 mTextPaint = new TextPaint(); 113 mTextPaint.setAntiAlias(true); 114 mTextPaint.setSubpixelText(true); 115 116 mAlignment = Alignment.ALIGN_CENTER; 117 118 mPaint = new Paint(); 119 mPaint.setAntiAlias(true); 120 121 mInnerPaddingX = 0; 122 setText(text); 123 setTextSize(textSize); 124 setStyle(CaptionStyleCompat.DEFAULT); 125 } 126 127 @Override setBackgroundColor(int color)128 public void setBackgroundColor(int color) { 129 mBackgroundColor = color; 130 forceUpdate(false); 131 } 132 133 /** 134 * Sets the text to be displayed by the view. 135 * 136 * @param text The text to display. 137 */ setText(CharSequence text)138 public void setText(CharSequence text) { 139 this.mText = text; 140 forceUpdate(true); 141 } 142 143 /** 144 * Sets the text size in pixels. 145 * 146 * @param size The text size in pixels. 147 */ setTextSize(float size)148 public void setTextSize(float size) { 149 if (mTextPaint.getTextSize() != size) { 150 mTextPaint.setTextSize(size); 151 mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f); 152 forceUpdate(true); 153 } 154 } 155 156 /** 157 * Sets the text alignment. 158 * 159 * @param textAlignment The text alignment. 160 */ setTextAlignment(Alignment textAlignment)161 public void setTextAlignment(Alignment textAlignment) { 162 mAlignment = textAlignment; 163 } 164 165 /** 166 * Configures the view according to the given style. 167 * 168 * @param style A style for the view. 169 */ setStyle(CaptionStyleCompat style)170 public void setStyle(CaptionStyleCompat style) { 171 mForegroundColor = style.foregroundColor; 172 mBackgroundColor = style.backgroundColor; 173 mEdgeType = style.edgeType; 174 mEdgeColor = style.edgeColor; 175 setTypeface(style.typeface); 176 super.setBackgroundColor(style.windowColor); 177 forceUpdate(true); 178 } 179 setTypeface(Typeface typeface)180 private void setTypeface(Typeface typeface) { 181 if (mTextPaint.getTypeface() != typeface) { 182 mTextPaint.setTypeface(typeface); 183 forceUpdate(true); 184 } 185 } 186 forceUpdate(boolean needsLayout)187 private void forceUpdate(boolean needsLayout) { 188 if (needsLayout) { 189 mHasMeasurements = false; 190 requestLayout(); 191 } 192 invalidate(); 193 } 194 195 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)196 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 197 final int widthSpec = MeasureSpec.getSize(widthMeasureSpec); 198 199 if (computeMeasurements(widthSpec)) { 200 final StaticLayout layout = this.mLayout; 201 final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2; 202 final int height = layout.getHeight() + getPaddingTop() + getPaddingBottom(); 203 int width = 0; 204 int lineCount = layout.getLineCount(); 205 for (int i = 0; i < lineCount; i++) { 206 width = Math.max((int) Math.ceil(layout.getLineWidth(i)), width); 207 } 208 width += paddingX; 209 setMeasuredDimension(width, height); 210 } else if (Util.SDK_INT >= 11) { 211 setTooSmallMeasureDimensionV11(); 212 } else { 213 setMeasuredDimension(0, 0); 214 } 215 } 216 217 @TargetApi(11) setTooSmallMeasureDimensionV11()218 private void setTooSmallMeasureDimensionV11() { 219 setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL); 220 } 221 222 @Override onLayout(boolean changed, int l, int t, int r, int b)223 public void onLayout(boolean changed, int l, int t, int r, int b) { 224 final int width = r - l; 225 computeMeasurements(width); 226 } 227 computeMeasurements(int maxWidth)228 private boolean computeMeasurements(int maxWidth) { 229 if (mHasMeasurements && maxWidth == mLastMeasuredWidth) { 230 return true; 231 } 232 233 // Account for padding. 234 final int paddingX = getPaddingLeft() + getPaddingRight() + mInnerPaddingX * 2; 235 maxWidth -= paddingX; 236 if (maxWidth <= 0) { 237 return false; 238 } 239 240 mHasMeasurements = true; 241 mLastMeasuredWidth = maxWidth; 242 mLayout = new StaticLayout(mText, mTextPaint, maxWidth, mAlignment, 243 mSpacingMult, mSpacingAdd, true); 244 return true; 245 } 246 247 @Override onDraw(Canvas c)248 protected void onDraw(Canvas c) { 249 final StaticLayout layout = this.mLayout; 250 if (layout == null) { 251 return; 252 } 253 254 final int saveCount = c.save(); 255 final int innerPaddingX = this.mInnerPaddingX; 256 c.translate(getPaddingLeft() + innerPaddingX, getPaddingTop()); 257 258 final int lineCount = layout.getLineCount(); 259 final Paint textPaint = this.mTextPaint; 260 final Paint paint = this.mPaint; 261 final RectF bounds = mLineBounds; 262 263 if (Color.alpha(mBackgroundColor) > 0) { 264 final float cornerRadius = this.mCornerRadius; 265 float previousBottom = layout.getLineTop(0); 266 267 paint.setColor(mBackgroundColor); 268 paint.setStyle(Style.FILL); 269 270 for (int i = 0; i < lineCount; i++) { 271 bounds.left = layout.getLineLeft(i) - innerPaddingX; 272 bounds.right = layout.getLineRight(i) + innerPaddingX; 273 bounds.top = previousBottom; 274 bounds.bottom = layout.getLineBottom(i); 275 previousBottom = bounds.bottom; 276 277 c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint); 278 } 279 } 280 281 if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) { 282 textPaint.setStrokeJoin(Join.ROUND); 283 textPaint.setStrokeWidth(mOutlineWidth); 284 textPaint.setColor(mEdgeColor); 285 textPaint.setStyle(Style.FILL_AND_STROKE); 286 layout.draw(c); 287 } else if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) { 288 textPaint.setShadowLayer(mShadowRadius, mShadowOffset, mShadowOffset, mEdgeColor); 289 } else if (mEdgeType == CaptionStyleCompat.EDGE_TYPE_RAISED 290 || mEdgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) { 291 boolean raised = mEdgeType == CaptionStyleCompat.EDGE_TYPE_RAISED; 292 int colorUp = raised ? Color.WHITE : mEdgeColor; 293 int colorDown = raised ? mEdgeColor : Color.WHITE; 294 float offset = mShadowRadius / 2f; 295 textPaint.setColor(mForegroundColor); 296 textPaint.setStyle(Style.FILL); 297 textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp); 298 layout.draw(c); 299 textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown); 300 } 301 302 textPaint.setColor(mForegroundColor); 303 textPaint.setStyle(Style.FILL); 304 layout.draw(c); 305 textPaint.setShadowLayer(0, 0, 0, 0); 306 c.restoreToCount(saveCount); 307 } 308 309 } 310