I want to display an ordered list inside a TextView, for example:
1) item 1
2) item 2
Using the following layout:
You can use this way instead:
• foo<br/>
• bar<br/>
• baz<br/>
Here is a solution I use. You can copy and paste it into an activity to see how it works, but you should change all attributes with variables for production. You can play with the padding parameters to indent it according to your needs. Instead of digits, you can use the bullet char if you want bullet list.
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/bullet1"
android:textStyle="bold"
android:layout_width="30dp"
android:gravity="right"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="1"
android:textSize="20dp" />
<TextView
android:id="@+id/bullet1Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:text="First bullet. First bullet. First bullet. First bullet. First bullet. First bullet. First bullet. First bullet. "
android:textSize="15dp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/bullet2"
android:textStyle="bold"
android:layout_width="30dp"
android:gravity="right"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:text="2"
android:textSize="20dp" />
<TextView
android:id="@+id/bullet2Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:text="Second bullet. Second bullet. Second bullet. Second bullet. Second bullet. Second bullet. Second bullet. "
android:textSize="15dp" />
</LinearLayout>
Go to res/values/strings.xml then paste below code
<string name="list">
<li>1) Item 1</li>\n
<li>2) Item 2</li>\n
<li>3) Item 3</li>\n
</string>
Then go to your layout file which contains TextView and replace with below code
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/list" />
This class handles numbering in TextView and EditText and scales the number depending on the size of the text:
import android.graphics.Canvas
import android.graphics.Paint
import android.text.Layout
import android.text.Spanned
import android.text.style.AbsoluteSizeSpan
import android.text.style.LeadingMarginSpan
/**
* Paragraph numbering.
*
*
* Android seems to add the leading margin for an empty paragraph to the previous paragraph
* (]0, 4][4, 4] --> the leading margin of the second span is added to the ]0, 4] paragraph
* regardless of the Spanned.flags) --> therefore we ignore the leading margin for the last,
* empty paragraph unless it's the only one
*/
class NumberSpan(nr: Int, gapWidth: Int, isEmpty: Boolean, isFirst: Boolean, isLast: Boolean)
: LeadingMarginSpan {
private val mNr: Int = nr
private val mGapWidth: Int = gapWidth
private val mIgnoreSpan: Boolean = isEmpty && isLast && !isFirst
private var mWidth: Float = 0.toFloat()
val value: Boolean?
get() = java.lang.Boolean.TRUE
override fun getLeadingMargin(first: Boolean): Int {
return if (mIgnoreSpan) 0 else Math.max(Math.round(mWidth + 2), mGapWidth)
}
override fun drawLeadingMargin(c: Canvas, p: Paint, x: Int, dir: Int, top: Int, baseline: Int, bottom: Int,
text: CharSequence, start: Int, end: Int, first: Boolean, l: Layout) {
val spanned = text as Spanned
if (!mIgnoreSpan && spanned.getSpanStart(this) == start) {
// set paint
val oldStyle = p.style
val oldTextSize = p.textSize
p.style = Paint.Style.FILL
val textSize = determineTextSize(spanned, start, end, oldTextSize)
p.textSize = textSize
mWidth = p.measureText(mNr.toString() + ".")
// draw the number
c.drawText(mNr.toString() + ".", x.toFloat(), baseline.toFloat(), p)
// restore paint
p.style = oldStyle
p.textSize = oldTextSize
}
}
private fun determineTextSize(spanned: Spanned, start: Int, end: Int, defaultTextSize: Float): Float {
// If the text size is different from default use that to determine the indicator size
// That is determined by finding the first visible character within the list item span
// and checking its size
val position = firstVisibleCharIndex(spanned, start, end)
if (position >= 0) {
val absoluteSizeSpans = spanned.getSpans(position, position, AbsoluteSizeSpan::class.java)
if (absoluteSizeSpans.isNotEmpty()) {
val absoluteSizeSpan = absoluteSizeSpans[absoluteSizeSpans.size - 1]
return absoluteSizeSpan.size.toFloat()
}
}
// If there are no spans or no visible characters yet use the default calculation
return defaultTextSize
}
private fun firstVisibleCharIndex(spanned: Spanned, start: Int, end: Int): Int {
var newStart = start
while (newStart < end) {
if (isVisibleChar(spanned[newStart])) {
return newStart
}
newStart++
}
return -1
}
private fun isVisibleChar(c: Char): Boolean {
return when (c) {
'\u200B', '\uFFEF' -> false
else -> true
}
}
}
The code is from this library https://github.com/1gravity/Android-RTEditor (translated from Java to Kotlin). I'm the author of that library.
We can use LeadingMarginSpan directly
for example
String[] textArray = {
"dfsdljjlfsdsdfjsdjldssdfidfsjdljasdfjfds\n",
"sdfjdfjlkfdjdfkfjiwejojodljfldsjodsjfsdjdlf\n",
"djsdfjsdffjdflljfjsadfdjfldfjl"
};
SpannableStringBuilder content = new SpannableStringBuilder();
int number = 1;
for (String t1 : textArray) {
int contentStart = content.length();
String leadingString = number + ". ";
content.append(leadingString);
content.append(t1);
int contentEnd = content.length();
content.setSpan(
new LeadingMarginSpan.Standard(0, 66),
contentStart,
contentEnd,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
);
number++;
}
I think you have to do this in code. I had to subclass LeadingMarginSpan to get this to work. Here is how I did it.
private class NumberIndentSpan implements LeadingMarginSpan {
private final int gapWidth;
private final int leadWidth;
private final int index;
public NumberIndentSpan(int leadGap, int gapWidth, int index) {
this.leadWidth = leadGap;
this.gapWidth = gapWidth;
this.index = index;
}
public int getLeadingMargin(boolean first) {
return leadWidth + gapWidth;
}
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 (first) {
Paint.Style orgStyle = p.getStyle();
p.setStyle(Paint.Style.FILL);
float width = p.measureText("4.");
c.drawText(index + ".", (leadWidth + x - width / 2) * dir, bottom - p.descent(), p);
p.setStyle(orgStyle);
}
}
}
Get hold of your view, and use it like this:
SpannableStringBuilder ssb = new SpannableStringBuilder();
for(String text : list) {
int contentStart = content.length();
content.append(text);
content.setSpan(new NumberIndentSpan(15, 15, number), contentStart, content.length(), 0);
}
TextView view = findViewById(R.id.....);
view.setText(ssb);
Hope this helps others looking for this :-)