处理剧透 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);
我正在为论坛开发应用程序。
我需要像 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);