Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.widget;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.Context;
     21 import android.content.res.Resources;
     22 import android.content.res.TypedArray;
     23 import android.graphics.Canvas;
     24 import android.graphics.Color;
     25 import android.graphics.Paint;
     26 import android.graphics.Paint.Join;
     27 import android.graphics.Paint.Style;
     28 import android.graphics.RectF;
     29 import android.graphics.Typeface;
     30 import android.text.Layout.Alignment;
     31 import android.text.StaticLayout;
     32 import android.text.TextPaint;
     33 import android.util.AttributeSet;
     34 import android.util.DisplayMetrics;
     35 import android.view.View;
     36 import android.view.accessibility.CaptioningManager.CaptionStyle;
     37 
     38 public class SubtitleView extends View {
     39     // Ratio of inner padding to font size.
     40     private static final float INNER_PADDING_RATIO = 0.125f;
     41 
     42     /** Color used for the shadowed edge of a bevel. */
     43     private static final int COLOR_BEVEL_DARK = 0x80000000;
     44 
     45     /** Color used for the illuminated edge of a bevel. */
     46     private static final int COLOR_BEVEL_LIGHT = 0x80FFFFFF;
     47 
     48     // Styled dimensions.
     49     private final float mCornerRadius;
     50     private final float mOutlineWidth;
     51     private final float mShadowRadius;
     52     private final float mShadowOffsetX;
     53     private final float mShadowOffsetY;
     54 
     55     /** Temporary rectangle used for computing line bounds. */
     56     private final RectF mLineBounds = new RectF();
     57 
     58     /** Reusable string builder used for holding text. */
     59     private final StringBuilder mText = new StringBuilder();
     60 
     61     private Alignment mAlignment;
     62     private TextPaint mTextPaint;
     63     private Paint mPaint;
     64 
     65     private int mForegroundColor;
     66     private int mBackgroundColor;
     67     private int mEdgeColor;
     68     private int mEdgeType;
     69 
     70     private boolean mHasMeasurements;
     71     private int mLastMeasuredWidth;
     72     private StaticLayout mLayout;
     73 
     74     private float mSpacingMult = 1;
     75     private float mSpacingAdd = 0;
     76     private int mInnerPaddingX = 0;
     77 
     78     public SubtitleView(Context context) {
     79         this(context, null);
     80     }
     81 
     82     public SubtitleView(Context context, AttributeSet attrs) {
     83         this(context, attrs, 0);
     84     }
     85 
     86     public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
     87         this(context, attrs, defStyleAttr, 0);
     88     }
     89 
     90     public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
     91         super(context, attrs);
     92 
     93         final TypedArray a = context.obtainStyledAttributes(
     94                     attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes);
     95 
     96         CharSequence text = "";
     97         int textSize = 15;
     98 
     99         final int n = a.getIndexCount();
    100         for (int i = 0; i < n; i++) {
    101             int attr = a.getIndex(i);
    102 
    103             switch (attr) {
    104                 case android.R.styleable.TextView_text:
    105                     text = a.getText(attr);
    106                     break;
    107                 case android.R.styleable.TextView_lineSpacingExtra:
    108                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
    109                     break;
    110                 case android.R.styleable.TextView_lineSpacingMultiplier:
    111                     mSpacingMult = a.getFloat(attr, mSpacingMult);
    112                     break;
    113                 case android.R.styleable.TextAppearance_textSize:
    114                     textSize = a.getDimensionPixelSize(attr, textSize);
    115                     break;
    116             }
    117         }
    118 
    119         // Set up density-dependent properties.
    120         // TODO: Move these to a default style.
    121         final Resources res = getContext().getResources();
    122         mCornerRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_corner_radius);
    123         mOutlineWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_outline_width);
    124         mShadowRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_shadow_radius);
    125         mShadowOffsetX = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_shadow_offset);
    126         mShadowOffsetY = mShadowOffsetX;
    127 
    128         mTextPaint = new TextPaint();
    129         mTextPaint.setAntiAlias(true);
    130         mTextPaint.setSubpixelText(true);
    131 
    132         mPaint = new Paint();
    133         mPaint.setAntiAlias(true);
    134 
    135         setText(text);
    136         setTextSize(textSize);
    137     }
    138 
    139     public void setText(int resId) {
    140         final CharSequence text = getContext().getText(resId);
    141         setText(text);
    142     }
    143 
    144     public void setText(CharSequence text) {
    145         mText.setLength(0);
    146         mText.append(text);
    147 
    148         mHasMeasurements = false;
    149 
    150         requestLayout();
    151     }
    152 
    153     public void setForegroundColor(int color) {
    154         mForegroundColor = color;
    155 
    156         invalidate();
    157     }
    158 
    159     @Override
    160     public void setBackgroundColor(int color) {
    161         mBackgroundColor = color;
    162 
    163         invalidate();
    164     }
    165 
    166     public void setEdgeType(int edgeType) {
    167         mEdgeType = edgeType;
    168 
    169         invalidate();
    170     }
    171 
    172     public void setEdgeColor(int color) {
    173         mEdgeColor = color;
    174 
    175         invalidate();
    176     }
    177 
    178     /**
    179      * Sets the text size in pixels.
    180      *
    181      * @param size the text size in pixels
    182      */
    183     public void setTextSize(float size) {
    184         if (mTextPaint.getTextSize() != size) {
    185             mTextPaint.setTextSize(size);
    186             mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
    187 
    188             mHasMeasurements = false;
    189 
    190             requestLayout();
    191             invalidate();
    192         }
    193     }
    194 
    195     public void setTypeface(Typeface typeface) {
    196         if (mTextPaint.getTypeface() != typeface) {
    197             mTextPaint.setTypeface(typeface);
    198 
    199             mHasMeasurements = false;
    200 
    201             requestLayout();
    202             invalidate();
    203         }
    204     }
    205 
    206     public void setAlignment(Alignment textAlignment) {
    207         if (mAlignment != textAlignment) {
    208             mAlignment = textAlignment;
    209 
    210             mHasMeasurements = false;
    211 
    212             requestLayout();
    213             invalidate();
    214         }
    215     }
    216 
    217     @Override
    218     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    219         final int widthSpec = MeasureSpec.getSize(widthMeasureSpec);
    220 
    221         if (computeMeasurements(widthSpec)) {
    222             final StaticLayout layout = mLayout;
    223 
    224             // Account for padding.
    225             final int paddingX = mPaddingLeft + mPaddingRight + mInnerPaddingX * 2;
    226             final int width = layout.getWidth() + paddingX;
    227             final int height = layout.getHeight() + mPaddingTop + mPaddingBottom;
    228             setMeasuredDimension(width, height);
    229         } else {
    230             setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL);
    231         }
    232     }
    233 
    234     @Override
    235     public void onLayout(boolean changed, int l, int t, int r, int b) {
    236         final int width = r - l;
    237 
    238         computeMeasurements(width);
    239     }
    240 
    241     private boolean computeMeasurements(int maxWidth) {
    242         if (mHasMeasurements && maxWidth == mLastMeasuredWidth) {
    243             return true;
    244         }
    245 
    246         // Account for padding.
    247         final int paddingX = mPaddingLeft + mPaddingRight + mInnerPaddingX * 2;
    248         maxWidth -= paddingX;
    249         if (maxWidth <= 0) {
    250             return false;
    251         }
    252 
    253         // TODO: Implement minimum-difference line wrapping. Adding the results
    254         // of Paint.getTextWidths() seems to return different values than
    255         // StaticLayout.getWidth(), so this is non-trivial.
    256         mHasMeasurements = true;
    257         mLastMeasuredWidth = maxWidth;
    258         mLayout = new StaticLayout(
    259                 mText, mTextPaint, maxWidth, mAlignment, mSpacingMult, mSpacingAdd, true);
    260 
    261         return true;
    262     }
    263 
    264     public void setStyle(int styleId) {
    265         final Context context = mContext;
    266         final ContentResolver cr = context.getContentResolver();
    267         final CaptionStyle style;
    268         if (styleId == CaptionStyle.PRESET_CUSTOM) {
    269             style = CaptionStyle.getCustomStyle(cr);
    270         } else {
    271             style = CaptionStyle.PRESETS[styleId];
    272         }
    273 
    274         final CaptionStyle defStyle = CaptionStyle.DEFAULT;
    275         mForegroundColor = style.hasForegroundColor() ?
    276                 style.foregroundColor : defStyle.foregroundColor;
    277         mBackgroundColor = style.hasBackgroundColor() ?
    278                 style.backgroundColor : defStyle.backgroundColor;
    279         mEdgeType = style.hasEdgeType() ? style.edgeType : defStyle.edgeType;
    280         mEdgeColor = style.hasEdgeColor() ? style.edgeColor : defStyle.edgeColor;
    281         mHasMeasurements = false;
    282 
    283         final Typeface typeface = style.getTypeface();
    284         setTypeface(typeface);
    285 
    286         requestLayout();
    287     }
    288 
    289     @Override
    290     protected void onDraw(Canvas c) {
    291         final StaticLayout layout = mLayout;
    292         if (layout == null) {
    293             return;
    294         }
    295 
    296         final int saveCount = c.save();
    297         final int innerPaddingX = mInnerPaddingX;
    298         c.translate(mPaddingLeft + innerPaddingX, mPaddingTop);
    299 
    300         final int lineCount = layout.getLineCount();
    301         final Paint textPaint = mTextPaint;
    302         final Paint paint = mPaint;
    303         final RectF bounds = mLineBounds;
    304 
    305         if (Color.alpha(mBackgroundColor) > 0) {
    306             final float cornerRadius = mCornerRadius;
    307             float previousBottom = layout.getLineTop(0);
    308 
    309             paint.setColor(mBackgroundColor);
    310             paint.setStyle(Style.FILL);
    311 
    312             for (int i = 0; i < lineCount; i++) {
    313                 bounds.left = layout.getLineLeft(i) -innerPaddingX;
    314                 bounds.right = layout.getLineRight(i) + innerPaddingX;
    315                 bounds.top = previousBottom;
    316                 bounds.bottom = layout.getLineBottom(i);
    317                 previousBottom = bounds.bottom;
    318 
    319                 c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
    320             }
    321         }
    322 
    323         final int edgeType = mEdgeType;
    324         if (edgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
    325             textPaint.setStrokeJoin(Join.ROUND);
    326             textPaint.setStrokeWidth(mOutlineWidth);
    327             textPaint.setColor(mEdgeColor);
    328             textPaint.setStyle(Style.FILL_AND_STROKE);
    329 
    330             for (int i = 0; i < lineCount; i++) {
    331                 layout.drawText(c, i, i);
    332             }
    333         } else if (edgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
    334             textPaint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mEdgeColor);
    335         } else if (edgeType == CaptionStyle.EDGE_TYPE_RAISED
    336                 || edgeType == CaptionStyle.EDGE_TYPE_DEPRESSED) {
    337             final boolean raised = edgeType == CaptionStyle.EDGE_TYPE_RAISED;
    338             final int colorUp = raised ? Color.WHITE : mEdgeColor;
    339             final int colorDown = raised ? mEdgeColor : Color.WHITE;
    340             final float offset = mShadowRadius / 2f;
    341 
    342             textPaint.setColor(mForegroundColor);
    343             textPaint.setStyle(Style.FILL);
    344             textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
    345 
    346             for (int i = 0; i < lineCount; i++) {
    347                 layout.drawText(c, i, i);
    348             }
    349 
    350             textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown);
    351         }
    352 
    353         textPaint.setColor(mForegroundColor);
    354         textPaint.setStyle(Style.FILL);
    355 
    356         for (int i = 0; i < lineCount; i++) {
    357             layout.drawText(c, i, i);
    358         }
    359 
    360         textPaint.setShadowLayer(0, 0, 0, 0);
    361         c.restoreToCount(saveCount);
    362     }
    363 }
    364