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:
Oliver Woodman 2015-07-15 11:10:53 +01:00
parent 8b08efce79
commit 884e7a4170
3 changed files with 329 additions and 430 deletions

View File

@ -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);
}
}

View File

@ -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;
}
} }

View File

@ -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);
}
}