处理剧透 BBcode Android

Handling spoiler BBcode Android

我正在为论坛开发应用程序。

我需要像 Tapatalk 那样的剧透按钮

隐藏文本部分,仅当用户单击按钮时才显示。

我正在获取 post 的所有文本,包括剧透 BBCode。我设法使用以下正则表达式获取剧透的内容:

\[SPOILER\](.+?)\[\/SPOILER\]

我的问题是我想添加一个剧透按钮,但我所有的文本都是 HTML 样的,因为每个对象(图像、链接、格式代码等...)都翻译成 HTML,然后由 Android 使用方法 Html.fromHtml().

处理

这里是解析方法,"translates" BBCode 变成HTML:

private static String parsePostContent(String text){
    String html = text;

    Map<String,String> bbMap = new HashMap<>();

    bbMap.put("(\r\n|\r|\n|\n\r)", "<br/>");
    bbMap.put("\[b\](.+?)\[/b\]", "<strong></strong>");
    bbMap.put("\[i\](.+?)\[/i\]", "<span style='font-style:italic;'></span>");
    bbMap.put("\[u\](.+?)\[/u\]", "<span style='text-decoration:underline;'></span>");
    bbMap.put("\[h1\](.+?)\[/h1\]", "<h1></h1>");
    bbMap.put("\[h2\](.+?)\[/h2\]", "<h2></h2>");
    bbMap.put("\[h3\](.+?)\[/h3\]", "<h3></h3>");
    bbMap.put("\[h4\](.+?)\[/h4\]", "<h4></h4>");
    bbMap.put("\[h5\](.+?)\[/h5\]", "<h5></h5>");
    bbMap.put("\[h6\](.+?)\[/h6\]", "<h6></h6>");
    bbMap.put("\[quote\](.+?)\[/quote\]", "<blockquote></blockquote>");
    bbMap.put("(?s)^\[quote name=\"([^\"]+)\".*\](.+)\[\/quote\]", "<span style='font-style:italic;'>Citazione di: </span> <blockquote></blockquote>");
    bbMap.put("\[p\](.+?)\[/p\]", "<p></p>");
    bbMap.put("\[p=(.+?),(.+?)\](.+?)\[/p\]", "<p style='text-indent:px;line-height:%;'></p>");
    bbMap.put("\[center\](.+?)\[/center\]", "<div align='center'>");
    bbMap.put("\[align=(.+?)\](.+?)\[/align\]", "<div align=''>");
    bbMap.put("\[color=(.+?)\](.+?)\[/color\]", "<span style='color:;'></span>");
    bbMap.put("\[size=(.+?)\](.+?)\[/size\]", "<span style='font-size:;'></span>");
    bbMap.put("\[img\](.+?)\[/img\]", "<img src='' />");
    bbMap.put("\[img=(.+?),(.+?)\](.+?)\[/img\]", "<img width='' height='' src='' />");
    bbMap.put("\[email\](.+?)\[/email\]", "<a href='mailto:'></a>");
    bbMap.put("\[email=(.+?)\](.+?)\[/email\]", "<a href='mailto:'></a>");
    bbMap.put("\[url\](.+?)\[/url\]", "<a href=''></a>");
    bbMap.put("\[url=(.+?)\](.+?)\[/url\]", "<a href=''></a>");
    bbMap.put("\[youtube\](.+?)\[/youtube\]", "<object width='640' height='380'><param name='movie' value='http://www.youtube.com/v/'></param><embed src='http://www.youtube.com/v/' type='application/x-shockwave-flash' width='640' height='380'></embed></object>");
    bbMap.put("\[video\](.+?)\[/video\]", "<video src='' />");
    bbMap.put("\[SPOILER\](.+?)\[\/SPOILER\]", "");

    for (Map.Entry entry: bbMap.entrySet()) {
        html = html.replaceAll(entry.getKey().toString(), entry.getValue().toString());
    }

    return html;
}

请注意,剧透行仅用于测试目的。

然后在Adapter中的TextView中设置字符串class:

postText.setText(Html.fromHtml(post.getPostText()));

post 有这样的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
    android:id="@+id/postAuthor"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:layout_alignParentTop="true"
    android:layout_marginLeft="10dp"
    android:layout_marginTop="10dp"
    android:text="New Text"
    android:textStyle="bold" />

<TextView
    android:id="@+id/postDate"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignLeft="@+id/postAuthor"
    android:layout_alignStart="@+id/postAuthor"
    android:layout_below="@+id/postAuthor"
    android:layout_marginTop="5dp"
    android:text="New Text"
    android:textStyle="italic" />

<TextView
    android:id="@+id/postText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignLeft="@+id/postDate"
    android:layout_alignStart="@+id/postDate"
    android:layout_below="@+id/postDate"
    android:layout_marginBottom="15dp"
    android:layout_marginTop="20dp"
    android:autoLink="web"
    android:linksClickable="true"
    android:text="New Text" />
</RelativeLayout>

每个 post 对象都放在 ListView 中。

我的问题是:如何创建类似的布局,即使使用 HTML 或某种外部库?

谢谢。

TextView 不支持 <span><object><button> 标签。有 unofficial list 个受支持的标签。

对于 [spoiler] bbcode,您可以渲染两个 TextViews,一个有剧透,一个没有剧透。然后添加一个按钮在两者之间切换。

演示 JavaScript:

