From 884e7a41707348c6a5a48e4342b4986bc7b2bad5 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 15 Jul 2015 11:10:53 +0100 Subject: [PATCH] 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. --- .../android/exoplayer/text/CuePainter.java | 293 +++++++++++++++++ .../exoplayer/text/SubtitleLayout.java | 164 +++------- .../android/exoplayer/text/SubtitleView.java | 302 ------------------ 3 files changed, 329 insertions(+), 430 deletions(-) create mode 100644 library/src/main/java/com/google/android/exoplayer/text/CuePainter.java delete mode 100644 library/src/main/java/com/google/android/exoplayer/text/SubtitleView.java diff --git a/library/src/main/java/com/google/android/exoplayer/text/CuePainter.java b/library/src/main/java/com/google/android/exoplayer/text/CuePainter.java new file mode 100644 index 0000000000..e8c387fca9 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/text/CuePainter.java @@ -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. + *

+ * 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); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/text/SubtitleLayout.java b/library/src/main/java/com/google/android/exoplayer/text/SubtitleLayout.java index fc8f764ce4..690e7056e7 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/SubtitleLayout.java +++ b/library/src/main/java/com/google/android/exoplayer/text/SubtitleLayout.java @@ -16,9 +16,9 @@ package com.google.android.exoplayer.text; import android.content.Context; -import android.text.Layout.Alignment; +import android.graphics.Canvas; import android.util.AttributeSet; -import android.view.ViewGroup; +import android.view.View; import java.util.ArrayList; import java.util.List; @@ -26,28 +26,13 @@ import java.util.List; /** * A view for rendering rich-formatted captions. */ -public final class SubtitleLayout extends ViewGroup { +public final class SubtitleLayout extends View { - /** - * 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 subtitleViews; - - private List subtitleCues; - private int viewsInUse; + private final List painters; + private List cues; private float fontScale; - private float textSize; - private CaptionStyleCompat captionStyle; + private CaptionStyleCompat style; public SubtitleLayout(Context context) { this(context, null); @@ -55,9 +40,9 @@ public final class SubtitleLayout extends ViewGroup { public SubtitleLayout(Context context, AttributeSet attrs) { super(context, attrs); - subtitleViews = new ArrayList<>(); + painters = new ArrayList<>(); fontScale = 1; - captionStyle = CaptionStyleCompat.DEFAULT; + style = CaptionStyleCompat.DEFAULT; } /** @@ -66,131 +51,54 @@ public final class SubtitleLayout extends ViewGroup { * @param cues The cues to display. */ public void setCues(List cues) { - subtitleCues = cues; - int size = (cues == null) ? 0 : cues.size(); - - // create new subtitle views if necessary - if (size > subtitleViews.size()) { - for (int i = subtitleViews.size(); i < size; i++) { - SubtitleView newView = createSubtitleView(); - subtitleViews.add(newView); - } + if (this.cues == cues) { + return; } - - // add the views we currently need, if necessary - for (int i = viewsInUse; i < size; i++) { - addView(subtitleViews.get(i)); + this.cues = cues; + // Ensure we have sufficient painters. + int cueCount = (cues == null) ? 0 : cues.size(); + while (painters.size() < cueCount) { + painters.add(new CuePainter(getContext())); } - - // remove the views we don't currently need, if necessary - 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(); + // Invalidate to trigger drawing. + invalidate(); } /** * 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) { - fontScale = scale; - updateSubtitlesTextSize(getHeight()); - - for (SubtitleView subtitleView : subtitleViews) { - subtitleView.setTextSize(textSize); + public void setFontScale(float fontScale) { + if (this.fontScale == fontScale) { + return; } - requestLayout(); + this.fontScale = fontScale; + // Invalidate to trigger drawing. + invalidate(); } /** * 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) { - this.captionStyle = captionStyle; - - for (SubtitleView subtitleView : subtitleViews) { - subtitleView.setStyle(captionStyle); + public void setStyle(CaptionStyleCompat style) { + if (this.style == style) { + return; } - requestLayout(); + this.style = style; + // Invalidate to trigger drawing. + invalidate(); } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - setMeasuredDimension(width, height); - - 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)); + public void dispatchDraw(Canvas canvas) { + int cueCount = (cues == null) ? 0 : cues.size(); + for (int i = 0; i < cueCount; i++) { + painters.get(i).draw(cues.get(i), style, fontScale, canvas, getLeft(), getTop(), getRight(), + getBottom()); } } - @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; - } - } diff --git a/library/src/main/java/com/google/android/exoplayer/text/SubtitleView.java b/library/src/main/java/com/google/android/exoplayer/text/SubtitleView.java deleted file mode 100644 index c317508dd4..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/text/SubtitleView.java +++ /dev/null @@ -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); - } - -}