Home | History | Annotate | Download | only in style
      1 /*
      2  * Copyright (C) 2006 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 android.text.style;
     18 
     19 import android.annotation.ColorInt;
     20 import android.annotation.IntRange;
     21 import android.annotation.NonNull;
     22 import android.annotation.Nullable;
     23 import android.annotation.Px;
     24 import android.graphics.Canvas;
     25 import android.graphics.Paint;
     26 import android.graphics.Path;
     27 import android.graphics.Path.Direction;
     28 import android.os.Parcel;
     29 import android.text.Layout;
     30 import android.text.ParcelableSpan;
     31 import android.text.Spanned;
     32 import android.text.TextUtils;
     33 
     34 /**
     35  * A span which styles paragraphs as bullet points (respecting layout direction).
     36  * <p>
     37  * BulletSpans must be attached from the first character to the last character of a single
     38  * paragraph, otherwise the bullet point will not be displayed but the first paragraph encountered
     39  * will have a leading margin.
     40  * <p>
     41  * BulletSpans allow configuring the following elements:
     42  * <ul>
     43  * <li><b>gap width</b> - the distance, in pixels, between the bullet point and the paragraph.
     44  * Default value is 2px.</li>
     45  * <li><b>color</b> - the bullet point color. By default, the bullet point color is 0 - no color,
     46  * so it uses the TextView's text color.</li>
     47  * <li><b>bullet radius</b> - the radius, in pixels, of the bullet point. Default value is
     48  * 4px.</li>
     49  * </ul>
     50  * For example, a BulletSpan using the default values can be constructed like this:
     51  * <pre>{@code
     52  *  SpannableString string = new SpannableString("Text with\nBullet point");
     53  *string.setSpan(new BulletSpan(), 10, 22, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre>
     54  * <img src="{@docRoot}reference/android/images/text/style/defaultbulletspan.png" />
     55  * <figcaption>BulletSpan constructed with default values.</figcaption>
     56  * <p>
     57  * <p>
     58  * To construct a BulletSpan with a gap width of 40px, green bullet point and bullet radius of
     59  * 20px:
     60  * <pre>{@code
     61  *  SpannableString string = new SpannableString("Text with\nBullet point");
     62  *string.setSpan(new BulletSpan(40, color, 20), 10, 22, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre>
     63  * <img src="{@docRoot}reference/android/images/text/style/custombulletspan.png" />
     64  * <figcaption>Customized BulletSpan.</figcaption>
     65  */
     66 public class BulletSpan implements LeadingMarginSpan, ParcelableSpan {
     67     // Bullet is slightly bigger to avoid aliasing artifacts on mdpi devices.
     68     private static final int STANDARD_BULLET_RADIUS = 4;
     69     public static final int STANDARD_GAP_WIDTH = 2;
     70     private static final int STANDARD_COLOR = 0;
     71 
     72     @Px
     73     private final int mGapWidth;
     74     @Px
     75     private final int mBulletRadius;
     76     private Path mBulletPath = null;
     77     @ColorInt
     78     private final int mColor;
     79     private final boolean mWantColor;
     80 
     81     /**
     82      * Creates a {@link BulletSpan} with the default values.
     83      */
     84     public BulletSpan() {
     85         this(STANDARD_GAP_WIDTH, STANDARD_COLOR, false, STANDARD_BULLET_RADIUS);
     86     }
     87 
     88     /**
     89      * Creates a {@link BulletSpan} based on a gap width
     90      *
     91      * @param gapWidth the distance, in pixels, between the bullet point and the paragraph.
     92      */
     93     public BulletSpan(int gapWidth) {
     94         this(gapWidth, STANDARD_COLOR, false, STANDARD_BULLET_RADIUS);
     95     }
     96 
     97     /**
     98      * Creates a {@link BulletSpan} based on a gap width and a color integer.
     99      *
    100      * @param gapWidth the distance, in pixels, between the bullet point and the paragraph.
    101      * @param color    the bullet point color, as a color integer
    102      * @see android.content.res.Resources#getColor(int, Resources.Theme)
    103      */
    104     public BulletSpan(int gapWidth, @ColorInt int color) {
    105         this(gapWidth, color, true, STANDARD_BULLET_RADIUS);
    106     }
    107 
    108     /**
    109      * Creates a {@link BulletSpan} based on a gap width and a color integer.
    110      *
    111      * @param gapWidth     the distance, in pixels, between the bullet point and the paragraph.
    112      * @param color        the bullet point color, as a color integer.
    113      * @param bulletRadius the radius of the bullet point, in pixels.
    114      * @see android.content.res.Resources#getColor(int, Resources.Theme)
    115      */
    116     public BulletSpan(int gapWidth, @ColorInt int color, @IntRange(from = 0) int bulletRadius) {
    117         this(gapWidth, color, true, bulletRadius);
    118     }
    119 
    120     private BulletSpan(int gapWidth, @ColorInt int color, boolean wantColor,
    121             @IntRange(from = 0) int bulletRadius) {
    122         mGapWidth = gapWidth;
    123         mBulletRadius = bulletRadius;
    124         mColor = color;
    125         mWantColor = wantColor;
    126     }
    127 
    128     /**
    129      * Creates a {@link BulletSpan} from a parcel.
    130      */
    131     public BulletSpan(@NonNull Parcel src) {
    132         mGapWidth = src.readInt();
    133         mWantColor = src.readInt() != 0;
    134         mColor = src.readInt();
    135         mBulletRadius = src.readInt();
    136     }
    137 
    138     @Override
    139     public int getSpanTypeId() {
    140         return getSpanTypeIdInternal();
    141     }
    142 
    143     /** @hide */
    144     @Override
    145     public int getSpanTypeIdInternal() {
    146         return TextUtils.BULLET_SPAN;
    147     }
    148 
    149     @Override
    150     public int describeContents() {
    151         return 0;
    152     }
    153 
    154     @Override
    155     public void writeToParcel(@NonNull Parcel dest, int flags) {
    156         writeToParcelInternal(dest, flags);
    157     }
    158 
    159     /** @hide */
    160     @Override
    161     public void writeToParcelInternal(@NonNull Parcel dest, int flags) {
    162         dest.writeInt(mGapWidth);
    163         dest.writeInt(mWantColor ? 1 : 0);
    164         dest.writeInt(mColor);
    165         dest.writeInt(mBulletRadius);
    166     }
    167 
    168     @Override
    169     public int getLeadingMargin(boolean first) {
    170         return 2 * mBulletRadius + mGapWidth;
    171     }
    172 
    173     /**
    174      * Get the distance, in pixels, between the bullet point and the paragraph.
    175      *
    176      * @return the distance, in pixels, between the bullet point and the paragraph.
    177      */
    178     public int getGapWidth() {
    179         return mGapWidth;
    180     }
    181 
    182     /**
    183      * Get the radius, in pixels, of the bullet point.
    184      *
    185      * @return the radius, in pixels, of the bullet point.
    186      */
    187     public int getBulletRadius() {
    188         return mBulletRadius;
    189     }
    190 
    191     /**
    192      * Get the bullet point color.
    193      *
    194      * @return the bullet point color
    195      */
    196     public int getColor() {
    197         return mColor;
    198     }
    199 
    200     @Override
    201     public void drawLeadingMargin(@NonNull Canvas canvas, @NonNull Paint paint, int x, int dir,
    202             int top, int baseline, int bottom,
    203             @NonNull CharSequence text, int start, int end,
    204             boolean first, @Nullable Layout layout) {
    205         if (((Spanned) text).getSpanStart(this) == start) {
    206             Paint.Style style = paint.getStyle();
    207             int oldcolor = 0;
    208 
    209             if (mWantColor) {
    210                 oldcolor = paint.getColor();
    211                 paint.setColor(mColor);
    212             }
    213 
    214             paint.setStyle(Paint.Style.FILL);
    215 
    216             if (layout != null) {
    217                 // "bottom" position might include extra space as a result of line spacing
    218                 // configuration. Subtract extra space in order to show bullet in the vertical
    219                 // center of characters.
    220                 final int line = layout.getLineForOffset(start);
    221                 bottom = bottom - layout.getLineExtra(line);
    222             }
    223 
    224             final float yPosition = (top + bottom) / 2f;
    225             final float xPosition = x + dir * mBulletRadius;
    226 
    227             if (canvas.isHardwareAccelerated()) {
    228                 if (mBulletPath == null) {
    229                     mBulletPath = new Path();
    230                     mBulletPath.addCircle(0.0f, 0.0f, mBulletRadius, Direction.CW);
    231                 }
    232 
    233                 canvas.save();
    234                 canvas.translate(xPosition, yPosition);
    235                 canvas.drawPath(mBulletPath, paint);
    236                 canvas.restore();
    237             } else {
    238                 canvas.drawCircle(xPosition, yPosition, mBulletRadius, paint);
    239             }
    240 
    241             if (mWantColor) {
    242                 paint.setColor(oldcolor);
    243             }
    244 
    245             paint.setStyle(style);
    246         }
    247     }
    248 }
    249