var spoilerVisible = false;
$('#toggleSpoiler').on('click', function () {
  spoilerVisible = !spoilerVisible;
  $('#textview1').toggle(!spoilerVisible);
  $('#textview2').toggle(spoilerVisible);
  $('#toggleSpoiler').text(spoilerVisible ? 'Hide spoiler' : 'Show spoiler');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="textview1">This is a test.</div>
<div id="textview2" style="display:none;">This is a test. Spoiled!</div>
<button id="toggleSpoiler">Show spoiler</button>

经过几个小时、代码版本和一些诅咒,我设法搞清楚了。这是最终代码,但我认为它仍然需要更多的修复才能完美。 (如果用户滚动到列表底部,包含 SPOILER 按钮的 post 将会消失。)

        String[] textArray = postText.split("<spoiler>");
        Spanned spoilerText;

        String preSpoilerText = "";
        String postSpoilerText = "";

        List<String> noSpoilerTextList = new ArrayList<>();
        for (int i = index - 1; i < textArray.length; i += 2) {
            if (!textArray[i].equals("</p>")) {
                String temp = textArray[i].replace("\n", "");
                temp = temp.replace("[/SPOILER]", "");
                if (!temp.equals("")) {
                    noSpoilerTextList.add(temp);
                }
            }
        }
        List<String> spoilerNameList = new ArrayList<>();
        List<String> spoilerTextList = new ArrayList<>();
        for (int i = index; i < textArray.length; i += 2) {
            if (!textArray[i].contains("</p>") || !textArray[i].equals("")) {
                String temp = textArray[i].replace("\n", "");
                temp = temp.replace("[SPOILER]", "");
                if (!temp.equals("")) {
                    if (temp.contains("<name>")) {
                        String[] spoilerTextArray = temp.split("<name>");
                        spoilerNameList.add(spoilerTextArray[1]);
                        spoilerTextList.add(spoilerTextArray[2]);
                    } else {
                        spoilerNameList.add("SPOILER");
                        spoilerTextList.add(temp);
                    }
                }
            }
        }

        if (noSpoilerTextList.size() == spoilerTextList.size()) {
            noSpoilerTextList.add(new String(""));
        }

        for (int i = 0; i < spoilerTextList.size(); i++) {
            int buttonHeight = 0;
            float metrics = context.getResources().getDisplayMetrics().density;
            if (metrics == 3.0) {
                Log.i("DYSPLAYSIZE: ", "xxhdpi");
                buttonHeight = 140;
            } else if (metrics == 2.0) {
                Log.i("DYSPLAYSIZE: ", "xhdpi");
                buttonHeight = 120;
            } else if (metrics == 1.5) {
                Log.i("DYSPLAYSIZE: ", "hdpi");
                buttonHeight = 100;
            }
            final TextView spoilerTextView = new TextView(context);
            TextView preSpoilerTextView = new TextView(context);
            TextView postSpoilerTextView = new TextView(context);
            spoilerTextView.setLinksClickable(true);
            preSpoilerTextView.setLinksClickable(true);
            postSpoilerTextView.setLinksClickable(true);
            spoilerTextView.setAutoLinkMask(Linkify.WEB_URLS);
            preSpoilerTextView.setAutoLinkMask(Linkify.WEB_URLS);
            postSpoilerTextView.setAutoLinkMask(Linkify.WEB_URLS);
            Button spoilerButton = new Button(context);
            spoilerButton.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, buttonHeight));
            spoilerText = Html.fromHtml(spoilerTextList.get(i), new Html.ImageGetter() {
                @Override
                public Drawable getDrawable(String source) {
                    LevelListDrawable d = new LevelListDrawable();
                    Drawable empty = context.getResources().getDrawable(R.drawable.abc_ab_share_pack_mtrl_alpha);
                    d.addLevel(0, 0, empty);
                    d.setBounds(0, 0, empty.getIntrinsicWidth(), empty.getIntrinsicHeight());
                    new ImageGetterAsyncTask(context, source, d).execute(spoilerTextView);
                    return d;
                }
            }, null);
            spoilerTextView.setText(spoilerText);
            spoilerTextView.setTypeface(null, Typeface.ITALIC);
            spoilerTextView.setVisibility(View.GONE);
            LinearLayout.LayoutParams spoilerMargins = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            spoilerMargins.setMargins(50, 5, 15, 15);
            final boolean[] visible = {false};
            spoilerButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!visible[0]) {
                        spoilerTextView.setVisibility(View.VISIBLE);
                        visible[0] = true;
                    } else {
                        spoilerTextView.setVisibility(View.GONE);
                        visible[0] = false;
                    }
                }
            });

            if (index == 1) {
                preSpoilerText = noSpoilerTextList.get(0);
                postSpoilerText = noSpoilerTextList.get(1);
                preSpoilerTextView.setText(Html.fromHtml(preSpoilerText));
                postSpoilerTextView.setText(Html.fromHtml(postSpoilerText));
                postContent.addView(preSpoilerTextView);
            } else {
                if (i != spoilerTextList.size() - 1) {
                    postSpoilerText = noSpoilerTextList.get(i + 1);
                    postSpoilerTextView.setText(Html.fromHtml(postSpoilerText));
                } else {
                    if (spoilerTextList.size() != noSpoilerTextList.size()) {
                        postSpoilerText = noSpoilerTextList.get(i + 1);
                    } else {
                        postSpoilerText = noSpoilerTextList.get(i);
                    }
                    postSpoilerTextView.setText(Html.fromHtml(postSpoilerText));
                }

            }
            spoilerButton.setText(spoilerNameList.get(i));
            postContent.addView(spoilerButton);
            postContent.addView(spoilerTextView, spoilerMargins);
            postContent.addView(postSpoilerTextView);