Android TextView中的Markdown支持

时间:2015-07-27 15:14:40

标签: android markdown textview

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

7 个答案:

答案 0 :(得分:5)

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

答案 1 :(得分:5)

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个以上的标题)。

这是一系列基于正则表达式的文本视图,包含两个regexp函数,它们始终匹配行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.

答案 2 :(得分:4)

我可以推荐MarkdownView。我用它来加载资产文件夹中的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)

答案 3 :(得分:3)

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

我使用了两者,在我看来,第二个更好:不需要处理原生架构,更小的APK,性能相当不错(在我的情况下比较慢2倍,而且还不够好)

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

  • Markwon:纯java,也使用commonmark-java作为解析器,可选择支持图像和表格

答案 4 :(得分:1)

查看commonmark-java库。 我自己没有尝试过,但我认为你可以在你的情况下使它成功

答案 5 :(得分:0)

自上周五以来,我一直关注着这篇文章,并测试了此处建议的许多Markdown库-这个问题和这些答案基本上是我可以在网上找到的最佳信息来源。

其中两个最吸引我的注意力,MarkdownViewMarkwon,但是前者比后者更易于处理,因此我用它通过Markdown格式化来增强a Room note taking app (这是我的主要个人目标)。

如果您想进行Markdown实时预览,可以使用this sample activity provided by the library itselfamong other options,如果您需要调整自己的活动,建议您添加以下代码到您的项目:

  • implementation 'us.feras.mdv:markdownview:1.1.0'
  • private MarkdownView markdownView;
  • markdownView = findViewById(R.id.markdownView);
  • udateMarkdownView();
  • private void updateMarkdownView() { markdownView.loadMarkdown(note_content.getText().toString()); }

Here,您可以找到我在GitHub上可用的示例,在该示例中,除了库本身提供给我们的示例之外,您还可以看到一个正在运行的项目。

答案 6 :(得分:-2)

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