【问题标题】:Android: How to combine Spannable.setSpan with String.format?Android:如何将 Spannable.setSpan 与 String.format 结合起来?
【发布时间】:2011-11-04 10:19:53
【问题描述】:

我将Span 设置为部分文本。跨度本身运作良好。但是,文本是由 Resources 中的 String.format 创建的,我不知道 startend 是我要将 Span 设置为的文本中的一部分。

我尝试在strings.xml 中使用自定义HTML 标签,但getTextgetString 将它们删除。我可以使用类似getString(R.string.text, "<nb>" + arg + "</nb>"),然后使用Html.fromHtml(),因为arg 正是我想要设置跨度的位置。

我看到this approach 使用了"normal text ##span here## normal text" 格式的文本。它解析字符串删除标签并设置跨度。

我的问题是,有没有更好的方法来完成将 Span 设置为像 "something %s something" 这样的格式化字符串,或者我应该使用上述方法之一吗?

【问题讨论】:

    标签: android resources spannable


    【解决方案1】:

    getText() 将返回包含在 strings.xml 中定义的格式的 SpannedString 对象。我创建了a custom version of String.format,它将保留格式字符串中的任何跨度,即使它们包含格式说明符(SpannedString 参数中的跨度也被保留)。像这样使用它:

    Spanned toDisplay = SpanFormatter.format(getText(R.string.foo), bar, baz, quux);
    

    【讨论】:

    • George 的解决方案效果很好。与这里的其他一些解决方案不同,George's 解决了尝试使用通常的格式化程序替换字符串的问题,例如可以在 strings.xml 文件中找到的%1$s。他的 SpanFormatter 类与 String.format 一样简单,但返回 SpannedString 以供使用。
    • 奇怪的是,对于这个简单但仍然很重要的功能仍然没有官方的 android 支持。
    【解决方案2】:

    我通过引入TaggedArg 类解决了这个问题,该类的实例扩展为<tag>value</tag>。然后我创建了负责读取包含标签的文本并用跨度替换这些标签的对象。在map tag->factory中注册了不同的span。

    有一个小惊喜。如果您有 "<xx>something</xx> something" 之类的文本,Html.fromHtml 会将此文本读取为 "<xx>something something</xx>"。我不得不在整个文本周围添加标签<html> 以防止这种情况发生。

    【讨论】:

      【解决方案3】:

      我决定为 George 提供的 here 编写一个 Kotlin 版本,以防链接有一天消失:

      /*
      * Copyright © 2014 George T. Steel
      *
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
      * You may obtain a copy of the License at
      *
      * http://www.apache.org/licenses/LICENSE-2.0
      *
      * Unless required by applicable law or agreed to in writing, software
      * distributed under the License is distributed on an "AS IS" BASIS,
      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      * See the License for the specific language governing permissions and
      * limitations under the License.
      */
      //https://github.com/george-steel/android-utils/blob/master/src/org/oshkimaadziig/george/androidutils/SpanFormatter.java
      /**
       * Provides [String.format] style functions that work with [Spanned] strings and preserve formatting.
       *
       * @author George T. Steel
       */
      object SpanFormatter {
          private val FORMAT_SEQUENCE: Pattern = Pattern.compile("%([0-9]+\\$|<?)([^a-zA-z%]*)([[a-zA-Z%]&&[^tT]]|[tT][a-zA-Z])")
      
          /**
           * Version of [String.format] that works on [Spanned] strings to preserve rich text formatting.
           * Both the `format` as well as any `%s args` can be Spanned and will have their formatting preserved.
           * Due to the way [android.text.Spannable]s work, any argument's spans will can only be included **once** in the result.
           * Any duplicates will appear as text only.
           *
           * @param format the format string (see [java.util.Formatter.format])
           * @param args
           * the list of arguments passed to the formatter. If there are
           * more arguments than required by `format`,
           * additional arguments are ignored.
           * @return the formatted string (with spans).
           */
          fun format(format: CharSequence?, vararg args: Any?): SpannedString {
              return format(java.util.Locale.getDefault(), format, *args)
          }
      
          /**
           * Version of [String.format] that works on [Spanned] strings to preserve rich text formatting.
           * Both the `format` as well as any `%s args` can be Spanned and will have their formatting preserved.
           * Due to the way [android.text.Spannable]s work, any argument's spans will can only be included **once** in the result.
           * Any duplicates will appear as text only.
           *
           * @param locale
           * the locale to apply; `null` value means no localization.
           * @param format the format string (see [java.util.Formatter.format])
           * @param args
           * the list of arguments passed to the formatter.
           * @return the formatted string (with spans).
           * @see String.format
           */
          fun format(locale: java.util.Locale, format: CharSequence?, vararg args: Any?): SpannedString {
              val out = SpannableStringBuilder(format)
              var i = 0
              var argAt: Int = -1
              while (i < out.length) {
                  val m: java.util.regex.Matcher = FORMAT_SEQUENCE.matcher(out)
                  if (!m.find(i))
                      break
                  i = m.start()
                  val exprEnd: Int = m.end()
                  val argTerm: String? = m.group(1)
                  val modTerm: String? = m.group(2)
                  val typeTerm: String? = m.group(3)
                  var cookedArg: CharSequence
                  when (typeTerm) {
                      "%" -> cookedArg = "%"
                      "n" -> cookedArg = "\n"
                      else -> {
                          val argIdx: Int = when (argTerm) {
                              "" -> ++argAt
                              "<" -> argAt
                              else -> argTerm!!.substring(0, argTerm.length - 1).toInt() - 1
                          }
                          val argItem: Any? = args[argIdx]
                          cookedArg = if ((typeTerm == "s") && argItem is Spanned) {
                              argItem
                          } else {
                              String.format(locale, "%$modTerm$typeTerm", argItem)
                          }
                      }
                  }
                  out.replace(i, exprEnd, cookedArg)
                  i += cookedArg.length
              }
              return SpannedString(out)
          }
      }
      

      【讨论】:

        【解决方案4】:

        乔治认为getText() 的方式很有趣。但是没有必要编写额外的类。 getText() 返回 CharSequence。所以使用SpannableStringBuilder 将其设置为TextView:

        textViewCell.setText(new SpannableStringBuilder(getText(R.string.foo));
        

        在您的 strings.xml 中,您可以使用 html-tags 编写它(不要忘记文本前后的引号):

        <string name="foo">"CO<sub><small>2</small></sub>"</string>
        

        这种标记对我有用,要么手动将其设置为 textView(如上),要么将其分配到布局中。

        【讨论】:

        • 如果你有字符串的参数怎么办?例如,在 strings.xml 文件中的意思是“Hello %s”?
        猜你喜欢
        • 2014-01-23
        • 1970-01-01
        • 2018-12-08
        • 2012-03-17
        • 2017-12-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多