mirror of
https://github.com/androidx/media.git
synced 2025-05-07 23:50:44 +08:00
Optimize captions.
SubtitleLayout no longer trigger re-layouts of the view hierarchy. Instead, the SubtitleLayout just invalidates itself. This is made possible by making SubtitleLayout a regular View that draws each Cue directly onto the canvas, rather than having SubtitleLayout be a ViewGroup with a child View for each Cue.
This commit is contained in:
parent
8b08efce79
commit
884e7a4170
@ -0,0 +1,293 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.text;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Paint.Join;
|
||||||
|
import android.graphics.Paint.Style;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.text.Layout.Alignment;
|
||||||
|
import android.text.StaticLayout;
|
||||||
|
import android.text.TextPaint;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws {@link Cue}s.
|
||||||
|
*/
|
||||||
|
/* package */ final class CuePainter {
|
||||||
|
|
||||||
|
private static final String TAG = "CuePainter";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ratio of inner padding to font size.
|
||||||
|
*/
|
||||||
|
private static final float INNER_PADDING_RATIO = 0.125f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the same line height ratio as WebVtt to match the display with the preview.
|
||||||
|
* WebVtt specifies line height as 5.3% of the viewport height.
|
||||||
|
*/
|
||||||
|
private static final float LINE_HEIGHT_FRACTION = 0.0533f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default bottom padding to apply when {@link Cue#line} is {@link Cue#UNSET_VALUE}, as a
|
||||||
|
* fraction of the viewport height.
|
||||||
|
*/
|
||||||
|
private static final float DEFAULT_BOTTOM_PADDING_FRACTION = 0.08f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary rectangle used for computing line bounds.
|
||||||
|
*/
|
||||||
|
private final RectF lineBounds = new RectF();
|
||||||
|
|
||||||
|
// Styled dimensions.
|
||||||
|
private final float cornerRadius;
|
||||||
|
private final float outlineWidth;
|
||||||
|
private final float shadowRadius;
|
||||||
|
private final float shadowOffset;
|
||||||
|
private final float spacingMult;
|
||||||
|
private final float spacingAdd;
|
||||||
|
|
||||||
|
private final TextPaint textPaint;
|
||||||
|
private final Paint paint;
|
||||||
|
|
||||||
|
// Previous input variables.
|
||||||
|
private CharSequence cueText;
|
||||||
|
private int cuePosition;
|
||||||
|
private Alignment cueAlignment;
|
||||||
|
private int foregroundColor;
|
||||||
|
private int backgroundColor;
|
||||||
|
private int windowColor;
|
||||||
|
private int edgeColor;
|
||||||
|
private int edgeType;
|
||||||
|
private int parentLeft;
|
||||||
|
private int parentTop;
|
||||||
|
private int parentRight;
|
||||||
|
private int parentBottom;
|
||||||
|
|
||||||
|
// Derived drawing variables.
|
||||||
|
private StaticLayout textLayout;
|
||||||
|
private int textLeft;
|
||||||
|
private int textTop;
|
||||||
|
private int textPaddingX;
|
||||||
|
|
||||||
|
public CuePainter(Context context) {
|
||||||
|
int[] viewAttr = {android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier};
|
||||||
|
TypedArray styledAttributes = context.obtainStyledAttributes(null, viewAttr, 0, 0);
|
||||||
|
spacingAdd = styledAttributes.getDimensionPixelSize(0, 0);
|
||||||
|
spacingMult = styledAttributes.getFloat(1, 1);
|
||||||
|
styledAttributes.recycle();
|
||||||
|
|
||||||
|
Resources resources = context.getResources();
|
||||||
|
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
|
||||||
|
int twoDpInPx = Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT);
|
||||||
|
cornerRadius = twoDpInPx;
|
||||||
|
outlineWidth = twoDpInPx;
|
||||||
|
shadowRadius = twoDpInPx;
|
||||||
|
shadowOffset = twoDpInPx;
|
||||||
|
|
||||||
|
textPaint = new TextPaint();
|
||||||
|
textPaint.setAntiAlias(true);
|
||||||
|
textPaint.setSubpixelText(true);
|
||||||
|
|
||||||
|
paint = new Paint();
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
paint.setStyle(Style.FILL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the provided {@link Cue} into a canvas with the specified styling.
|
||||||
|
* <p>
|
||||||
|
* A call to this method is able to use cached results of calculations made during the previous
|
||||||
|
* call, and so an instance of this class is able to optimize repeated calls to this method in
|
||||||
|
* which the same parameters are passed.
|
||||||
|
*
|
||||||
|
* @param cue The cue to draw.
|
||||||
|
* @param style The style to use when drawing the cue text.
|
||||||
|
* @param fontScale The font scale.
|
||||||
|
* @param canvas The canvas into which to draw.
|
||||||
|
* @param cueBoxLeft The left position of the enclosing cue box.
|
||||||
|
* @param cueBoxTop The top position of the enclosing cue box.
|
||||||
|
* @param cueBoxRight The right position of the enclosing cue box.
|
||||||
|
* @param cueBoxBottom The bottom position of the enclosing cue box.
|
||||||
|
*/
|
||||||
|
public void draw(Cue cue, CaptionStyleCompat style, float fontScale, Canvas canvas,
|
||||||
|
int cueBoxLeft, int cueBoxTop, int cueBoxRight, int cueBoxBottom) {
|
||||||
|
if (TextUtils.equals(cueText, cue.text)
|
||||||
|
&& cuePosition == cue.position
|
||||||
|
&& Util.areEqual(cueAlignment, cue.alignment)
|
||||||
|
&& foregroundColor == style.foregroundColor
|
||||||
|
&& backgroundColor == style.backgroundColor
|
||||||
|
&& windowColor == style.windowColor
|
||||||
|
&& edgeType == style.edgeType
|
||||||
|
&& edgeColor == style.edgeColor
|
||||||
|
&& Util.areEqual(textPaint.getTypeface(), style.typeface)
|
||||||
|
&& parentLeft == cueBoxLeft
|
||||||
|
&& parentTop == cueBoxTop
|
||||||
|
&& parentRight == cueBoxRight
|
||||||
|
&& parentBottom == cueBoxBottom) {
|
||||||
|
// We can use the cached layout.
|
||||||
|
drawLayout(canvas);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cueText = cue.text;
|
||||||
|
cuePosition = cue.position;
|
||||||
|
cueAlignment = cue.alignment;
|
||||||
|
foregroundColor = style.foregroundColor;
|
||||||
|
backgroundColor = style.backgroundColor;
|
||||||
|
windowColor = style.windowColor;
|
||||||
|
edgeType = style.edgeType;
|
||||||
|
edgeColor = style.edgeColor;
|
||||||
|
textPaint.setTypeface(style.typeface);
|
||||||
|
parentLeft = cueBoxLeft;
|
||||||
|
parentTop = cueBoxTop;
|
||||||
|
parentRight = cueBoxRight;
|
||||||
|
parentBottom = cueBoxBottom;
|
||||||
|
|
||||||
|
int parentWidth = parentRight - parentLeft;
|
||||||
|
int parentHeight = parentBottom - parentTop;
|
||||||
|
|
||||||
|
float textSize = LINE_HEIGHT_FRACTION * parentHeight * fontScale;
|
||||||
|
textPaint.setTextSize(textSize);
|
||||||
|
int textPaddingX = (int) (textSize * INNER_PADDING_RATIO + 0.5f);
|
||||||
|
int availableWidth = parentWidth - textPaddingX * 2;
|
||||||
|
if (availableWidth <= 0) {
|
||||||
|
Log.w(TAG, "Skipped drawing subtitle cue (insufficient space)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Alignment layoutAlignment = cueAlignment == null ? Alignment.ALIGN_CENTER : cueAlignment;
|
||||||
|
textLayout = new StaticLayout(cueText, textPaint, availableWidth, layoutAlignment, spacingMult,
|
||||||
|
spacingAdd, true);
|
||||||
|
|
||||||
|
int textHeight = textLayout.getHeight();
|
||||||
|
int textWidth = 0;
|
||||||
|
int lineCount = textLayout.getLineCount();
|
||||||
|
for (int i = 0; i < lineCount; i++) {
|
||||||
|
textWidth = Math.max((int) Math.ceil(textLayout.getLineWidth(i)), textWidth);
|
||||||
|
}
|
||||||
|
textWidth += textPaddingX * 2;
|
||||||
|
|
||||||
|
int textLeft = (parentWidth - textWidth) / 2;
|
||||||
|
int textRight = textLeft + textWidth;
|
||||||
|
int textTop = parentBottom - textHeight
|
||||||
|
- (int) (parentHeight * DEFAULT_BOTTOM_PADDING_FRACTION);
|
||||||
|
int textBottom = textTop + textHeight;
|
||||||
|
|
||||||
|
if (cue.position != Cue.UNSET_VALUE) {
|
||||||
|
if (cue.alignment == Alignment.ALIGN_OPPOSITE) {
|
||||||
|
textRight = (parentWidth * cue.position) / 100 + parentLeft;
|
||||||
|
textLeft = Math.max(textRight - textWidth, parentLeft);
|
||||||
|
} else {
|
||||||
|
textLeft = (parentWidth * cue.position) / 100 + parentLeft;
|
||||||
|
textRight = Math.min(textLeft + textWidth, parentRight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cue.line != Cue.UNSET_VALUE) {
|
||||||
|
textTop = (parentHeight * cue.line) / 100 + parentTop;
|
||||||
|
textBottom = textTop + textHeight;
|
||||||
|
if (textBottom > parentBottom) {
|
||||||
|
textTop = parentBottom - textHeight;
|
||||||
|
textBottom = parentBottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textWidth = textRight - textLeft;
|
||||||
|
|
||||||
|
// Update the derived drawing variables.
|
||||||
|
this.textLayout = new StaticLayout(cueText, textPaint, textWidth, layoutAlignment, spacingMult,
|
||||||
|
spacingAdd, true);
|
||||||
|
this.textLeft = textLeft;
|
||||||
|
this.textTop = textTop;
|
||||||
|
this.textPaddingX = textPaddingX;
|
||||||
|
|
||||||
|
drawLayout(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws {@link #textLayout} into the provided canvas.
|
||||||
|
*
|
||||||
|
* @param canvas The canvas into which to draw.
|
||||||
|
*/
|
||||||
|
private void drawLayout(Canvas canvas) {
|
||||||
|
final StaticLayout layout = textLayout;
|
||||||
|
if (layout == null) {
|
||||||
|
// Nothing to draw.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int saveCount = canvas.save();
|
||||||
|
canvas.translate(textLeft, textTop);
|
||||||
|
|
||||||
|
if (Color.alpha(windowColor) > 0) {
|
||||||
|
paint.setColor(windowColor);
|
||||||
|
canvas.drawRect(-textPaddingX, 0, layout.getWidth() + textPaddingX, layout.getHeight(),
|
||||||
|
paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Color.alpha(backgroundColor) > 0) {
|
||||||
|
paint.setColor(backgroundColor);
|
||||||
|
float previousBottom = layout.getLineTop(0);
|
||||||
|
int lineCount = layout.getLineCount();
|
||||||
|
for (int i = 0; i < lineCount; i++) {
|
||||||
|
lineBounds.left = layout.getLineLeft(i) - textPaddingX;
|
||||||
|
lineBounds.right = layout.getLineRight(i) + textPaddingX;
|
||||||
|
lineBounds.top = previousBottom;
|
||||||
|
lineBounds.bottom = layout.getLineBottom(i);
|
||||||
|
previousBottom = lineBounds.bottom;
|
||||||
|
canvas.drawRoundRect(lineBounds, cornerRadius, cornerRadius, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) {
|
||||||
|
textPaint.setStrokeJoin(Join.ROUND);
|
||||||
|
textPaint.setStrokeWidth(outlineWidth);
|
||||||
|
textPaint.setColor(edgeColor);
|
||||||
|
textPaint.setStyle(Style.FILL_AND_STROKE);
|
||||||
|
layout.draw(canvas);
|
||||||
|
} else if (edgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) {
|
||||||
|
textPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, edgeColor);
|
||||||
|
} else if (edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED
|
||||||
|
|| edgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) {
|
||||||
|
boolean raised = edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED;
|
||||||
|
int colorUp = raised ? Color.WHITE : edgeColor;
|
||||||
|
int colorDown = raised ? edgeColor : Color.WHITE;
|
||||||
|
float offset = shadowRadius / 2f;
|
||||||
|
textPaint.setColor(foregroundColor);
|
||||||
|
textPaint.setStyle(Style.FILL);
|
||||||
|
textPaint.setShadowLayer(shadowRadius, -offset, -offset, colorUp);
|
||||||
|
layout.draw(canvas);
|
||||||
|
textPaint.setShadowLayer(shadowRadius, offset, offset, colorDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
textPaint.setColor(foregroundColor);
|
||||||
|
textPaint.setStyle(Style.FILL);
|
||||||
|
layout.draw(canvas);
|
||||||
|
textPaint.setShadowLayer(0, 0, 0, 0);
|
||||||
|
|
||||||
|
canvas.restoreToCount(saveCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,9 +16,9 @@
|
|||||||
package com.google.android.exoplayer.text;
|
package com.google.android.exoplayer.text;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.text.Layout.Alignment;
|
import android.graphics.Canvas;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.ViewGroup;
|
import android.view.View;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -26,28 +26,13 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* A view for rendering rich-formatted captions.
|
* A view for rendering rich-formatted captions.
|
||||||
*/
|
*/
|
||||||
public final class SubtitleLayout extends ViewGroup {
|
public final class SubtitleLayout extends View {
|
||||||
|
|
||||||
/**
|
private final List<CuePainter> painters;
|
||||||
* Use the same line height ratio as WebVtt to match the display with the preview.
|
|
||||||
* WebVtt specifies line height as 5.3% of the viewport height.
|
|
||||||
*/
|
|
||||||
private static final float LINE_HEIGHT_FRACTION = 0.0533f;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default bottom padding to apply when {@link Cue#line} is {@link Cue#UNSET_VALUE}, as a
|
|
||||||
* fraction of the viewport height.
|
|
||||||
*/
|
|
||||||
private static final float DEFAULT_BOTTOM_PADDING_FRACTION = 0.08f;
|
|
||||||
|
|
||||||
private final List<SubtitleView> subtitleViews;
|
|
||||||
|
|
||||||
private List<Cue> subtitleCues;
|
|
||||||
private int viewsInUse;
|
|
||||||
|
|
||||||
|
private List<Cue> cues;
|
||||||
private float fontScale;
|
private float fontScale;
|
||||||
private float textSize;
|
private CaptionStyleCompat style;
|
||||||
private CaptionStyleCompat captionStyle;
|
|
||||||
|
|
||||||
public SubtitleLayout(Context context) {
|
public SubtitleLayout(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
@ -55,9 +40,9 @@ public final class SubtitleLayout extends ViewGroup {
|
|||||||
|
|
||||||
public SubtitleLayout(Context context, AttributeSet attrs) {
|
public SubtitleLayout(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
subtitleViews = new ArrayList<>();
|
painters = new ArrayList<>();
|
||||||
fontScale = 1;
|
fontScale = 1;
|
||||||
captionStyle = CaptionStyleCompat.DEFAULT;
|
style = CaptionStyleCompat.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,131 +51,54 @@ public final class SubtitleLayout extends ViewGroup {
|
|||||||
* @param cues The cues to display.
|
* @param cues The cues to display.
|
||||||
*/
|
*/
|
||||||
public void setCues(List<Cue> cues) {
|
public void setCues(List<Cue> cues) {
|
||||||
subtitleCues = cues;
|
if (this.cues == cues) {
|
||||||
int size = (cues == null) ? 0 : cues.size();
|
return;
|
||||||
|
|
||||||
// create new subtitle views if necessary
|
|
||||||
if (size > subtitleViews.size()) {
|
|
||||||
for (int i = subtitleViews.size(); i < size; i++) {
|
|
||||||
SubtitleView newView = createSubtitleView();
|
|
||||||
subtitleViews.add(newView);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this.cues = cues;
|
||||||
// add the views we currently need, if necessary
|
// Ensure we have sufficient painters.
|
||||||
for (int i = viewsInUse; i < size; i++) {
|
int cueCount = (cues == null) ? 0 : cues.size();
|
||||||
addView(subtitleViews.get(i));
|
while (painters.size() < cueCount) {
|
||||||
|
painters.add(new CuePainter(getContext()));
|
||||||
}
|
}
|
||||||
|
// Invalidate to trigger drawing.
|
||||||
// remove the views we don't currently need, if necessary
|
invalidate();
|
||||||
for (int i = size; i < viewsInUse; i++) {
|
|
||||||
removeView(subtitleViews.get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
viewsInUse = size;
|
|
||||||
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
subtitleViews.get(i).setText(cues.get(i).text);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestLayout();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the scale of the font.
|
* Sets the scale of the font.
|
||||||
*
|
*
|
||||||
* @param scale The scale of the font.
|
* @param fontScale The scale of the font.
|
||||||
*/
|
*/
|
||||||
public void setFontScale(float scale) {
|
public void setFontScale(float fontScale) {
|
||||||
fontScale = scale;
|
if (this.fontScale == fontScale) {
|
||||||
updateSubtitlesTextSize(getHeight());
|
return;
|
||||||
|
|
||||||
for (SubtitleView subtitleView : subtitleViews) {
|
|
||||||
subtitleView.setTextSize(textSize);
|
|
||||||
}
|
}
|
||||||
requestLayout();
|
this.fontScale = fontScale;
|
||||||
|
// Invalidate to trigger drawing.
|
||||||
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the view according to the given style.
|
* Configures the view according to the given style.
|
||||||
*
|
*
|
||||||
* @param captionStyle A style for the view.
|
* @param style A style for the view.
|
||||||
*/
|
*/
|
||||||
public void setStyle(CaptionStyleCompat captionStyle) {
|
public void setStyle(CaptionStyleCompat style) {
|
||||||
this.captionStyle = captionStyle;
|
if (this.style == style) {
|
||||||
|
return;
|
||||||
for (SubtitleView subtitleView : subtitleViews) {
|
|
||||||
subtitleView.setStyle(captionStyle);
|
|
||||||
}
|
}
|
||||||
requestLayout();
|
this.style = style;
|
||||||
|
// Invalidate to trigger drawing.
|
||||||
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
public void dispatchDraw(Canvas canvas) {
|
||||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
int cueCount = (cues == null) ? 0 : cues.size();
|
||||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
for (int i = 0; i < cueCount; i++) {
|
||||||
setMeasuredDimension(width, height);
|
painters.get(i).draw(cues.get(i), style, fontScale, canvas, getLeft(), getTop(), getRight(),
|
||||||
|
getBottom());
|
||||||
updateSubtitlesTextSize(height);
|
|
||||||
|
|
||||||
for (int i = 0; i < viewsInUse; i++) {
|
|
||||||
subtitleViews.get(i).setTextSize(textSize);
|
|
||||||
subtitleViews.get(i).measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
|
|
||||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
|
||||||
int width = right - left;
|
|
||||||
int height = bottom - top;
|
|
||||||
|
|
||||||
for (int i = 0; i < viewsInUse; i++) {
|
|
||||||
SubtitleView subtitleView = subtitleViews.get(i);
|
|
||||||
Cue subtitleCue = subtitleCues.get(i);
|
|
||||||
|
|
||||||
int viewLeft = (width - subtitleView.getMeasuredWidth()) / 2;
|
|
||||||
int viewRight = viewLeft + subtitleView.getMeasuredWidth();
|
|
||||||
int viewTop = bottom - subtitleView.getMeasuredHeight()
|
|
||||||
- (int) (height * DEFAULT_BOTTOM_PADDING_FRACTION);
|
|
||||||
int viewBottom = bottom;
|
|
||||||
|
|
||||||
if (subtitleCue.alignment != null) {
|
|
||||||
subtitleView.setTextAlignment(subtitleCue.alignment);
|
|
||||||
} else {
|
|
||||||
subtitleView.setTextAlignment(Alignment.ALIGN_CENTER);
|
|
||||||
}
|
|
||||||
if (subtitleCue.position != Cue.UNSET_VALUE) {
|
|
||||||
if (subtitleCue.alignment == Alignment.ALIGN_OPPOSITE) {
|
|
||||||
viewRight = (int) ((width * (double) subtitleCue.position) / 100) + left;
|
|
||||||
viewLeft = Math.max(viewRight - subtitleView.getMeasuredWidth(), left);
|
|
||||||
} else {
|
|
||||||
viewLeft = (int) ((width * (double) subtitleCue.position) / 100) + left;
|
|
||||||
viewRight = Math.min(viewLeft + subtitleView.getMeasuredWidth(), right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (subtitleCue.line != Cue.UNSET_VALUE) {
|
|
||||||
viewTop = (int) (height * (double) subtitleCue.line / 100) + top;
|
|
||||||
viewBottom = viewTop + subtitleView.getMeasuredHeight();
|
|
||||||
if (viewBottom > bottom) {
|
|
||||||
viewTop = bottom - subtitleView.getMeasuredHeight();
|
|
||||||
viewBottom = bottom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subtitleView.layout(viewLeft, viewTop, viewRight, viewBottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSubtitlesTextSize(int height) {
|
|
||||||
textSize = LINE_HEIGHT_FRACTION * height * fontScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SubtitleView createSubtitleView() {
|
|
||||||
SubtitleView view = new SubtitleView(getContext());
|
|
||||||
view.setStyle(captionStyle);
|
|
||||||
view.setTextSize(textSize);
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,302 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.exoplayer.text;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Paint.Join;
|
|
||||||
import android.graphics.Paint.Style;
|
|
||||||
import android.graphics.RectF;
|
|
||||||
import android.graphics.Typeface;
|
|
||||||
import android.text.Layout.Alignment;
|
|
||||||
import android.text.StaticLayout;
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A view for rendering a single caption.
|
|
||||||
*/
|
|
||||||
public class SubtitleView extends View {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ratio of inner padding to font size.
|
|
||||||
*/
|
|
||||||
private static final float INNER_PADDING_RATIO = 0.125f;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporary rectangle used for computing line bounds.
|
|
||||||
*/
|
|
||||||
private final RectF lineBounds = new RectF();
|
|
||||||
|
|
||||||
// Styled dimensions.
|
|
||||||
private final float cornerRadius;
|
|
||||||
private final float outlineWidth;
|
|
||||||
private final float shadowRadius;
|
|
||||||
private final float shadowOffset;
|
|
||||||
|
|
||||||
private TextPaint textPaint;
|
|
||||||
private Paint paint;
|
|
||||||
|
|
||||||
private CharSequence text;
|
|
||||||
|
|
||||||
private int foregroundColor;
|
|
||||||
private int backgroundColor;
|
|
||||||
private int edgeColor;
|
|
||||||
private int edgeType;
|
|
||||||
|
|
||||||
private boolean hasMeasurements;
|
|
||||||
private int lastMeasuredWidth;
|
|
||||||
private StaticLayout layout;
|
|
||||||
|
|
||||||
private Alignment alignment;
|
|
||||||
private float spacingMult;
|
|
||||||
private float spacingAdd;
|
|
||||||
private int innerPaddingX;
|
|
||||||
|
|
||||||
public SubtitleView(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SubtitleView(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
|
|
||||||
int[] viewAttr = {android.R.attr.text, android.R.attr.textSize,
|
|
||||||
android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier};
|
|
||||||
TypedArray a = context.obtainStyledAttributes(attrs, viewAttr, defStyleAttr, 0);
|
|
||||||
CharSequence text = a.getText(0);
|
|
||||||
int textSize = a.getDimensionPixelSize(1, 15);
|
|
||||||
spacingAdd = a.getDimensionPixelSize(2, 0);
|
|
||||||
spacingMult = a.getFloat(3, 1);
|
|
||||||
a.recycle();
|
|
||||||
|
|
||||||
Resources resources = getContext().getResources();
|
|
||||||
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
|
|
||||||
int twoDpInPx = Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT);
|
|
||||||
cornerRadius = twoDpInPx;
|
|
||||||
outlineWidth = twoDpInPx;
|
|
||||||
shadowRadius = twoDpInPx;
|
|
||||||
shadowOffset = twoDpInPx;
|
|
||||||
|
|
||||||
textPaint = new TextPaint();
|
|
||||||
textPaint.setAntiAlias(true);
|
|
||||||
textPaint.setSubpixelText(true);
|
|
||||||
|
|
||||||
alignment = Alignment.ALIGN_CENTER;
|
|
||||||
|
|
||||||
paint = new Paint();
|
|
||||||
paint.setAntiAlias(true);
|
|
||||||
|
|
||||||
innerPaddingX = 0;
|
|
||||||
setText(text);
|
|
||||||
setTextSize(textSize);
|
|
||||||
setStyle(CaptionStyleCompat.DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBackgroundColor(int color) {
|
|
||||||
backgroundColor = color;
|
|
||||||
forceUpdate(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the text to be displayed by the view.
|
|
||||||
*
|
|
||||||
* @param text The text to display.
|
|
||||||
*/
|
|
||||||
public void setText(CharSequence text) {
|
|
||||||
this.text = text;
|
|
||||||
forceUpdate(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the text size in pixels.
|
|
||||||
*
|
|
||||||
* @param size The text size in pixels.
|
|
||||||
*/
|
|
||||||
public void setTextSize(float size) {
|
|
||||||
if (textPaint.getTextSize() != size) {
|
|
||||||
textPaint.setTextSize(size);
|
|
||||||
innerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
|
|
||||||
forceUpdate(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the text alignment.
|
|
||||||
*
|
|
||||||
* @param textAlignment The text alignment.
|
|
||||||
*/
|
|
||||||
public void setTextAlignment(Alignment textAlignment) {
|
|
||||||
alignment = textAlignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the view according to the given style.
|
|
||||||
*
|
|
||||||
* @param style A style for the view.
|
|
||||||
*/
|
|
||||||
public void setStyle(CaptionStyleCompat style) {
|
|
||||||
foregroundColor = style.foregroundColor;
|
|
||||||
backgroundColor = style.backgroundColor;
|
|
||||||
edgeType = style.edgeType;
|
|
||||||
edgeColor = style.edgeColor;
|
|
||||||
setTypeface(style.typeface);
|
|
||||||
super.setBackgroundColor(style.windowColor);
|
|
||||||
forceUpdate(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setTypeface(Typeface typeface) {
|
|
||||||
if (textPaint.getTypeface() != typeface) {
|
|
||||||
textPaint.setTypeface(typeface);
|
|
||||||
forceUpdate(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void forceUpdate(boolean needsLayout) {
|
|
||||||
if (needsLayout) {
|
|
||||||
hasMeasurements = false;
|
|
||||||
requestLayout();
|
|
||||||
}
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
||||||
final int widthSpec = MeasureSpec.getSize(widthMeasureSpec);
|
|
||||||
|
|
||||||
if (computeMeasurements(widthSpec)) {
|
|
||||||
final StaticLayout layout = this.layout;
|
|
||||||
final int paddingX = getPaddingLeft() + getPaddingRight() + innerPaddingX * 2;
|
|
||||||
final int height = layout.getHeight() + getPaddingTop() + getPaddingBottom();
|
|
||||||
int width = 0;
|
|
||||||
int lineCount = layout.getLineCount();
|
|
||||||
for (int i = 0; i < lineCount; i++) {
|
|
||||||
width = Math.max((int) Math.ceil(layout.getLineWidth(i)), width);
|
|
||||||
}
|
|
||||||
width += paddingX;
|
|
||||||
setMeasuredDimension(width, height);
|
|
||||||
} else if (Util.SDK_INT >= 11) {
|
|
||||||
setTooSmallMeasureDimensionV11();
|
|
||||||
} else {
|
|
||||||
setMeasuredDimension(0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(11)
|
|
||||||
private void setTooSmallMeasureDimensionV11() {
|
|
||||||
setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
||||||
final int width = r - l;
|
|
||||||
computeMeasurements(width);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean computeMeasurements(int maxWidth) {
|
|
||||||
if (hasMeasurements && maxWidth == lastMeasuredWidth) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Account for padding.
|
|
||||||
final int paddingX = getPaddingLeft() + getPaddingRight() + innerPaddingX * 2;
|
|
||||||
maxWidth -= paddingX;
|
|
||||||
if (maxWidth <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasMeasurements = true;
|
|
||||||
lastMeasuredWidth = maxWidth;
|
|
||||||
layout = new StaticLayout(text, textPaint, maxWidth, alignment, spacingMult, spacingAdd, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDraw(Canvas c) {
|
|
||||||
final StaticLayout layout = this.layout;
|
|
||||||
if (layout == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int saveCount = c.save();
|
|
||||||
final int innerPaddingX = this.innerPaddingX;
|
|
||||||
c.translate(getPaddingLeft() + innerPaddingX, getPaddingTop());
|
|
||||||
|
|
||||||
final int lineCount = layout.getLineCount();
|
|
||||||
final Paint textPaint = this.textPaint;
|
|
||||||
final Paint paint = this.paint;
|
|
||||||
final RectF bounds = lineBounds;
|
|
||||||
|
|
||||||
if (Color.alpha(backgroundColor) > 0) {
|
|
||||||
final float cornerRadius = this.cornerRadius;
|
|
||||||
float previousBottom = layout.getLineTop(0);
|
|
||||||
|
|
||||||
paint.setColor(backgroundColor);
|
|
||||||
paint.setStyle(Style.FILL);
|
|
||||||
|
|
||||||
for (int i = 0; i < lineCount; i++) {
|
|
||||||
bounds.left = layout.getLineLeft(i) - innerPaddingX;
|
|
||||||
bounds.right = layout.getLineRight(i) + innerPaddingX;
|
|
||||||
bounds.top = previousBottom;
|
|
||||||
bounds.bottom = layout.getLineBottom(i);
|
|
||||||
previousBottom = bounds.bottom;
|
|
||||||
|
|
||||||
c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (edgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) {
|
|
||||||
textPaint.setStrokeJoin(Join.ROUND);
|
|
||||||
textPaint.setStrokeWidth(outlineWidth);
|
|
||||||
textPaint.setColor(edgeColor);
|
|
||||||
textPaint.setStyle(Style.FILL_AND_STROKE);
|
|
||||||
layout.draw(c);
|
|
||||||
} else if (edgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) {
|
|
||||||
textPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, edgeColor);
|
|
||||||
} else if (edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED
|
|
||||||
|| edgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) {
|
|
||||||
boolean raised = edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED;
|
|
||||||
int colorUp = raised ? Color.WHITE : edgeColor;
|
|
||||||
int colorDown = raised ? edgeColor : Color.WHITE;
|
|
||||||
float offset = shadowRadius / 2f;
|
|
||||||
textPaint.setColor(foregroundColor);
|
|
||||||
textPaint.setStyle(Style.FILL);
|
|
||||||
textPaint.setShadowLayer(shadowRadius, -offset, -offset, colorUp);
|
|
||||||
layout.draw(c);
|
|
||||||
textPaint.setShadowLayer(shadowRadius, offset, offset, colorDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
textPaint.setColor(foregroundColor);
|
|
||||||
textPaint.setStyle(Style.FILL);
|
|
||||||
layout.draw(c);
|
|
||||||
textPaint.setShadowLayer(0, 0, 0, 0);
|
|
||||||
c.restoreToCount(saveCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user