/* * 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.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Path.Direction; 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). *

* 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. *

* BulletSpans allow configuring the following elements: *

* For example, a BulletSpan using the default values can be constructed like this: *
{@code
 *  SpannableString string = new SpannableString("Text with\nBullet point");
 *string.setSpan(new BulletSpan(), 10, 22, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
* *
BulletSpan constructed with default values.
*

*

* To construct a BulletSpan with a gap width of 40px, green bullet point and bullet radius of * 20px: *

{@code
 *  SpannableString string = new SpannableString("Text with\nBullet point");
 *string.setSpan(new BulletSpan(40, color, 20), 10, 22, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
* *
Customized BulletSpan.
*/ 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 private final int mGapWidth; @Px private final int mBulletRadius; private Path mBulletPath = null; @ColorInt private final int mColor; 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); } } }