【问题标题】:Android Text Annotations wont find annotations in textsAndroid文本注释不会在文本中找到注释
【发布时间】:2021-07-17 15:08:59
【问题描述】:

我创建了一个自定义的 MaterialTextView 类,我想在其中通过android.text.annotations 格式化给定的文本。该类直接实现到我的 android 应用程序布局中,我通过从 firebase firestore 获取数据的 viewholder 分配文本。 在课堂上,我直接从数据库中给出了一个示例文本。 该代码不会产生任何错误,但它根本不会在给定文本中找到任何注释。我试图从各种来源实现代码,例如

https://gist.github.com/florina-muntenescu/08d751d843d55b75061039fee4e97931 https://medium.com/androiddevelopers/styling-internationalized-text-in-android-f99759fb7b8f https://developer.android.com/guide/topics/resources/string-resource#StylingWithAnnotations

但我还没有找到解决问题的方法。

我唯一改变的是转换为 SpannedString,因为它会产生 CastException,请参见我的另一个问题Android Text Annotations java.lang.ClassCastException: java.lang.String cannot be cast to android.text.SpannedString

这是我的课

import android.graphics.Color
import android.text.Annotation
import android.text.SpannableString
import android.text.Spanned
import android.text.SpannedString
import android.text.style.*
import android.util.AttributeSet
import android.util.Log
import android.view.View
import com.google.android.material.textview.MaterialTextView


class SpannedAnnotationMaterialTextView : MaterialTextView {

    private var mListener: OnItemClickListener? = null

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    override fun setText(text: CharSequence, type: BufferType) {

        super.setText(processingText(text), type)
    }

    /**
     * Beispieltext Deutsch
     * nach Anamnese, Schmerzzustand und Untersuchungsbefunden\n \n Hochrisiko-Anamnese:\n <annotation format="bulletspan">bekanntes Marfan-Syndrom oder andere Bindegewebserkrankung</annotation>\n <annotation format="bulletspan">positive Familienanamnese für Aortenerkrankungen</annotation>\n <annotation format="bulletspan">bekannte Aortenklappenerkrankung</annotation>\n <annotation format="bulletspan">bekanntes thorakales Aortenaneurysma</annotation>\n <annotation format="bulletspan">vorausgegangene(r) Manipulation Aorta <per> Herzkatheter <per> herzchirurg. Eingriff</annotation>\n \n Hochrisiko-Schmerzsymptomatik:\n <annotation format="bulletspan">Schmerzen im Brust- <per> Rücken <per> Bauchbereich (und<per>oder)</annotation>\n <annotation format="bulletspan">abrupter Beginn</annotation>\n <annotation format="bulletspan">hohe Schmerzintensität</annotation>\n <annotation format="bulletspan">reißender Schmerzcharakter</annotation>\n \n Hochrisiko-Untersuchungsbefund:\n <annotation format="bulletspan">Pulsdefizit <per> Pulsdifferenz (evtl. Blutdruckdifferenz > 20mmHg (Arme), evtl. einseitig fehlender Radialispuls oder fehlende Pulse Leiste und Beine peripher Blutdruckdifferenz (syst. Messwert, höherer Wert zählt als realer syst. Blutdruck)</annotation>\n <annotation format="bulletspan">neurologische Symptomatik in Zusammenhang mit Schmerzauftreten</annotation>\n <annotation format="bulletspan">diastolisches Geräusch bei Auskultation über Erb'schem Punkt (neu und in Zusammenhang mit dem Schmerz)</annotation>\n <annotation bulletspan>Hypotension <per> Schocksymptomatik</annotation>\n
     *
     * Exampletext English
     * according to anamnesis, pain condition and examination results \ n \ n high-risk anamnesis: \ n <annotation format = "bulletspan"> known Marfan syndrome or other connective tissue disease </annotation> \ n <annotation format = "bulletspan"> positive family history for aortic diseases < / annotation> \ n <annotation format = "bulletspan"> known aortic valve disease </annotation> \ n <annotation format = "bulletspan"> known thoracic aortic aneurysm </annotation> \ n <annotation format = "bulletspan"> previous Manipulation aorta <per> cardiac catheter <per> cardiac surgeon. Intervention </annotation> \ n \ n High-risk pain symptoms: \ n <annotation format = "bulletspan"> Pain in the chest <per> back <per> abdominal area (and <per> or) </annotation> \ n <annotation format = "bulletspan"> abrupt beginning </annotation> \ n <annotation format = "bulletspan"> high pain intensity </annotation> \ n <annotation format = "bulletspan"> tearing pain character </annotation> \ n \ n high risk Examination results: \ n <annotation format = "bulletspan"> Pulse deficit <per> Pulse difference (possibly blood pressure difference> 20mmHg (arms), possibly one-sided missing radial pulse or missing pulses Groin and legs peripheral blood pressure difference (system measured value, higher value counts as real syst. blood pressure) </annotation> \ n <annotation format = "bulletspan"> neurological symptoms in connection with the occurrence of pain </annotation> \ n <annotation format = "bulletspan"> diastolic noise during auscultation over Erb's point (new and in connection with the pain) </annotation> \ n <annotation bulletspan> Hypotensi on <per> Shock symptoms </annotation> \ n
     */

