【问题标题】:Markdown support in Android TextViewAndroid TextView 中的 Markdown 支持
【发布时间】:2020-10-14 02:48:49
【问题描述】:

有没有办法让TextView 检测降价标签并相应地呈现文本?更具体地说,我的应用程序包含一个TextView,用户可以在其中提供描述,并且他们通常会使用降价来格式化他们的描述。不幸的是,文本没有呈现,而是我们看到所有标签都写在textview 中。

【问题讨论】:

  • 能否请您添加一些代码。这将有助于我们检测问题,您更有可能得到答案
  • uncodin.github.io/bypass,虽然 gradle build 目前不支持,但恐怕,因为它是一个“apklib”。

标签: android markdown textview


【解决方案1】:

Android SDK 中没有对 Markdown 的内置支持。您必须使用像 markdown4jCommonMark 这样的库。

【讨论】:

    【解决方案2】:

    在 textview 中没有对 markdown 的继承支持,但是如果您只需要通过简单的“regexp”匹配实现简单的 markdown-lite,那么我在 https://github.com/mofosyne/instantReadmeApp 中的“从项目根文件夹加载自述文件”中的这一部分会有所帮助。

    请注意,这不会删除文本中的标记,只会对线条进行不同的样式设置。这可能是好事也可能是坏事,具体取决于您的应用程序。

    哦,还有什么好事?它在本机文本视图中设置样式,因此文本仍然可以像普通文本一样选择。

    特别是这一行:https://github.com/mofosyne/instantReadmeApp/blob/master/app/src/main/java/com/github/mofosyne/instantreadme/ReadMe.java#L137

    以下稍作修改:private void updateMainDisplay(String text)private void style_psudomarkdown_TextView(String text, TextView textview_input),因此您可以对不同的文本视图使用相同的功能

    ```

    /*
        Text Styler
        A crappy psudo markdown styler. Could do with a total revamp.
     */
    
    /*
    * Styling the textview for easier readability
    * */
    private void style_psudomarkdown_TextView(String text, TextView textview_input) {
        //TextView mTextView = (TextView) findViewById(R.id.readme_info);
        TextView mTextView = textview_input;
    
        // Let's update the main display
        // Needs to set as spannable otherwise http://stackoverflow.com/questions/16340681/fatal-exception-string-cant-be-cast-to-spannable
        mTextView.setText(text, TextView.BufferType.SPANNABLE);
        // Let's prettify it!
        changeLineinView_TITLESTYLE(mTextView, "# ", 0xfff4585d, 2f); // Primary Header
        changeLineinView(mTextView, "\n# ", 0xFFF4A158, 1.5f); // Secondary Header
        changeLineinView(mTextView, "\n## ", 0xFFF4A158, 1.2f); // Secondary Header
        changeLineinView(mTextView, "\n---", 0xFFF4A158, 1.2f); // Horizontal Rule
        changeLineinView(mTextView, "\n>",   0xFF89e24d, 0.9f); // Block Quotes
        changeLineinView(mTextView, "\n - ", 0xFFA74DE3, 1f);   // Classic Markdown List
        changeLineinView(mTextView, "\n- ", 0xFFA74DE3, 1f);   // NonStandard List
    
        //spanSetterInView(String startTarget, String endTarget, int typefaceStyle, String fontFamily,TextView tv, int colour, float size)
        // Limitation of spanSetterInView. Well its not a regular expression... so can't exactly have * list, and *bold* at the same time.
        spanSetterInView(mTextView, "\n```\n", "\n```\n",   Typeface.BOLD,        "monospace",  0xFF45c152,  0.8f, false); // fenced code Blocks ( endAtLineBreak=false since this is a multiline block operator)
        spanSetterInView(mTextView,   " **"  ,     "** ",   Typeface.BOLD,        "",  0xFF89e24d,  1f, true); // Bolding
        spanSetterInView(mTextView,    " *"  ,      "* ",   Typeface.ITALIC,      "",  0xFF4dd8e2,  1f, true); // Italic
        spanSetterInView(mTextView,  " ***"  ,    "*** ",   Typeface.BOLD_ITALIC, "",  0xFF4de25c,  1f, true); // Bold and Italic
        spanSetterInView(mTextView,    " `"  ,      "` ",   Typeface.BOLD,        "monospace",  0xFF45c152,  0.8f, true); // inline code
        spanSetterInView(mTextView, "\n    " ,      "\n",   Typeface.BOLD,        "monospace",  0xFF45c152,  0.7f, true); // classic indented code
    }
    
    private void changeLineinView(TextView tv, String target, int colour, float size) {
        String vString = (String) tv.getText().toString();
        int startSpan = 0, endSpan = 0;
        //Spannable spanRange = new SpannableString(vString);
        Spannable spanRange = (Spannable) tv.getText();
        while (true) {
            startSpan = vString.indexOf(target, endSpan-1);     // (!@#$%) I want to check a character behind in case it is a newline
            endSpan = vString.indexOf("\n", startSpan+1);       // But at the same time, I do not want to read the point found by startSpan. This is since startSpan may point to a initial newline.
            ForegroundColorSpan foreColour = new ForegroundColorSpan(colour);
            // Need a NEW span object every loop, else it just moves the span
            // Fix: -1 in startSpan or endSpan, indicates that the indexOf has already searched the entire string with not valid match (Lack of endspan check, occoured because of the inclusion of endTarget, which added extra complications)
            if ( (startSpan < 0) || ( endSpan < 0 ) ) break;// Need a NEW span object every loop, else it just moves the span
            // Need to make sure that start range is always smaller than end range. (Solved! Refer to few lines above with (!@#$%) )
            if (endSpan > startSpan) {
                //endSpan = startSpan + target.length();
                spanRange.setSpan(foreColour, startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                // Also wannna bold the span too
                spanRange.setSpan(new RelativeSizeSpan(size), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                spanRange.setSpan(new StyleSpan(Typeface.BOLD), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
        tv.setText(spanRange);
    }
    
    private void changeLineinView_TITLESTYLE(TextView tv, String target, int colour, float size) {
        String vString = (String) tv.getText().toString();
        int startSpan = 0, endSpan = 0;
        //Spannable spanRange = new SpannableString(vString);
        Spannable spanRange = (Spannable) tv.getText();
        /*
        * Had to do this, since there is something wrong with this overlapping the "##" detection routine
        * Plus you only really need one title.
         */
        //while (true) {
        startSpan = vString.substring(0,target.length()).indexOf(target, endSpan-1); //substring(target.length()) since we only want the first line
        endSpan = vString.indexOf("\n", startSpan+1);
        ForegroundColorSpan foreColour = new ForegroundColorSpan(colour);
        // Need a NEW span object every loop, else it just moves the span
            /*
            if (startSpan < 0)
                break;
                */
        if ( !(startSpan < 0) ) { // hacky I know, but its to cater to the case where there is no header text
            // Need to make sure that start range is always smaller than end range.
            if (endSpan > startSpan) {
                //endSpan = startSpan + target.length();
                spanRange.setSpan(foreColour, startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                // Also wannna bold the span too
                spanRange.setSpan(new RelativeSizeSpan(size), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                spanRange.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
        //}
        tv.setText(spanRange);
    }
    
    
    private void spanSetterInView(TextView tv, String startTarget, String endTarget, int typefaceStyle, String fontFamily, int colour, float size, boolean endAtLineBreak) {
        String vString = (String) tv.getText().toString();
        int startSpan = 0, endSpan = 0;
        //Spannable spanRange = new SpannableString(vString);
        Spannable spanRange = (Spannable) tv.getText();
        while (true) {
            startSpan = vString.indexOf(startTarget, endSpan-1);     // (!@#$%) I want to check a character behind in case it is a newline
            endSpan = vString.indexOf(endTarget, startSpan+1+startTarget.length());     // But at the same time, I do not want to read the point found by startSpan. This is since startSpan may point to a initial newline. We also need to avoid the first patten matching a token from the second pattern.
            // Since this is pretty powerful, we really want to avoid overmatching it, and limit any problems to a single line. Especially if people forget to type in the closing symbol (e.g. * in bold)
            if (endAtLineBreak){
                int endSpan_linebreak = vString.indexOf("\n", startSpan+1+startTarget.length());
                if ( endSpan_linebreak < endSpan ) { endSpan = endSpan_linebreak; }
            }
            // Fix: -1 in startSpan or endSpan, indicates that the indexOf has already searched the entire string with not valid match (Lack of endspan check, occoured because of the inclusion of endTarget, which added extra complications)
            if ( (startSpan < 0) || ( endSpan < 0 ) ) break;// Need a NEW span object every loop, else it just moves the span
            // We want to also include the end "** " characters
            endSpan += endTarget.length();
            // If all is well, we shall set the styles and etc...
            if (endSpan > startSpan) {// Need to make sure that start range is always smaller than end range. (Solved! Refer to few lines above with (!@#$%) )
                spanRange.setSpan(new ForegroundColorSpan(colour), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                spanRange.setSpan(new RelativeSizeSpan(size), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                spanRange.setSpan(new StyleSpan(typefaceStyle), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                // Default to normal font family if settings is empty
                if( !fontFamily.equals("") )  spanRange.setSpan(new TypefaceSpan(fontFamily), startSpan, endSpan, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
        tv.setText(spanRange);
    }
    

    ```

    上述实现最多只支持 2 个标头(但您可以轻松修改正则表达式以支持超过 2 个级别的标头)。

    这是一系列基于正则表达式的文本视图,由两个正则表达式函数组成,始终匹配一行changeLineinView()changeLineinView_TITLESTYLE()

    对于多行跨越spanSetterInView() 函数处理它。

    因此,只要您有一个不与任何其他语法冲突的正则表达式,就可以扩展它以适应您的目的。

    Markdownish 语法:

    这是支持的语法。不能支持完整的降价,因为这只是一个简单的 hacky 实现。但是对于易于在手机键盘上键入的简洁显示来说非常方便。

    # H1 only in first line (Due to technical hacks used)
    
    ## H2 headers as usual
    
    ## Styling
    Like: *italic* **bold** ***bold_italic***
    
    ## Classic List
     - list item 1
     - list item 2
    
    ## Nonstandard List Syntax
    - list item 1
    - list item 2
    
    ## Block Quotes
    > Quoted stuff
    
    ## codes
    here is inline `literal` codes. Must have space around it.
    
        ```
        codeblocks
        Good for ascii art
        ```
    
        Or 4 space code indent like classic markdown.
    

    【讨论】:

    • 你能添加一个*.md文件加载器吗
    【解决方案3】:

    我了解到您希望将包含 Markdown 标记的 String 转换为可在 TextView 中使用的格式化 CharSequence。我知道的两个选项是:

    我两个都用过,在我看来,第二个更好:不需要处理原生架构,APK 更小,性能相当好(在我的情况下慢了 2 倍,已经足够好) )

    更新:找到另一个选项(这是我现在正在使用的选项):

    • Markwon : 纯 java,同样使用 commonmark-java 作为解析器,可选支持图片和表格

    【讨论】:

    • 这些提供定制的人吗?如所有属性的字体颜色等?
    • Markwon 允许quite a few customization
    • 嗨@bwt,我在我的应用程序中尝试了 Markwon 库,但我被链接处理部分卡住了。我想知道如何检索链接文本以进行进一步格式化。有没有地方可以让我获得有关使用 Markwon 库的更多信息?非常感谢任何帮助。
    【解决方案4】:

    我可以推荐MarkdownView。我用它从 assets 文件夹中加载 markdown 文件。

    如果它对任何人有帮助,这是我的实现......

    在我的布局中:

    <us.feras.mdv.MarkdownView
        android:id="@+id/descriptionMarkdownView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:layout_constraintTop_toBottomOf="@id/thumbnailImageView"
        app:layout_constraintStart_toEndOf="@id/guidelineStart"
        app:layout_constraintEnd_toEndOf="@id/guidelineEnd"
        app:layout_constraintBottom_toTopOf="@id/parent"/>
    

    在我的Activity

    val cssPath = "file:///android_asset/markdown.css"
    val markdownPath = "file:///android_asset/markdown/filename.md"
    descriptionMarkdownView.loadMarkdownFile(markdownPath, cssPath)
    

    【讨论】:

    • 感谢您的评论,这个周末我与这个图书馆取得了联系,处理它很容易。就我而言,我用它来查看带有 Markdown 格式的笔记,它运行良好,足以完成这项任务。
    【解决方案5】:

    看看commonmark-java 库。 我自己没有尝试过,但我认为你可以让它在你的情况下工作

    【讨论】:

      【解决方案6】:

      我从上周五开始关注这篇文章并测试了这里建议的许多 Markdown 库 - 这个问题和这些答案基本上是我在网上可以找到的关于该主题的最佳来源。

      其中两个最引起我的注意,MarkdownViewMarkwon,但前者比后者更容易处理,所以我用它通过 Markdown 格式(这是我的主要个人目标)。

      如果你想有一个 Markdown 实时预览,你可以使用 this sample activity provided by the library itselfamong other options,如果你需要调整你自己的活动来适应它,我建议你在你的项目中添加以下代码:

      build.gradle

      implementation 'us.feras.mdv:markdownview:1.1.0'
      
      private MarkdownView markdownView;
      
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          this.markdownView = findViewById(R.id.markdownView);
          this.udateMarkdownView();
      }
      
      private void updateMarkdownView() {
          markdownView.loadMarkdown(note_content.getText().toString());
      }
      

      Here 您可以找到我在 GitHub 上提供的示例,除了库本身作为示例提供给我们的示例之外,您还可以在其中看到一个工作项目。

      【讨论】:

        【解决方案7】:

        如果你想渲染 HTML,你可以使用Html.fromHtml("your string"),有关 Android 中字符串的更多资源,请查看link

        【讨论】:

          猜你喜欢
          • 2017-11-08
          • 1970-01-01
          • 2019-09-12
          • 1970-01-01
          • 2014-11-02
          • 1970-01-01
          • 2015-03-22
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多