| /* |
| * Copyright (C) 2006 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 android.text.style; |
| |
| import android.annotation.ColorInt; |
| import android.annotation.IntRange; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.Px; |
| import android.annotation.UnsupportedAppUsage; |
| import android.graphics.Canvas; |
| import android.graphics.Paint; |
| import android.graphics.Path; |
| import android.graphics.Path.Direction; |
| import android.os.Build; |
| import android.os.Parcel; |
| import android.text.Layout; |
| import android.text.ParcelableSpan; |
| import android.text.Spanned; |
| import android.text.TextUtils; |
| |
| /** |
| * A span which styles paragraphs as bullet points (respecting layout direction). |
| * <p> |
| * BulletSpans must be attached from the first character to the last character of a single |
| * paragraph, otherwise the bullet point will not be displayed but the first paragraph encountered |
| * will have a leading margin. |
| * <p> |
| * BulletSpans allow configuring the following elements: |
| * <ul> |
| * <li><b>gap width</b> - the distance, in pixels, between the bullet point and the paragraph. |
| * Default value is 2px.</li> |
| * <li><b>color</b> - the bullet point color. By default, the bullet point color is 0 - no color, |
| * so it uses the TextView's text color.</li> |
| * <li><b>bullet radius</b> - the radius, in pixels, of the bullet point. Default value is |
| * 4px.</li> |
| * </ul> |
| * For example, a BulletSpan using the default values can be constructed like this: |
| * <pre>{@code |
| * SpannableString string = new SpannableString("Text with\nBullet point"); |
| *string.setSpan(new BulletSpan(), 10, 22, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> |
| * <img src="{@docRoot}reference/android/images/text/style/defaultbulletspan.png" /> |
| * <figcaption>BulletSpan constructed with default values.</figcaption> |
| * <p> |
| * <p> |
| * To construct a BulletSpan with a gap width of 40px, green bullet point and bullet radius of |
| * 20px: |
| * <pre>{@code |
| * SpannableString string = new SpannableString("Text with\nBullet point"); |
| *string.setSpan(new BulletSpan(40, color, 20), 10, 22, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> |
| * <img src="{@docRoot}reference/android/images/text/style/custombulletspan.png" /> |
| * <figcaption>Customized BulletSpan.</figcaption> |
| */ |
| public class BulletSpan implements LeadingMarginSpan, ParcelableSpan { |
| // Bullet is slightly bigger to avoid aliasing artifacts on mdpi devices. |
| private static final int STANDARD_BULLET_RADIUS = 4; |
| public static final int STANDARD_GAP_WIDTH = 2; |
| private static final int STANDARD_COLOR = 0; |
| |
| @Px |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) |
| private final int mGapWidth; |
| @Px |
| private final int mBulletRadius; |
| private Path mBulletPath = null; |
| @ColorInt |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) |
| private final int mColor; |
| @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) |
| private final boolean mWantColor; |
| |
| /** |
| * Creates a {@link BulletSpan} with the default values. |
| */ |
| public BulletSpan() { |
| this(STANDARD_GAP_WIDTH, STANDARD_COLOR, false, STANDARD_BULLET_RADIUS); |
| } |
| |
| /** |
| * Creates a {@link BulletSpan} based on a gap width |
| * |
| * @param gapWidth the distance, in pixels, between the bullet point and the paragraph. |
| */ |
| public BulletSpan(int gapWidth) { |
| this(gapWidth, STANDARD_COLOR, false, STANDARD_BULLET_RADIUS); |
| } |
| |
| /** |
| * Creates a {@link BulletSpan} based on a gap width and a color integer. |
| * |
| * @param gapWidth the distance, in pixels, between the bullet point and the paragraph. |
| * @param color the bullet point color, as a color integer |
| * @see android.content.res.Resources#getColor(int, Resources.Theme) |
| */ |
| public BulletSpan(int gapWidth, @ColorInt int color) { |
| this(gapWidth, color, true, STANDARD_BULLET_RADIUS); |
| } |
| |
| /** |
| * Creates a {@link BulletSpan} based on a gap width and a color integer. |
| * |
| * @param gapWidth the distance, in pixels, between the bullet point and the paragraph. |
| * @param color the bullet point color, as a color integer. |
| * @param bulletRadius the radius of the bullet point, in pixels. |
| * @see android.content.res.Resources#getColor(int, Resources.Theme) |
| */ |
| public BulletSpan(int gapWidth, @ColorInt int color, @IntRange(from = 0) int bulletRadius) { |
| this(gapWidth, color, true, bulletRadius); |
| } |
| |
| private BulletSpan(int gapWidth, @ColorInt int color, boolean wantColor, |
| @IntRange(from = 0) int bulletRadius) { |
| mGapWidth = gapWidth; |
| mBulletRadius = bulletRadius; |
| mColor = color; |
| mWantColor = wantColor; |
| } |
| |
| /** |
| * Creates a {@link BulletSpan} from a parcel. |
| */ |
| public BulletSpan(@NonNull Parcel src) { |
| mGapWidth = src.readInt(); |
| mWantColor = src.readInt() != 0; |
| mColor = src.readInt(); |
| mBulletRadius = src.readInt(); |
| } |
| |
| @Override |
| public int getSpanTypeId() { |
| return getSpanTypeIdInternal(); |
| } |
| |
| /** @hide */ |
| @Override |
| public int getSpanTypeIdInternal() { |
| return TextUtils.BULLET_SPAN; |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(@NonNull Parcel dest, int flags) { |
| writeToParcelInternal(dest, flags); |
| } |
| |
| /** @hide */ |
| @Override |
| public void writeToParcelInternal(@NonNull Parcel dest, int flags) { |
| dest.writeInt(mGapWidth); |
| dest.writeInt(mWantColor ? 1 : 0); |
| dest.writeInt(mColor); |
| dest.writeInt(mBulletRadius); |
| } |
| |
| @Override |
| public int getLeadingMargin(boolean first) { |
| return 2 * mBulletRadius + mGapWidth; |
| } |
| |
| /** |
| * Get the distance, in pixels, between the bullet point and the paragraph. |
| * |
| * @return the distance, in pixels, between the bullet point and the paragraph. |
| */ |
| public int getGapWidth() { |
| return mGapWidth; |
| } |
| |
| /** |
| * Get the radius, in pixels, of the bullet point. |
| * |
| * @return the radius, in pixels, of the bullet point. |
| */ |
| public int getBulletRadius() { |
| return mBulletRadius; |
| } |
| |
| /** |
| * Get the bullet point color. |
| * |
| * @return the bullet point color |
| */ |
| public int getColor() { |
| return mColor; |
| } |
| |
| @Override |
| public void drawLeadingMargin(@NonNull 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) { |
| if (((Spanned) text).getSpanStart(this) == start) { |
| Paint.Style style = paint.getStyle(); |
| int oldcolor = 0; |
| |
| if (mWantColor) { |
| oldcolor = paint.getColor(); |
| paint.setColor(mColor); |
| } |
| |
| paint.setStyle(Paint.Style.FILL); |
| |
| if (layout != null) { |
| // "bottom" position might include extra space as a result of line spacing |
| // configuration. Subtract extra space in order to show bullet in the vertical |
| // center of characters. |
| final int line = layout.getLineForOffset(start); |
| bottom = bottom - layout.getLineExtra(line); |
| } |
| |
| final float yPosition = (top + bottom) / 2f; |
| final float xPosition = x + dir * mBulletRadius; |
| |
| if (canvas.isHardwareAccelerated()) { |
| if (mBulletPath == null) { |
| mBulletPath = new Path(); |
| mBulletPath.addCircle(0.0f, 0.0f, mBulletRadius, Direction.CW); |
| } |
| |
| canvas.save(); |
| canvas.translate(xPosition, yPosition); |
| canvas.drawPath(mBulletPath, paint); |
| canvas.restore(); |
| } else { |
| canvas.drawCircle(xPosition, yPosition, mBulletRadius, paint); |
| } |
| |
| if (mWantColor) { |
| paint.setColor(oldcolor); |
| } |
| |
| paint.setStyle(style); |
| } |
| } |
| } |