Android BBcode Spoiler Control

I am developing a forum application.

I need to get a spoiler button like Tapatalk one

enter image description here

which hides the text part, which is displayed only when the user clicks the button.

I get all the text of the message, including the BBCode spoiler. I managed to get the contents of the spoiler with the following regular expression:

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

My problem is that I want to add a spoiler button, but all my text is similar to HTML, as in each object (images, links, formatting code, etc.) it is translated into HTML and then processed by Android using the Html method .fromHtml ().

Here is a parsing method that translates BBCode to 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>$1</strong>"); bbMap.put("\\[i\\](.+?)\\[/i\\]", "<span style='font-style:italic;'>$1</span>"); bbMap.put("\\[u\\](.+?)\\[/u\\]", "<span style='text-decoration:underline;'>$1</span>"); bbMap.put("\\[h1\\](.+?)\\[/h1\\]", "<h1>$1</h1>"); bbMap.put("\\[h2\\](.+?)\\[/h2\\]", "<h2>$1</h2>"); bbMap.put("\\[h3\\](.+?)\\[/h3\\]", "<h3>$1</h3>"); bbMap.put("\\[h4\\](.+?)\\[/h4\\]", "<h4>$1</h4>"); bbMap.put("\\[h5\\](.+?)\\[/h5\\]", "<h5>$1</h5>"); bbMap.put("\\[h6\\](.+?)\\[/h6\\]", "<h6>$1</h6>"); bbMap.put("\\[quote\\](.+?)\\[/quote\\]", "<blockquote>$1</blockquote>"); bbMap.put("(?s)^\\[quote name=\"([^\"]+)\".*\\](.+)\\[\\/quote\\]", "<span style='font-style:italic;'>Citazione di: $1</span> <blockquote>$2</blockquote>"); bbMap.put("\\[p\\](.+?)\\[/p\\]", "<p>$1</p>"); bbMap.put("\\[p=(.+?),(.+?)\\](.+?)\\[/p\\]", "<p style='text-indent:$1px;line-height:$2%;'>$3</p>"); bbMap.put("\\[center\\](.+?)\\[/center\\]", "<div align='center'>$1"); bbMap.put("\\[align=(.+?)\\](.+?)\\[/align\\]", "<div align='$1'>$2"); bbMap.put("\\[color=(.+?)\\](.+?)\\[/color\\]", "<span style='color:$1;'>$2</span>"); bbMap.put("\\[size=(.+?)\\](.+?)\\[/size\\]", "<span style='font-size:$1;'>$2</span>"); bbMap.put("\\[img\\](.+?)\\[/img\\]", "<img src='$1' />"); bbMap.put("\\[img=(.+?),(.+?)\\](.+?)\\[/img\\]", "<img width='$1' height='$2' src='$3' />"); bbMap.put("\\[email\\](.+?)\\[/email\\]", "<a href='mailto:$1'>$1</a>"); bbMap.put("\\[email=(.+?)\\](.+?)\\[/email\\]", "<a href='mailto:$1'>$2</a>"); bbMap.put("\\[url\\](.+?)\\[/url\\]", "<a href='$1'>$1</a>"); bbMap.put("\\[url=(.+?)\\](.+?)\\[/url\\]", "<a href='$1'>$2</a>"); bbMap.put("\\[youtube\\](.+?)\\[/youtube\\]", "<object width='640' height='380'><param name='movie' value='http://www.youtube.com/v/$1'></param><embed src='http://www.youtube.com/v/$1' type='application/x-shockwave-flash' width='640' height='380'></embed></object>"); bbMap.put("\\[video\\](.+?)\\[/video\\]", "<video src='$1' />"); bbMap.put("\\[SPOILER\\](.+?)\\[\\/SPOILER\\]", "$1"); for (Map.Entry entry: bbMap.entrySet()) { html = html.replaceAll(entry.getKey().toString(), entry.getValue().toString()); } return html; } 

Note that the SPOILER line exists for testing purposes only.

Then the line is installed in the TextView in the Adapter class:

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

The post has this layout:

 <?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> 

Each post object is placed in a ListView.

My question is: how to create a similar layout, even with HTML or some kind of external library?

Thanks.

+5
source share
2 answers

After hours, code versions, and some curses, I managed to get it straight. Here's the last code, but I think a few more fixes are still needed to fix it. (A message containing the SPOILER button will disappear if the user moves to the bottom of the list.)

  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); 
0
source

TextView does not support the <span> , <object> and <button> tags. Supported tags have an unofficial list .

For [spoiler] bbcode, you can display two TextViews , one with and one without a spoiler. Then add a button to switch between them.

JavaScript demo:

 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> 
0
source

All Articles