Question

I have a TextView in an app, the text of which is set by a hard-coded string resource in the layout. In order to get a bulleted list in the TextView, I've used the (unofficial?) support for the <li> element. This creates properly-indented bullets, as desired, but the leftmost edge of the bullets themselves are slightly cut off, as you can see:

in this image.

I have tried adding left padding to these, but it did nothing to the clipped edge - just moved the whole thing inwards.

  1. Is there any simple solution to resolve this?
  2. Where does the resource for that bulleted list live?
Was it helpful?

Solution

Old question but for anyone else finding this late:

I've found the built in BulletSpan class has had bugs from early android versions all the way through to marshmallow:

  • Bullet radius and gap width don't scale depending on dp
  • Bullets are sometimes cut off (should be + BULLET_RADIUS, not * BULLET_RADIUS)

Warning: I've seen a few custom BulletSpan classes out there which implement ParcelableSpan like the internal class. This WILL cause crashes and is not intended to be used externally.

Here's my BulletSpanCompat:

public class BulletSpanCompat implements LeadingMarginSpan {
    private final int mGapWidth;
    private final boolean mWantColor;
    private final int mColor;

    private static final int BULLET_RADIUS = MaterialDesignUtils.dpToPx(1.5f);
    private static Path sBulletPath = null;
    public static final int STANDARD_GAP_WIDTH = MaterialDesignUtils.dpToPx(8);

    public BulletSpanCompat() {
        mGapWidth = STANDARD_GAP_WIDTH;
        mWantColor = false;
        mColor = 0;
    }

    public BulletSpanCompat(int gapWidth) {
        mGapWidth = gapWidth;
        mWantColor = false;
        mColor = 0;
    }

    public BulletSpanCompat(int gapWidth, int color) {
        mGapWidth = gapWidth;
        mWantColor = true;
        mColor = color;
    }

    public BulletSpanCompat(Parcel src) {
        mGapWidth = src.readInt();
        mWantColor = src.readInt() != 0;
        mColor = src.readInt();
    }

    public int getLeadingMargin(boolean first) {
        return 2 * BULLET_RADIUS + mGapWidth;
    }
    public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
                                  int top, int baseline, int bottom,
                                  CharSequence text, int start, int end,
                                  boolean first, Layout l) {
        if (((Spanned) text).getSpanStart(this) == start) {
            Paint.Style style = p.getStyle();
            int oldcolor = 0;

            if (mWantColor) {
                oldcolor = p.getColor();
                p.setColor(mColor);
            }

            p.setStyle(Paint.Style.FILL);

            if (c.isHardwareAccelerated()) {
                if (sBulletPath == null) {
                    sBulletPath = new Path();
                    // Bullet is slightly better to avoid aliasing artifacts on mdpi devices.
                    sBulletPath.addCircle(0.0f, 0.0f, 1.2f + BULLET_RADIUS, Path.Direction.CW);
                }

                c.save();
                c.translate(x + dir + BULLET_RADIUS, (top + bottom) / 2.0f);
                c.drawPath(sBulletPath, p);
                c.restore();
            } else {
                c.drawCircle(x + dir + BULLET_RADIUS, (top + bottom) / 2.0f, BULLET_RADIUS, p);
            }

            if (mWantColor) {
                p.setColor(oldcolor);
            }

            p.setStyle(style);
        }
    }
}

OTHER TIPS

Try using the unicode character • (Unicode 2022)? Looks like you can just paste it into the XML.

http://www.fileformat.info/info/unicode/char/2022/index.htm

               Please use below code:-

             <CustomBulletTextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Point1" />
            <CustomBulletTextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Point2" />

           /* CustomBulletTextView.java */
            public class CustomBulletTextView extends TextView {
                public CustomBulletTextView(Context context) {
                    super(context);
                    addBullet();
                }

                public CustomBulletTextView(Context context, AttributeSet attrs) {
                    super(context, attrs);
                    addBullet();
                }

                public CustomBulletTextView(Context context, AttributeSet attrs, int defStyleAttr) {
                    super(context, attrs, defStyleAttr);
                    addBullet();
                }

                public CustomBulletTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
                    super(context, attrs, defStyleAttr, defStyleRes);
                    addBullet();
                }

                private void addBullet() {
                    CharSequence text = getText();
                    if (TextUtils.isEmpty(text)) {
                        return;
                    }
                    SpannableString spannable = new SpannableString(text);
                    spannable.setSpan(new CustomBulletSpan(16), 0, text.length(), 0);
                    setText(spannable);
                }
            }

              /* CustomBulletSpan.java */
            public class CustomBulletSpan implements LeadingMarginSpan, ParcelableSpan {
            private final int mGapWidth;
            private final boolean mWantColor;
            private final int mColor;

            private static final int BULLET_RADIUS = 3;
            private static Path sBulletPath = null;
            public static final int STANDARD_GAP_WIDTH = 2;

            public CustomBulletSpan(int gapWidth) {
                mGapWidth = gapWidth;
                mWantColor = false;
                mColor = 0;
            }

            public int describeContents() {
                return 0;
            }

            public void writeToParcel(Parcel dest, int flags) {
                dest.writeInt(mGapWidth);
                dest.writeInt(mWantColor ? 1 : 0);
                dest.writeInt(mColor);
            }

            public int getLeadingMargin(boolean first) {
                return 2 * BULLET_RADIUS + mGapWidth;
            }

            public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom,
                    CharSequence text, int start, int end, boolean first, Layout l) {
                // Here I shifted the bullets to right by the given half bullet
                x += mGapWidth / 2;
                if (((Spanned) text).getSpanStart(this) == start) {
                    Paint.Style style = p.getStyle();
                    int oldcolor = 0;

                    if (mWantColor) {
                        oldcolor = p.getColor();
                        p.setColor(mColor);
                    }

                    p.setStyle(Paint.Style.FILL);

                    if (c.isHardwareAccelerated()) {
                        if (sBulletPath == null) {
                            sBulletPath = new Path();
                            // Bullet is slightly better to avoid aliasing artifacts on
                            // mdpi devices.
                            sBulletPath.addCircle(0.0f, 0.0f, 1.2f * BULLET_RADIUS, Direction.CW);
                        }

                        c.save();
                        c.translate(x + dir * BULLET_RADIUS, (top + bottom) / 2.0f);
                        c.drawPath(sBulletPath, p);
                        c.restore();
                    } else {
                        c.drawCircle(x + dir * BULLET_RADIUS, (top + bottom) / 2.0f, BULLET_RADIUS, p);
                    }

                    if (mWantColor) {
                        p.setColor(oldcolor);
                    }

                    p.setStyle(style);
                }
            }

            @Override
            public int getSpanTypeId() {
                return 0;
            }

        }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top