    private fun processingText(text: CharSequence): CharSequence {

        Log.e(TAG, "processing Text")

        // get the text as spannableString so we can get the spans attached to the text

        // val fullText = text as SpannedString /* not working causes exception */

        val fullText = SpannedString(text)

        val spannableString = SpannableString(fullText)

        // get all the annotation spans from the text
        // make sure you import android.text.Annotation
        val annotations = fullText.getSpans(0, fullText.length, Annotation::class.java)


        Log.e(TAG, "annotations found, size = ${annotations.size}")

        // iterate through all the annotation spans
        for (annotation in annotations) {

            // look for the span with the key font
            when (annotation.key) {
                "link" -> {
                    spannableString.apply {
                        // set the span the same indices as the annotation
                        setSpan(
                            object : ClickableSpan() {
                                override fun onClick(widget: View) {
                                    mListener!!.onSpanClick(annotation.value)
                                }
                            },
                            fullText.getSpanStart(annotation),
                            fullText.getSpanEnd(annotation),
                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                        )
                    }
                }
                "format" -> {
                    when (annotation.value) {
                        "bulletspan" -> {
                            spannableString.apply {
                                setSpan(
                                    BulletSpan(),
                                    fullText.getSpanStart(annotation),
                                    fullText.getSpanEnd(annotation),
                                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                                )
                            }
                        }
                        "bold" -> {
                            spannableString.apply {
                                setSpan(
                                    BulletSpan(12),
                                    fullText.getSpanStart(annotation),
                                    fullText.getSpanEnd(annotation),
                                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                                )
                            }
                        }
                        "underline" -> {
                            // first underline then resize with x1.15

                            spannableString.apply {
                                setSpan(
                                    UnderlineSpan(),
                                    fullText.getSpanStart(annotation),
                                    fullText.getSpanEnd(annotation),
                                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                                )
                                setSpan(
                                        RelativeSizeSpan(1.15f),
                                fullText.getSpanStart(annotation),
                                fullText.getSpanEnd(annotation),
                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                                )
                            }
                        }
                    }
                }
                "color" -> {
                    spannableString.apply {
                        setSpan(
                            ForegroundColorSpan(Color.parseColor(annotation.value)),
                            fullText.getSpanStart(annotation),
                            fullText.getSpanEnd(annotation),
                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                        )
                    }
                }
            }
        }
        return spannableString
    }

    /**
     * Method to bind the OnItemClickListener.
     *
     * @param listener see down below
     */
    fun setOnItemClickListener(listener: OnItemClickListener?) {
        mListener = listener
    }

    interface OnItemClickListener {
        fun onSpanClick(spanText: String?)
    }

