This question already has an answer here:
How to hightlight the typed text in searchview in android
How can we have searched characters colored when we use searchview in recyclerview?
(1 answer)
Closed 2 years ago.
How to highlight search text result in RecyclerView. I found some posts regarding Spannable TextView, but not sure where to implement in my case. Appreciate you can look and assist.
Can any one help it is not highlight the search text
public void onSearch(String query){
Log.d("Query From Activity",query);
ArrayList<TaskModel> filteredList = new ArrayList<>();
//resultView.setVisibility(View.GONE);
for (int i = 0; i < listTask.size(); i++) {
final String jTitle = listTask.get(i).getAssigned().toLowerCase();
// final String jCompany = listTask.get(i).getJob_company().toLowerCase();
// final String jLoc = mDataList.get(i).getJob_location().toLowerCase();
if (jTitle.contains(query) ) {
// make them also bold
filteredList.add(listTask.get(i));
highlightText(query,jTitle);
// resultView.setVisibility(View.GONE);
// listTask.get(i).setText(sb);
} else {
// resultView.setText("No results found for : " + query);
// resultView.setVisibility(View.VISIBLE);
}
}
final LinearLayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
recyclerViewTask.setLayoutManager(mLayoutManager);
taskAdapter = new TaskAdapter(getContext(),filteredList,this,this);
recyclerViewTask.setAdapter(taskAdapter);
taskAdapter.notifyDataSetChanged();
}
public static CharSequence highlightText(String search, String originalText) {
if (search != null && !search.equalsIgnoreCase("")) {
String normalizedText = Normalizer.normalize(originalText, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "").toLowerCase();
int start = normalizedText.indexOf(search);
if (start < 0) {
return originalText;
} else {
Spannable highlighted = new SpannableString(originalText);
while (start >= 0) {
int spanStart = Math.min(start, originalText.length());
int spanEnd = Math.min(start + search.length(), originalText.length());
highlighted.setSpan(new ForegroundColorSpan(Color.BLUE), spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
start = normalizedText.indexOf(search, spanEnd);
}
return highlighted;
}
}
return originalText;
}
working perfect but
when i am highlight text using html then some text can not be view perfect(Hindi text).
android
String str="रिश्ते भले ही कम ही बनाओ लेकिन दिल से निभाओ,\n" +
"क्योंकि आज कल इंसान अच्छाई के चक्कर में अच्छे खो देते है।";
//textview.setText(str);
textview.setText(Html.fromHtml(String.format(colorfulltext(str))), TextView.BufferType.SPANNABLE);
// highlight text
public String colorfulltext(String text) {
String[] colors = new String[]{"#fdc113", "#fdc113", "#fdc113","#fdc113", "#fdc113" ,"#fcfcfc", "#fcfcfc", "#fcfcfc", "#fcfcfc", "#fcfcfc", "#fcfcfc", "#fcfcfc", "#fcfcfc", "#fcfcfc","#fcfcfc","#fcfcfc","#fcfcfc","#fcfcfc"};
StringBuilder finals = new StringBuilder();
int size = colors.length;
int k = 0;
for (int item = 0; item < text.length(); item++) {
if (k >= size) {
k = 0;
}
finals.append("<font color='" + colors[k] + "'>" + text.charAt(item) + "</font>");
k++;
}
return finals.toString();
}
screen
Why do you convert a String to html to apply fontcolor for a static text??
You have to follow the following steps:
Create entries in Strings.xml for each text. For instance, रिश्ते
has a different color and needs to be a separate entry in
strings.xml.
add this to a Util class:
public static void addColoredPart(SpannableStringBuilder ssb,
String word, int color,String... texts) {
for(String text : texts) {
if (word != null) {
int idx1 = text.indexOf(word);
if (idx1 == -1) {
return;
}
int idx2 = idx1 + word.length();
ssb.setSpan(new ForegroundColorSpan(color), idx1, idx2,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
apply style the following way:
String string1 = context.getString(R.string.String_id1)
String string2 = context.getString(R.string.String_id2)
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
spannableStringBuilder.append(string2)
spannableStringBuilder.append(string2)
SpannableUtil.addColoredPart(
spannableStringBuilder,
spannableStringBuilder.toString(), color, string1, string2);
I want to set String with HTML tags effects. By using following method I am not able to do that. Its showing me normal text.
#SuppressWarnings("deprecation")
public static Spanned fromHtml(String html){
Spanned result;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
result = Html.fromHtml(html,Html.FROM_HTML_MODE_LEGACY);
} else {
result = Html.fromHtml(html);
}
return result;
}
I am passing following string to function;
vgdgffdgdgfdfgdfgdfgdfgdfg..........aererwerewrwerweryyiyuiuuyuuyiyiuy
hjjgjhghghgjhgjhgjgtttggtttghgggggg
But when I apply Html.fromHtml(html); its return following string
vgdgffdgdgfdfgdfgdfgdfgdfg.......... aererwerewrwerwer yyiyuiuuyuuyiyiuy hjjgjhghghgjhgjhgj gtttggtttghgggggg
I am running my app in emulator with API 23
Please provide some solution to handle HTML tags.
Android support some Html tags. you can see supported tags by android.
Supproted Html Tag by Android
Android not support ul and li tags. for that you have to handle tags like below
public class HtmlTagHandler implements Html.TagHandler {
boolean first = true;
String parent = null;
int index = 1;
private int mListItemCount = 0;
private Vector<String> mListParents = new Vector<String>();
#Override
public void handleTag(final boolean opening, final String tag, Editable output, final XMLReader xmlReader) {
if (tag.equals("ul") || tag.equals("ol") || tag.equals("dd")) {
if (opening) {
mListParents.add(tag);
} else mListParents.remove(tag);
mListItemCount = 0;
} else if (tag.equals("li") && !opening) {
handleListTag(output);
} else if (tag.equalsIgnoreCase("code")) {
if (opening) {
output.setSpan(new TypefaceSpan("monospace"), output.length(), output.length(), Spannable.SPAN_MARK_MARK);
} else {
Log.d("COde Tag", "Code tag encountered");
Object obj = getLast(output, TypefaceSpan.class);
int where = output.getSpanStart(obj);
output.setSpan(new TypefaceSpan("monospace"), where, output.length(), 0);
}
}
}
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for (int i = objs.length; i > 0; i--) {
if (text.getSpanFlags(objs[i - 1]) == Spannable.SPAN_MARK_MARK) {
return objs[i - 1];
}
}
return null;
}
}
private void handleListTag(Editable output) {
if (mListParents.lastElement().equals("ul")) {
output.append("\n");
String[] split = output.toString().split("\n");
int lastIndex = split.length - 1;
int start = output.length() - split[lastIndex].length() - 1;
output.setSpan(new BulletSpan(15 * mListParents.size()), start, output.length(), 0);
} else if (mListParents.lastElement().equals("ol")) {
mListItemCount++;
output.append("\n");
String[] split = output.toString().split("\n");
int lastIndex = split.length - 1;
int start = output.length() - split[lastIndex].length() - 1;
output.insert(start, mListItemCount + ". ");
output.setSpan(new LeadingMarginSpan.Standard(15 * mListParents.size()), start, output.length(), 0);
}
}
}
Now you just have to create list item string with ul or li tag like below. In this method you just have to pass array of strings which you want to show as html list.
public static String getHtmlText(String[] s) {
String ulStart = "<ul>";
for (int i = 0; i < s.length; i++) {
ulStart += "<li>" + s[i] + "</li>";
}
ulStart += "</ul>";
return ulStart;
}
You can use like this:
textview.setText(Html.fromHtml(htmlString, null, new HtmlTagHandler()));
i have string like these for example
309\306\308\337_338
309\306\337_338
310
311\315_316\336_337
311\315_316\336_337
311\335_336
these strings means list of page number , for example string "309\306\308\337_339" means
pages 309,306,308,337,338,339
i want to pass one of these string to function which return it as string like this
309,306,308,337,338,339
this function do that but in c# , i want to impalement in android
private static string Get_PageNumbers(string str)
{
ArrayList arrAll = new ArrayList();
MatchCollection match;
string[] excar;
string strid, firstNumber, lastlNumber;
int fn, ln;
ArrayList arrID = new ArrayList();
//***In Case The Range Number Between "_"
if (str.Contains("_"))
{
// match_reg = new Regex("(w?[\\d]+)*(_[\\d]+)");
Regex matchReg = new Regex("(w?[\\69]+_[\\d]+)*(q?[\\d]+//)*(a?[\\d]+_[\\d]+)*(y?[\\d]+)*");
match = matchReg.Matches(str);
int count = match.Count;
excar = new string[0];
for (int i = 0; i < count; i++)
{
Array.Resize(ref excar, count);
excar[i] = match[i].Groups[0].Value;
if (excar[i] != string.Empty)
arrID.Add(excar[i]);
}
//******IF Array Contains Range Of Number Like"102_110"
if (str.Contains("_"))
{
for (int i = 0; i < arrID.Count; i++)
{
strid = arrID[i].ToString();
if (arrID[i].ToString().Contains("_"))
{
int idy = strid.LastIndexOf("_");
firstNumber = strid.Substring(0, idy);
if (idy != -1)
{
lastlNumber = strid.Substring(idy + 1);
fn = int.Parse(firstNumber);
arrAll.Add(fn);
ln = int.Parse(lastlNumber);
for (int c = fn; c < ln; c++)
{
fn++;
arrAll.Add(fn);
}
}
}
else
{
arrAll.Add(arrID[i].ToString());
}
}
//******If Array Contain More Than One Number
if (arrAll.Count > 0)
{
str = "";
for (int i = 0; i < arrAll.Count; i++)
{
if (str != string.Empty)
str = str + "," + arrAll[i];
else
str = arrAll[i].ToString();
}
}
}
}
//***If string Contains between "/" only without "_"
else if (str.Contains("/") && !str.Contains("_"))
{
str = str.Replace("/", ",");
}
else if (str.Contains("\\"))
{
str = str.Replace("\\", ",");
}
return str;
}
I think this is easier to do with split function:
public static String Get_PageNumbers(String str) {// Assume str = "309\\306\\308\\337_338"
String result = "";
String[] pages = str.split("\\\\"); // now we have pages = {"309","306","308","337_338"}
for (int i = 0; i < pages.length; i++) {
String page = pages[i];
int index = page.indexOf('_');
if (index != -1) { // special case i.e. "337_338", index = 3
int start = Integer.parseInt(page.substring(0, index)); // start = 337
int end = Integer.parseInt(page.substring(index + 1)); // end = 338
for (int j = start; j <= end; j++) {
result += String.valueOf(j);
if (j != end) { // don't add ',' after last one
result += ",";
}
}
} else { // regular case i.e. "309","306","308"
result += page;
}
if (i != (pages.length-1)) { // don't add ',' after last one
result += ",";
}
}
return result; // result = "309,306,308,337,338"
}
For example this function when called as follows:
String result1 = Get_PageNumbers("309\\306\\308\\337_338");
String result2 = Get_PageNumbers("311\\315_316\\336_337");
String result3 = Get_PageNumbers("310");
Returns:
309,306,308,337,338
311,315,316,336,337
310
if i can suggest different implementation....
first, split string with "\" str.split("\\");, here you receive an array string with single number or a pattern like "num_num"
for all string founded, if string NOT contains "" char, put string in another array (othArr named), than, you split again with "" str.split("_");, now you have a 2 position array
convert that 2 strings in integer
now create a loot to min val form max val or two strings converted (and put it into othArr)
tranform othArr in a string separated with ","
I want to save a Spanned object persistently. (I'm saving the String it's based on persistently now, but it takes over 1 second to run Html.fromHtml() on it, noticeably slowing the UI.)
I see things like ParcelableSpan and SpannedString and SpannableString but I'm not sure which to use.
Right now, Html.toHtml() is your only built-in option. Parcelable is used for inter-process communication and is not designed to be durable. If toHtml() does not cover all the particular types of spans that you are using, you will have to cook up your own serialization mechanism.
Since saving the object involves disk I/O, you should be doing that in a background thread anyway, regardless of the speed of toHtml().
I had a similar problem; I used a SpannableStringBuilder to hold a string and a bunch of spans, and I wanted to be able to save and restore this object. I wrote this code to accomplish this manually using SharedPreferences:
// Save Log
SpannableStringBuilder logText = log.getText();
editor.putString(SAVE_LOG, logText.toString());
ForegroundColorSpan[] spans = logText
.getSpans(0, logText.length(), ForegroundColorSpan.class);
editor.putInt(SAVE_LOG_SPANS, spans.length);
for (int i = 0; i < spans.length; i++){
int col = spans[i].getForegroundColor();
int start = logText.getSpanStart(spans[i]);
int end = logText.getSpanEnd(spans[i]);
editor.putInt(SAVE_LOG_SPAN_COLOUR + i, col);
editor.putInt(SAVE_LOG_SPAN_START + i, start);
editor.putInt(SAVE_LOG_SPAN_END + i, end);
}
// Load Log
String logText = save.getString(SAVE_LOG, "");
log.setText(logText);
int numSpans = save.getInt(SAVE_LOG_SPANS, 0);
for (int i = 0; i < numSpans; i++){
int col = save.getInt(SAVE_LOG_SPAN_COLOUR + i, 0);
int start = save.getInt(SAVE_LOG_SPAN_START + i, 0);
int end = save.getInt(SAVE_LOG_SPAN_END + i, 0);
log.getText().setSpan(new ForegroundColorSpan(col), start, end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
I my case I knew that all the spans were of type ForegroundColorSpan and with flags SPAN_EXCLUSIVE_EXCLUSIVE, but this code can be easily adapted to accomodate other types.
My use case was about putting a Spanned into a Bundle, and Google brought me here. #CommonsWare is right that Parcelable is no good for persistent storage, but it's fine for storing into a Bundle. Most spans seems to extend ParcelableSpan, and so this worked for me in onSaveInstanceState:
ParcelableSpan spanObjects[] = mStringBuilder.getSpans(0, mStringBuilder.length(), ParcelableSpan.class);
int spanStart[] = new int[spanObjects.length];
int spanEnd[] = new int[spanObjects.length];
int spanFlags[] = new int[spanObjects.length];
for(int i = 0; i < spanObjects.length; ++i)
{
spanStart[i] = mStringBuilder.getSpanStart(spanObjects[i]);
spanEnd[i] = mStringBuilder.getSpanEnd(spanObjects[i]);
spanFlags[i] = mStringBuilder.getSpanFlags(spanObjects[i]);
}
outState.putString("mStringBuilder:string", mStringBuilder.toString());
outState.putParcelableArray("mStringBuilder:spanObjects", spanObjects);
outState.putIntArray("mStringBuilder:spanStart", spanStart);
outState.putIntArray("mStringBuilder:spanEnd", spanEnd);
outState.putIntArray("mStringBuilder:spanFlags", spanFlags);
Then the state can be restored with something like this:
mStringBuilder = new SpannableStringBuilder(savedInstanceState.getString("mStringBuilder:string"));
ParcelableSpan spanObjects[] = (ParcelableSpan[])savedInstanceState.getParcelableArray("mStringBuilder:spanObjects");
int spanStart[] = savedInstanceState.getIntArray("mStringBuilder:spanStart");
int spanEnd[] = savedInstanceState.getIntArray("mStringBuilder:spanEnd");
int spanFlags[] = savedInstanceState.getIntArray("mStringBuilder:spanFlags");
for(int i = 0; i < spanObjects.length; ++i)
mStringBuilder.setSpan(spanObjects[i], spanStart[i], spanEnd[i], spanFlags[i]);
I've used a SpannableStringBuilder here but it should work with any class implementing Spanned as far as I can tell. It's probably possible to wrap this code into a ParcelableSpanned, but this version seems fine for now.
From Dan's idea:
public static String spannableString2JsonString(SpannableString ss) throws JSONException {
JSONObject json = new JSONObject();
json.put("text",ss.toString());
JSONArray ja = new JSONArray();
ForegroundColorSpan[] spans = ss.getSpans(0, ss.length(), ForegroundColorSpan.class);
for (int i = 0; i < spans.length; i++){
int col = spans[i].getForegroundColor();
int start = ss.getSpanStart(spans[i]);
int end = ss.getSpanEnd(spans[i]);
JSONObject ij = new JSONObject();
ij.put("color",col);
ij.put("start",start);
ij.put("end",end);
ja.put(ij);
}
json.put("spans",ja);
return json.toString();
}
public static SpannableString jsonString2SpannableString(String strjson) throws JSONException{
JSONObject json = new JSONObject(strjson);
SpannableString ss = new SpannableString(json.getString("text"));
JSONArray ja = json.getJSONArray("spans");
for (int i=0;i<ja.length();i++){
JSONObject jo = ja.getJSONObject(i);
int col = jo.getInt("color");
int start = jo.getInt("start");
int end = jo.getInt("end");
ss.setSpan(new ForegroundColorSpan(col),start,end,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return ss;
}
A solution I came up with is using GSON with a custom serializer/deserializer. The solution combines some of the ideas mentioned in other answers.
Define some JSON Keys
/* JSON Property Keys */
private static final String PREFIX = "SpannableStringBuilder:";
private static final String PROP_INPUT_STRING = PREFIX + "string";
private static final String PROP_SPAN_OBJECTS= PREFIX + "spanObjects";
private static final String PROP_SPAN_START= PREFIX + "spanStart";
private static final String PROP_SPAN_END = PREFIX + "spanEnd";
private static final String PROP_SPAN_FLAGS = PREFIX + "spanFlags";
Gson Serializer
public static class SpannableSerializer implements JsonSerializer<SpannableStringBuilder> {
#Override
public JsonElement serialize(SpannableStringBuilder spannableStringBuilder, Type type, JsonSerializationContext context) {
ParcelableSpan[] spanObjects = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), ParcelableSpan.class);
int[] spanStart = new int[spanObjects.length];
int[] spanEnd= new int[spanObjects.length];
int[] spanFlags = new int[spanObjects.length];
for(int i = 0; i < spanObjects.length; ++i) {
spanStart[i] = spannableStringBuilder.getSpanStart(spanObjects[i]);
spanEnd[i] = spannableStringBuilder.getSpanEnd(spanObjects[i]);
spanFlags[i] = spannableStringBuilder.getSpanFlags(spanObjects[i]);
}
JsonObject jsonSpannable = new JsonObject();
jsonSpannable.addProperty(PROP_INPUT_STRING, spannableStringBuilder.toString());
jsonSpannable.addProperty(PROP_SPAN_OBJECTS, gson.toJson(spanObjects));
jsonSpannable.addProperty(PROP_SPAN_START, gson.toJson(spanStart));
jsonSpannable.addProperty(PROP_SPAN_END, gson.toJson(spanEnd));
jsonSpannable.addProperty(PROP_SPAN_FLAGS, gson.toJson(spanFlags));
return jsonSpannable;
}
}
Gson Deserializer
public static class SpannableDeserializer implements JsonDeserializer<SpannableStringBuilder> {
#Override
public SpannableStringBuilder deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject jsonSpannable = jsonElement.getAsJsonObject();
try {
String spannableString = jsonSpannable.get(PROP_INPUT_STRING).getAsString();
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(spannableString);
String spanObjectJson = jsonSpannable.get(PROP_SPAN_OBJECTS).getAsString();
ParcelableSpan[] spanObjects = gson.fromJson(spanObjectJson, ParcelableSpan[].class);
String spanStartJson = jsonSpannable.get(PROP_SPAN_START).getAsString();
int[] spanStart = gson.fromJson(spanStartJson, int[].class);
String spanEndJson = jsonSpannable.get(PROP_SPAN_END).getAsString();
int[] spanEnd = gson.fromJson(spanEndJson, int[].class);
String spanFlagsJson = jsonSpannable.get(PROP_SPAN_FLAGS).getAsString();
int[] spanFlags = gson.fromJson(spanFlagsJson, int[].class);
for (int i = 0; i <spanObjects.length; ++i) {
spannableStringBuilder.setSpan(spanObjects[i], spanStart[i], spanEnd[i], spanFlags[i]);
}
return spannableStringBuilder;
} catch (Exception ex) {
Log.e(TAG, Log.getStackTraceString(ex));
}
return null;
}
}
For ParcelableSpan you might need to register the types to GSON like so:
RuntimeTypeAdapterFactory
.of(ParcelableSpan.class)
.registerSubtype(ForegroundColorSpan.class);
.registerSubtype(StyleSpan.class); //etc.
My use case was converting a TextView's contents, including color and style, to/from a hex string. Building off Dan's answer, I came up with the following code. Hopefully if someone has a similar use case, it'll save you some headache.
Store textBox's contents to string:
String actualText = textBox.getText().toString();
SpannableString spanStr = new SpannableString(textBox.getText());
ForegroundColorSpan[] fSpans = spanStr.getSpans(0,spanStr.length(),ForegroundColorSpan.class);
StyleSpan[] sSpans = spanStr.getSpans(0,spanStr.length(),StyleSpan.class);
int nSpans = fSpans.length;
String spanInfo = "";
String headerInfo = String.format("%08X",nSpans);
for (int i = 0; i < nSpans; i++) {
spanInfo += String.format("%08X",fSpans[i].getForegroundColor());
spanInfo += String.format("%08X",spanStr.getSpanStart(fSpans[i]));
spanInfo += String.format("%08X",spanStr.getSpanEnd(fSpans[i]));
}
nSpans = sSpans.length;
headerInfo += String.format("%08X",nSpans);
for (int i = 0; i < nSpans; i++) {
spanInfo += String.format("%08X",sSpans[i].getStyle());
spanInfo += String.format("%08X",spanStr.getSpanStart(sSpans[i]));
spanInfo += String.format("%08X",spanStr.getSpanEnd(sSpans[i]));
}
headerInfo += spanInfo;
headerInfo += actualText;
return headerInfo;
Retrieve textBox's contents from string:
String header = tvString.substring(0,8);
int fSpans = Integer.parseInt(header,16);
header = tvString.substring(8,16);
int sSpans = Integer.parseInt(header,16);
int nSpans = fSpans + sSpans;
SpannableString tvText = new SpannableString(tvString.substring(nSpans*24+16));
tvString = tvString.substring(16,nSpans*24+16);
int cc, ss, ee;
int begin;
for (int i = 0; i < fSpans; i++) {
begin = i*24;
cc = (int) Long.parseLong(tvString.substring(begin,begin+8),16);
ss = (int) Long.parseLong(tvString.substring(begin+8,begin+16),16);
ee = (int) Long.parseLong(tvString.substring(begin+16,begin+24),16);
tvText.setSpan(new ForegroundColorSpan(cc), ss, ee, 0);
}
for (int i = 0; i < sSpans; i++) {
begin = i*24+fSpans*24;
cc = (int) Long.parseLong(tvString.substring(begin,begin+8),16);
ss = (int) Long.parseLong(tvString.substring(begin+8,begin+16),16);
ee = (int) Long.parseLong(tvString.substring(begin+16,begin+24),16);
tvText.setSpan(new StyleSpan(cc), ss, ee, 0);
}
textBox.setText(tvText);
The reason for the (int) Long.parseLong in the retrieval code is because the style/color can be negative numbers. This trips up parseInt and results in an overflow error. But, doing parseLong and then casting to int gives the correct (positive or negative) integer.
This problem is interesting, because you have to save all the information you want from the SpannableString or SpannableStringBuilder, Gson doesn't pick them automatically. Using HTML didn't work properly for my implementation, so here's another working solution. All answers here are incomplete, you have to do something like this:
class SpannableSerializer : JsonSerializer<SpannableStringBuilder?>, JsonDeserializer<SpannableStringBuilder?> {
private val gson: Gson
get() {
val rtaf = RuntimeTypeAdapterFactory
.of(ParcelableSpan::class.java, ParcelableSpan::class.java.simpleName)
.registerSubtype(ForegroundColorSpan::class.java, ForegroundColorSpan::class.java.simpleName)
.registerSubtype(StyleSpan::class.java, StyleSpan::class.java.simpleName)
.registerSubtype(RelativeSizeSpan::class.java, RelativeSizeSpan::class.java.simpleName)
.registerSubtype(SuperscriptSpan::class.java, SuperscriptSpan::class.java.simpleName)
.registerSubtype(UnderlineSpan::class.java, UnderlineSpan::class.java.simpleName)
return GsonBuilder()
.registerTypeAdapterFactory(rtaf)
.create()
}
override fun serialize(spannableStringBuilder: SpannableStringBuilder?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
val spanTypes = spannableStringBuilder?.getSpans(0, spannableStringBuilder.length, ParcelableSpan::class.java)
val spanStart = IntArray(spanTypes?.size ?: 0)
val spanEnd = IntArray(spanTypes?.size ?: 0)
val spanFlags = IntArray(spanTypes?.size ?: 0)
val spanInfo = DoubleArray(spanTypes?.size ?: 0)
spanTypes?.forEachIndexed { i, span ->
when (span) {
is ForegroundColorSpan -> spanInfo[i] = span.foregroundColor.toDouble()
is StyleSpan -> spanInfo[i] = span.style.toDouble()
is RelativeSizeSpan -> spanInfo[i] = span.sizeChange.toDouble()
}
spanStart[i] = spannableStringBuilder.getSpanStart(span)
spanEnd[i] = spannableStringBuilder.getSpanEnd(span)
spanFlags[i] = spannableStringBuilder.getSpanFlags(span)
}
val jsonSpannable = JsonObject()
jsonSpannable.addProperty(INPUT_STRING, spannableStringBuilder.toString())
jsonSpannable.addProperty(SPAN_TYPES, gson.toJson(spanTypes))
jsonSpannable.addProperty(SPAN_START, gson.toJson(spanStart))
jsonSpannable.addProperty(SPAN_END, gson.toJson(spanEnd))
jsonSpannable.addProperty(SPAN_FLAGS, gson.toJson(spanFlags))
jsonSpannable.addProperty(SPAN_INFO, gson.toJson(spanInfo))
return jsonSpannable
}
override fun deserialize(jsonElement: JsonElement, type: Type, jsonDeserializationContext: JsonDeserializationContext): SpannableStringBuilder {
val jsonSpannable = jsonElement.asJsonObject
val spannableString = jsonSpannable[INPUT_STRING].asString
val spannableStringBuilder = SpannableStringBuilder(spannableString)
val spanObjectJson = jsonSpannable[SPAN_TYPES].asString
val spanTypes: Array<ParcelableSpan> = gson.fromJson(spanObjectJson, Array<ParcelableSpan>::class.java)
val spanStartJson = jsonSpannable[SPAN_START].asString
val spanStart: IntArray = gson.fromJson(spanStartJson, IntArray::class.java)
val spanEndJson = jsonSpannable[SPAN_END].asString
val spanEnd: IntArray = gson.fromJson(spanEndJson, IntArray::class.java)
val spanFlagsJson = jsonSpannable[SPAN_FLAGS].asString
val spanFlags: IntArray = gson.fromJson(spanFlagsJson, IntArray::class.java)
val spanInfoJson = jsonSpannable[SPAN_INFO].asString
val spanInfo: DoubleArray = gson.fromJson(spanInfoJson, DoubleArray::class.java)
for (i in spanTypes.indices) {
when (spanTypes[i]) {
is ForegroundColorSpan -> spannableStringBuilder.setSpan(ForegroundColorSpan(spanInfo[i].toInt()), spanStart[i], spanEnd[i], spanFlags[i])
is StyleSpan -> spannableStringBuilder.setSpan(StyleSpan(spanInfo[i].toInt()), spanStart[i], spanEnd[i], spanFlags[i])
is RelativeSizeSpan -> spannableStringBuilder.setSpan(RelativeSizeSpan(spanInfo[i].toFloat()), spanStart[i], spanEnd[i], spanFlags[i])
else -> spannableStringBuilder.setSpan(spanTypes[i], spanStart[i], spanEnd[i], spanFlags[i])
}
}
return spannableStringBuilder
}
companion object {
private const val PREFIX = "SSB:"
private const val INPUT_STRING = PREFIX + "string"
private const val SPAN_TYPES = PREFIX + "spanTypes"
private const val SPAN_START = PREFIX + "spanStart"
private const val SPAN_END = PREFIX + "spanEnd"
private const val SPAN_FLAGS = PREFIX + "spanFlags"
private const val SPAN_INFO = PREFIX + "spanInfo"
}
}
If there are other types of spans you have to add them in the when sections and pick the associated information of the span, it's easy to add them all.
RuntimeTypeAdapterFactory is private in the the gson library, you have to copy it to your project.
https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
now use it!
val gson by lazy {
val type: Type = object : TypeToken<SpannableStringBuilder>() {}.type
GsonBuilder()
.registerTypeAdapter(type, SpannableSerializer())
.create()
}
val ssb = gson.fromJson("your json here", SpannableStringBuilder::class.java)