我想持久保存Spanned对象。 (我现在正在保存基于持久性的String,但是在它上面运行Html.fromHtml()需要1秒钟以上,显着减慢了用户界面。)
我看到像ParcelableSpan和SpannedString以及SpannableString这样的东西,但我不确定使用哪个。
答案 0 :(得分:6)
目前,Html.toHtml()
是您唯一的内置选项。 Parcelable
用于进程间通信,并不是设计为持久的。如果toHtml()
未涵盖您正在使用的所有特定类型的跨度,则必须使用自己的序列化机制。
由于保存对象涉及磁盘I / O,无论如何都应该在后台线程中执行此操作,无论toHtml()
的速度如何。
答案 1 :(得分:4)
我有类似的问题;我使用SpannableStringBuilder来保存字符串和一堆跨度,我希望能够保存和恢复此对象。我编写此代码以使用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);
}
我的情况我知道所有的跨度都是ForegroundColorSpan类型和标志SPAN_EXCLUSIVE_EXCLUSIVE,但是这个代码可以很容易地适应其他类型。
答案 2 :(得分:2)
我的用例是关于将跨区域放入捆绑包中,Google将我带到了这里。 @CommonsWare是正确的,Parcelable对于持久存储没有好处,但它可以存储到Bundle中。大多数跨度似乎扩展了ParcelableSpan,所以这对我在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);
然后可以用这样的东西恢复状态:
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]);
我在这里使用过SpannableStringBuilder但是它应该适用于任何实现Spanned的类,据我所知。可能可以将此代码包装到ParcelableSpanned中,但此版本现在看来还不错。
答案 3 :(得分:2)
来自Dan的想法:
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;
}
答案 4 :(得分:0)
我的用例是将TextView的内容(包括颜色和样式)转换为十六进制字符串或从十六进制字符串转换为十六进制字符串。基于Dan的答案,我想到了以下代码。希望有人使用类似的用例,可以为您省去一些麻烦。
将textBox的内容存储到字符串:
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;
从字符串中检索textBox的内容:
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);
在检索代码中使用(int)Long.parseLong的原因是因为样式/颜色可以为负数。这会触发parseInt并导致溢出错误。但是,先执行parseLong然后将其强制转换为int会给出正确的(正数或负数)整数。
答案 5 :(得分:0)
这个问题很有趣,因为你必须从 SpannableString 或 SpannableStringBuilder 中保存你想要的所有信息,Gson 不会自动选择它们。对于我的实现,使用 HTML 无法正常工作,因此这是另一个可行的解决方案。这里所有的答案都不完整,你必须这样做:
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"
}
}
如果还有其他类型的跨度,你必须在when部分添加它们并选择跨度的关联信息,很容易将它们全部添加。
RuntimeTypeAdapterFactory 在 gson 库中是私有的,您必须将其复制到您的项目中。 https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
现在使用它!
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)
答案 6 :(得分:-1)
我提出的解决方案是使用带有自定义序列化器/解串器的GSON。该解决方案结合了其他答案中提到的一些想法。
定义一些JSON密钥
library(dplyr)
df %>% mutate(VAR3=coalesce(VAR1,VAR2))
Gson Serializer
/* 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 Deserializer
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;
}
}
对于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;
}
}
,您可能需要将类型注册到GSON,如下所示:
ParcelableSpan