    companion object {
        const val TAG = "SpannedAnnotationMaterialTextView"
    }
}

【问题讨论】:

  • 使用 &lt;annotation&gt; 这样的标签仅适用于从资源加载的字符串。也就是说,这些标签只会在从资源加载字符串时自动转换为Annotation 跨度。如果您只是设置任意外部文本,则不会像这些示例所示那样工作,因为它们专门涉及资源字符串。
  • 那么通过外部导入有什么好的替代注释吗?
  • 嗯,你已经拥有的一半——创建和设置各种*Spans——适用于任何地方;你只需要弄清楚你想如何替换Annotation 功能,它基本上只是为你解析了那些&lt;annotation&gt; 标签。不幸的是,平台执行它的机制是不可公开访问的,但您可以自己处理。或者,如果您的设置可能更简单,可能会设计一些其他方式来标记跨度类型和位置,具体取决于您首先构建标记字符串的方式。

标签: android kotlin text annotations


【解决方案1】:

感谢您之前的回答,您为我指明了正确的方向。

我制作了一个简单的正则表达式来查找任何 &lt;annotation key="value"&gt; YOUR TEXT HERE &lt;/annotation&gt; 可以将任何给定文本用作 CharSequence(到目前为止我还没有测试过字符串)。

private fun processAnnotations(fullText: CharSequence) {
    val regexAnnotation = "(?:<annotation )(?<key>:link|color|format)(?:=\\\")(?<value>[\\s|\\w!@#\$%^&*()_+=.äüöß;':,<>?-]*)(?:\\\">)[\\s|\\w!@#\$%^&*()_+=.äüöß;':,<>?-]*(?:<\\/annotation>)"

    val pattern = Pattern.compile(regexAnnotation)
    val matcher = pattern.matcher(fullText)
    var matchesTimes = 0
    while (matcher.find()) {
        matchesTimes++
        try {
            val key = matcher.group("key")
            val value = matcher.group("value")
            Log.d(TAG, "Key = $key, Value = $value")
            // your code here
            
            
            
        } catch (e: Exception) {
            Log.d(TAG, "no key value Exception = ${e.message}")
        }
    }
    Log.d(TAG, "Got $matchesTimes matches")
}

更新:18.07.2021 18:30

这是使用它们后删除注释的完整代码;)

