• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.compat.annotation.UnsupportedAppUsage;
25 import android.graphics.Canvas;
26 import android.graphics.Paint;
27 import android.os.Build;
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 @android.ravenwood.annotation.RavenwoodKeepWholeClass
67 public class BulletSpan implements LeadingMarginSpan, ParcelableSpan {
68     // Bullet is slightly bigger to avoid aliasing artifacts on mdpi devices.
69     private static final int STANDARD_BULLET_RADIUS = 4;
70     public static final int STANDARD_GAP_WIDTH = 2;
71     private static final int STANDARD_COLOR = 0;
72 
73     @Px
74     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
75     private final int mGapWidth;
76     @Px
77     private final int mBulletRadius;
78     @ColorInt
79     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
80     private final int mColor;
81     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
82     private final boolean mWantColor;
83 
84     /**
85      * Creates a {@link BulletSpan} with the default values.
86      */
BulletSpan()87     public BulletSpan() {
88         this(STANDARD_GAP_WIDTH, STANDARD_COLOR, false, STANDARD_BULLET_RADIUS);
89     }
90 
91     /**
92      * Creates a {@link BulletSpan} based on a gap width
93      *
94      * @param gapWidth the distance, in pixels, between the bullet point and the paragraph.
95      */
BulletSpan(int gapWidth)96     public BulletSpan(int gapWidth) {
97         this(gapWidth, STANDARD_COLOR, false, STANDARD_BULLET_RADIUS);
98     }
99 
100     /**
101      * Creates a {@link BulletSpan} based on a gap width and a color integer.
102      *
103      * @param gapWidth the distance, in pixels, between the bullet point and the paragraph.
104      * @param color    the bullet point color, as a color integer
105      * @see android.content.res.Resources#getColor(int, Resources.Theme)
106      */
BulletSpan(int gapWidth, @ColorInt int color)107     public BulletSpan(int gapWidth, @ColorInt int color) {
108         this(gapWidth, color, true, STANDARD_BULLET_RADIUS);
109     }
110 
111     /**
112      * Creates a {@link BulletSpan} based on a gap width and a color integer.
113      *
114      * @param gapWidth     the distance, in pixels, between the bullet point and the paragraph.
115      * @param color        the bullet point color, as a color integer.
116      * @param bulletRadius the radius of the bullet point, in pixels.
117      * @see android.content.res.Resources#getColor(int, Resources.Theme)
118      */
BulletSpan(int gapWidth, @ColorInt int color, @IntRange(from = 0) int bulletRadius)119     public BulletSpan(int gapWidth, @ColorInt int color, @IntRange(from = 0) int bulletRadius) {
120         this(gapWidth, color, true, bulletRadius);
121     }
122 
123     /**
124      * @hide
125      */
BulletSpan(int gapWidth, @ColorInt int color, boolean wantColor, @IntRange(from = 0) int bulletRadius)126     public BulletSpan(int gapWidth, @ColorInt int color, boolean wantColor,
127             @IntRange(from = 0) int bulletRadius) {
128         mGapWidth = gapWidth;
129         mBulletRadius = bulletRadius;
130         mColor = color;
131         mWantColor = wantColor;
132     }
133 
134     /**
135      * Creates a {@link BulletSpan} from a parcel.
136      */
BulletSpan(@onNull Parcel src)137     public BulletSpan(@NonNull Parcel src) {
138         mGapWidth = src.readInt();
139         mWantColor = src.readInt() != 0;
140         mColor = src.readInt();
141         mBulletRadius = src.readInt();
142     }
143 
144     @Override
getSpanTypeId()145     public int getSpanTypeId() {
146         return getSpanTypeIdInternal();
147     }
148 
149     /** @hide */
150     @Override
getSpanTypeIdInternal()151     public int getSpanTypeIdInternal() {
152         return TextUtils.BULLET_SPAN;
153     }
154 
155     @Override
describeContents()156     public int describeContents() {
157         return 0;
158     }
159 
160     @Override
writeToParcel(@onNull Parcel dest, int flags)161     public void writeToParcel(@NonNull Parcel dest, int flags) {
162         writeToParcelInternal(dest, flags);
163     }
164 
165     /** @hide */
166     @Override
writeToParcelInternal(@onNull Parcel dest, int flags)167     public void writeToParcelInternal(@NonNull Parcel dest, int flags) {
168         dest.writeInt(mGapWidth);
169         dest.writeInt(mWantColor ? 1 : 0);
170         dest.writeInt(mColor);
171         dest.writeInt(mBulletRadius);
172     }
173 
174     @Override
getLeadingMargin(boolean first)175     public int getLeadingMargin(boolean first) {
176         return 2 * mBulletRadius + mGapWidth;
177     }
178 
179     /**
180      * Get the distance, in pixels, between the bullet point and the paragraph.
181      *
182      * @return the distance, in pixels, between the bullet point and the paragraph.
183      */
getGapWidth()184     public int getGapWidth() {
185         return mGapWidth;
186     }
187 
188     /**
189      * Get the radius, in pixels, of the bullet point.
190      *
191      * @return the radius, in pixels, of the bullet point.
192      */
getBulletRadius()193     public int getBulletRadius() {
194         return mBulletRadius;
195     }
196 
197     /**
198      * Get the bullet point color.
199      *
200      * @return the bullet point color
201      */
getColor()202     public int getColor() {
203         return mColor;
204     }
205 
206     /**
207      * @return true if the bullet should apply the color.
208      * @hide
209      */
getWantColor()210     public boolean getWantColor() {
211         return mWantColor;
212     }
213 
214     @Override
drawLeadingMargin(@onNull Canvas canvas, @NonNull Paint paint, int x, int dir, int top, int baseline, int bottom, @NonNull CharSequence text, int start, int end, boolean first, @Nullable Layout layout)215     public void drawLeadingMargin(@NonNull Canvas canvas, @NonNull Paint paint, int x, int dir,
216             int top, int baseline, int bottom,
217             @NonNull CharSequence text, int start, int end,
218             boolean first, @Nullable Layout layout) {
219         if (((Spanned) text).getSpanStart(this) == start) {
220             Paint.Style style = paint.getStyle();
221             int oldcolor = 0;
222 
223             if (mWantColor) {
224                 oldcolor = paint.getColor();
225                 paint.setColor(mColor);
226             }
227 
228             paint.setStyle(Paint.Style.FILL);
229 
230             if (layout != null) {
231                 // "bottom" position might include extra space as a result of line spacing
232                 // configuration. Subtract extra space in order to show bullet in the vertical
233                 // center of characters.
234                 final int line = layout.getLineForOffset(start);
235                 bottom = bottom - layout.getLineExtra(line);
236             }
237 
238             final float yPosition = (top + bottom) / 2f;
239             final float xPosition = x + dir * mBulletRadius;
240 
241             canvas.drawCircle(xPosition, yPosition, mBulletRadius, paint);
242 
243             if (mWantColor) {
244                 paint.setColor(oldcolor);
245             }
246 
247             paint.setStyle(style);
248         }
249     }
250 
251     @Override
toString()252     public String toString() {
253         return "BulletSpan{"
254                 + "gapWidth=" + getGapWidth()
255                 + ", bulletRadius=" + getBulletRadius()
256                 + ", color=" + String.format("%08X", getColor())
257                 + '}';
258     }
259 }
260