Android TextView 中的有序列表
Posted
技术标签:
【中文标题】Android TextView 中的有序列表【英文标题】:Ordered lists inside an Android TextView 【发布时间】:2011-01-12 21:09:13 【问题描述】:我想在 TextView 中显示一个有序列表,例如: 1) 项目 1 2)第2项
使用以下布局:
<TextView
android:text="<ol><li>item 1\n</li><li>item 2\n</li></ol>
/>
我明白了:
项目 1 项目 2如何将项目符号更改为数字?
谢谢。
【问题讨论】:
【参考方案1】:我认为您必须在代码中执行此操作。我必须继承LeadingMarginSpan 才能让它工作。这是我的做法。
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);
掌握你的观点,并像这样使用它:
SpannableStringBuilder content = 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(content);
【讨论】:
嘿,你能告诉我 "list" 和 "text" 代表什么。我的意思是你在哪里定义了这个以及它是如何工作的。如果你在这里传递整个代码,我真的很感激。 "list" 是包含要格式化的行的列表。并且 "text" 应该被称为 l (您要格式化的行) 嗨@Håvard 我试过你的代码给我带来了一些问题你能检查一下***.com/questions/67709515/…【参考方案2】:我跟着answer by @jk2K的答案 并对他的代码进行了修改。因为我需要为每个项目符号点缩进,
String[] textArray =
"dfsdljjlfsdsdfjsdjldssdfidfsjdljasdfjfds\n",
"sdfjdfjlkfdjdfkfjiwejojodljfldsjodsjfsdjdlf\n",
"djsdfjsdffjdflljfjsadfdjfldfjl"
;
SpannableStringBuilder content = new SpannableStringBuilder();
int number = 1;
for (String t1 : textArray)
int contentStart = content.length();
content.append(t1);
int contentEnd = content.length();
content.setSpan(
new BulletSpan(10),
contentStart,
contentEnd,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
);
number++;
我使用了BulletSpan class from android library 并将new LeadingMarginSpan.Standard(0, 66)
替换为new BulletSpan(10)
,它创建了一个有宽度的BulletSpan。如BulletSpan class documentation中所述
BulletSpan(int gapWidth)
Creates a BulletSpan based on a gap width
因此您不再需要附加answer by @jk2K 中提到的项目符号,
String leadingString = number + ". ";
content.append(leadingString);
【讨论】:
【参考方案3】:转到 res/values/strings.xml 然后粘贴下面的代码
<string name="list">
<li>1) Item 1</li>\n
<li>2) Item 2</li>\n
<li>3) Item 3</li>\n
</string>
然后转到包含 TextView 的布局文件并替换为以下代码
<TextView
android:layout_
android:layout_
android:text="@string/list" />
【讨论】:
你在哪里找到的?【参考方案4】:您可以改用这种方式:
• foo<br/>
• bar<br/>
• baz<br/>
【讨论】:
获取项目符号而不是数字 :(【参考方案5】:这是我使用的解决方案。您可以将其复制并粘贴到活动中以查看其工作方式,但您应该使用变量更改所有属性以进行生产。您可以根据需要使用填充参数来缩进它。如果您想要项目符号列表,您可以使用项目符号字符而不是数字。
<LinearLayout
android:layout_
android:layout_
android:orientation="horizontal" >
<TextView
android:id="@+id/bullet1"
android:textStyle="bold"
android:layout_
android:gravity="right"
android:layout_
android:paddingRight="5dp"
android:text="1"
android:textSize="20dp" />
<TextView
android:id="@+id/bullet1Text"
android:layout_
android:layout_
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_
android:layout_
android:orientation="horizontal" >
<TextView
android:id="@+id/bullet2"
android:textStyle="bold"
android:layout_
android:gravity="right"
android:layout_
android:paddingRight="5dp"
android:text="2"
android:textSize="20dp" />
<TextView
android:id="@+id/bullet2Text"
android:layout_
android:layout_
android:paddingBottom="10dp"
android:text="Second bullet. Second bullet. Second bullet. Second bullet. Second bullet. Second bullet. Second bullet. "
android:textSize="15dp" />
</LinearLayout>
【讨论】:
【参考方案6】:我们可以直接使用LeadingMarginSpan
例如
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++;
【讨论】:
【参考方案7】:该类处理 TextView 和 EditText 中的编号,并根据文本的大小缩放数字:
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
代码来自这个库https://github.com/1gravity/Android-RTEditor(从Java 翻译成Kotlin)。我是那个图书馆的作者。
【讨论】:
【参考方案8】:根据@Håvard 的回答并面临@Vivek modi 遇到的相同问题,我实施了下一个工作解决方案,该解决方案考虑了自定义字体 + 行间距附加功能,并用 Kotlin 编写:
https://gist.github.com/marcelpallares/ae6285ee0dcb3f04493991dcb18d01bd
class NumberIndentSpan(
private val number: Int,
private val leadWidth: Int = 15,
private val gapWidth: Int = 30,
) : LeadingMarginSpan
override fun getLeadingMargin(first: Boolean): Int
return leadWidth + gapWidth
override fun drawLeadingMargin(
canvas: Canvas,
paint: Paint,
x: Int,
dir: Int,
top: Int,
baseline: Int,
bottom: Int,
t: CharSequence?,
start: Int,
end: Int,
first: Boolean,
layout: Layout?
)
if (first)
val text = "$number."
val width = paint.measureText(text)
val yPosition = baseline.toFloat()
val xPosition = (leadWidth + x - width / 2) * dir
canvas.drawText(text, xPosition, yPosition, paint)
我还添加了一个扩展方法来使用它:
fun TextView.setNumberedText(list: List<String>)
val content = SpannableStringBuilder()
val gap = context.resources.getDimensionPixelSize(R.dimen.margin_16)
list.forEachIndexed index, text ->
val contentStart = content.length
content.appendLine(text)
content.setSpan(NumberIndentSpan(index + 1, gapWidth = gap), contentStart, content.length, 0)
text = content
最后一个自定义 TextView 能够从 XML 布局中使用它
class OrderedListTextView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : AppCompatTextView(context, attrs)
init
setNumberedText(text.lines())
【讨论】:
以上是关于Android TextView 中的有序列表的主要内容,如果未能解决你的问题,请参考以下文章