private fun processAnnotations(fullText: CharSequence): CharSequence {
    val regexAnnotation =
        "(?<start><annotation )(?<key>link|color|format)(?:=\\\")(?<value>[\\s|\\w!@#\$%^&*()/_+=.äüöß;':,<>?-]*)(?<mid>\\\">)[\\s|\\w!@#\$%^&*()/_+=.äüöß;':,<>?-]*(?<end><\\/annotation>)"

    val frontRegexAnnotation =
        "(?<start><annotation )(?<key>link|color|format)(?:=\\\")(?<value>[\\s|\\w!@#\$%^&*()/_+=.äüöß;':,<>?-]*)(?<mid>\\\">)"
    val endRegexAnnotation = "(?<end><\\/annotation>)"


    // line break
    val brokenText = try {
        (fullText as String).replace("\\n", System.getProperty("line.separator")!!)
    } catch (e: Exception) {
        Log.e(TAG, "Line Break Exception = ${e.message}")
        fullText
    }

    val spannableString = SpannableStringBuilder(brokenText)

    val pattern = Pattern.compile(regexAnnotation)
    val matcher = pattern.matcher(spannableString)
    var matchesTimes = 0
    while (matcher.find()) {
        try {
            val key = matcher.group("key")
            val value = matcher.group("value")
            Log.d(TAG, "Key = $key, Value = $value")

            // your code here
            when (key) {
                "link" -> {
                    spannableString.apply {
                        // set the span the same indices as the annotation
                        setSpan(
                            object : ClickableSpan() {
                                override fun onClick(widget: View) {
                                    mListener!!.onSpanClick(value)
                                }
                            },
                            matcher.start(),
                            matcher.end(),
                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                        )
                    }
                }
                "format" -> {
                    when (value) {
                        "bulletspan" -> {
                            spannableString.apply {
                                setSpan(
                                    BulletSpan(),
                                    matcher.start(),
                                    matcher.end(),
                                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                                )
                            }
                        }
                        "bold" -> {
                            spannableString.apply {
                                setSpan(
                                    StyleSpan(Typeface.BOLD),
                                    matcher.start(),
                                    matcher.end(),
                                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                                )
                                setSpan(
                                    RelativeSizeSpan(1.15f),
                                    matcher.start(),
                                    matcher.end(),
                                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                                )
                            }
                        }
                        "underline" -> {
                            // first underline then resize with x1.15

                            spannableString.apply {
                                setSpan(
                                    UnderlineSpan(),
                                    matcher.start(),
                                    matcher.end(),
                                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                                )
                                setSpan(
                                    RelativeSizeSpan(1.15f),
                                    matcher.start(),
                                    matcher.end(),
                                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                                )
                            }
                        }
                    }
                }
                "color" -> {
                    spannableString.apply {
                        setSpan(
                            ForegroundColorSpan(Color.parseColor(value)),
                            matcher.start(),
                            matcher.end(),
                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                        )
                    }
                }
            }

        } catch (e: Exception) {
            Log.d(TAG, "no key value Exception = ${e.message}")
        }
        matchesTimes++
    }
    Log.d(TAG, "Got $matchesTimes matches")

    val frontStartList = ArrayList<Int>()
    val frontEndList = ArrayList<Int>()
    val endStartList = ArrayList<Int>()
    val endEndList = ArrayList<Int>()

    val endMatcher = Pattern.compile(endRegexAnnotation).matcher(spannableString)
    val frontMatcher = Pattern.compile(frontRegexAnnotation).matcher(spannableString)
    var matchesFrontTimes = 0
    while (frontMatcher.find()) {

        val frontStart = frontMatcher.start()
        val frontEnd = frontMatcher.end()

        endMatcher.find()
        val endStart = endMatcher.start()
        val endEnd = endMatcher.end()

        frontStartList.add(frontStart)
        frontEndList.add(frontEnd)
        endStartList.add(endStart)
        endEndList.add(endEnd)

        matchesFrontTimes++
    }
    Log.d(TAG, "Got $matchesFrontTimes frontMatches")

    frontStartList.sortDescending()
    frontEndList.sortDescending()
    endStartList.sortDescending()
    endEndList.sortDescending()

    for (frontStart in frontStartList) {
        val index = frontStartList.indexOf(frontStart)

        spannableString.delete(endStartList[index], endEndList[index])
        spannableString.delete(frontStartList[index], frontEndList[index])
    }


    return spannableString
}

更新2:18.07.2021 19:30

我做了一些更改,这是一种更好的方法,不删除注释,而是使它们的 AboluteSizeSpans 大小=0。所以他们在技术上仍然在字符串中,但在以后的布局中不可见。

...
                "color" -> {
                    spannableString.apply {
                        setSpan(
                            ForegroundColorSpan(Color.parseColor(value)),
                            matcher.start(),
                            matcher.end(),
                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                        )
                    }
                }
            }

            val endMatcher = Pattern.compile(endRegexAnnotation).matcher(spannableString)
            val frontMatcher = Pattern.compile(frontRegexAnnotation).matcher(spannableString)
            for (i in 0.. matchesTimes) {
                endMatcher.find()
                frontMatcher.find()
            }

            spannableString.apply {
                setSpan(AbsoluteSizeSpan(0, true), frontMatcher.start(), frontMatcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
                setSpan(AbsoluteSizeSpan(0, true), endMatcher.start(), endMatcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
            }

            matchesTimes++
        } catch (e: Exception) {
            Log.d(TAG, "no key value Exception = ${e.message}")
        }
    }
    Log.d(TAG, "Got $matchesTimes matches")

    return spannableString
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多