I have googled my question but there is not working answer provided. How do add a bulleted list to my textview.
An option I used was to set the bullet drawable using a style.
<style name="Text.Bullet">
<item name="android:background">@drawable/bullet</item>
<item name="android:paddingLeft">10dp</item>
</style>
Usage:
<TextView android:id="@+id/tx_hdr"
android:text="Item 1" style="@style/Text.Bullet" />
Bulleted list can be simply created by using the <ul>
and <li>
tags in the string resource.
DO NOT USE setText(Html.fromHtml(string)) to set the string in code! Just set the string normally in xml or by using setText(string).
E.g:
strings.xml file
<string name="str1">
<ul>
<li><i>first</i> item</li>
<li>item 2</li>
</ul>
</string>
layout.xml file
<TextView
android:text="@string/str1"
/>
It will produce the following result:
Following tags are supported like this (directly embedded in the string resource):
Here's another solution, not exactly adding a list to one textview, but I guess the goal is the same. It's using TableLayout, that needs only XML and it's really simple for small ordered or unordered lists. Below, sample code I used for this, not a line of code in Java.
Positive:
Negative:
every list item is store as a separate string resource
<TableRow
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
style="@style/helpPagePointsStyle"
android:layout_weight="0.2"
android:text="1." />
<TextView
style="@style/helpPagePointsStyle"
android:layout_weight="3"
android:text="@string/help_points1" />
</TableRow>
<TableRow
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
style="@style/helpPagePointsStyle"
android:layout_weight="0.2"
android:text="2." />
<TextView
style="@style/helpPagePointsStyle"
android:layout_weight="3"
android:text="@string/help_points2" />
</TableRow>
<TableRow
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
style="@style/helpPagePointsStyle"
android:layout_weight="0.2"
android:text="3." />
<TextView
style="@style/helpPagePointsStyle"
android:layout_weight="3"
android:text="@string/help_points3" />
</TableRow>
</TableLayout>
and the style:
<style name="helpPagePointsStyle">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:gravity">left</item>
</style>
browep explained nice the way over HTML. The provided solution with the html entity can be useful. But it includes only the bullet. If your text wraps, the indent will not be correct.
I found other solutions embedding a web view. That maybe is appropriate for some, but i think its kind of overkill... (The same with using a list view.)
I like the creative approach of Nelson :D, but it does not give you the possibility of adding an unordered list to a text view.
My example of an unordered list with bullets using BulletSpan
CharSequence t1 = getText(R.string.xxx1);
SpannableString s1 = new SpannableString(t1);
s1.setSpan(new BulletSpan(15), 0, t1.length(), 0);
CharSequence t2 = getText(R.string.xxx2);
SpannableString s2 = new SpannableString(t2);
s2.setSpan(new BulletSpan(15), 0, t2.length(), 0);
textView.setText(TextUtils.concat(s1, s2));
Positive:
Negative:
Here is a bulleted list with a header and a tab in front of each item.
public class BulletListBuilder {
private static final String SPACE = " ";
private static final String BULLET_SYMBOL = "•";
private static final String EOL = System.getProperty("line.separator");
private static final String TAB = "\t";
private BulletListBuilder() {
}
public static String getBulletList(String header, String []items) {
StringBuilder listBuilder = new StringBuilder();
if (header != null && !header.isEmpty()) {
listBuilder.append(header + EOL + EOL);
}
if (items != null && items.length != 0) {
for (String item : items) {
Spanned formattedItem = Html.fromHtml(BULLET_SYMBOL + SPACE + item);
listBuilder.append(TAB + formattedItem + EOL);
}
}
return listBuilder.toString();
}
}
Ready-to-use Kotlin extension
fun List<String>.toBulletedList(): CharSequence {
return SpannableString(this.joinToString("\n")).apply {
this@toBulletedList.foldIndexed(0) { index, acc, span ->
val end = acc + span.length + if (index != this@toBulletedList.size - 1) 1 else 0
this.setSpan(BulletSpan(16), acc, end, 0)
end
}
}
}
Usage:
val bulletedList = listOf("One", "Two", "Three").toBulletedList()
label.text = bulletedList
Colors and size:
To change bullet color or size use CustomBulletSpan instead of BulletSpan
package com.fbs.archBase.ui.spans
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.text.Layout
import android.text.Spanned
import android.text.style.LeadingMarginSpan
import androidx.annotation.ColorInt
class CustomBulletSpan(
private val bulletRadius: Int = STANDARD_BULLET_RADIUS,
private val gapWidth: Int = STANDARD_GAP_WIDTH,
@ColorInt private val circleColor: Int = STANDARD_COLOR
) : LeadingMarginSpan {
private companion object {
val STANDARD_BULLET_RADIUS = Screen.dp(2)
val STANDARD_GAP_WIDTH = Screen.dp(8)
const val STANDARD_COLOR = Color.BLACK
}
private val circlePaint = Paint().apply {
color = circleColor
style = Paint.Style.FILL
isAntiAlias = true
}
override fun getLeadingMargin(first: Boolean): Int {
return 2 * bulletRadius + gapWidth
}
override fun drawLeadingMargin(
canvas: Canvas, paint: Paint, x: Int, dir: Int,
top: Int, baseline: Int, bottom: Int,
text: CharSequence, start: Int, end: Int,
first: Boolean,
layout: Layout?
) {
if ((text as Spanned).getSpanStart(this) == start) {
val yPosition = (top + bottom) / 2f
val xPosition = (x + dir * bulletRadius).toFloat()
canvas.drawCircle(xPosition, yPosition, bulletRadius.toFloat(), circlePaint)
}
}
}