回答編集履歴

3

修正

2023/03/03 00:12

投稿

jimbe
jimbe

スコア13318

test CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  ---
16
16
  操作できず表示の確認だけですが、 json を用いるようにしてみました。
17
- Span 毎に処理を行う必要があるため、 AbsoluteSizeSpan と ForegroundColorSpan だけに対応です。
17
+ Span 毎に処理を行う必要があるため、 サンプルとして AbsoluteSizeSpan と ForegroundColorSpan だけに対応です。
18
18
  ```java
19
19
  import androidx.appcompat.app.AppCompatActivity;
20
20
 

2

コード修正

2023/03/02 11:34

投稿

jimbe
jimbe

スコア13318

test CHANGED
@@ -35,7 +35,7 @@
35
35
  private static final class SpanObject {
36
36
  private static class What {
37
37
  private static final class Param {
38
- String type, value;
38
+ final String type, value;
39
39
  Param(int value) {
40
40
  type = int.class.getCanonicalName();
41
41
  this.value = "" + value;
@@ -43,6 +43,20 @@
43
43
  Param(boolean value) {
44
44
  type = boolean.class.getCanonicalName();
45
45
  this.value = "" + value;
46
+ }
47
+ Class<?> getType() {
48
+ switch(type) {
49
+ case "int": return int.class;
50
+ case "boolean": return boolean.class;
51
+ default: return String.class;
52
+ }
53
+ }
54
+ Object getValue() {
55
+ switch(type) {
56
+ case "int": return Integer.parseInt(value);
57
+ case "boolean": return Boolean.parseBoolean(value);
58
+ default: return value;
59
+ }
46
60
  }
47
61
  @Override
48
62
  public String toString() { return "[type="+type+",value="+value+"]"; }
@@ -64,23 +78,13 @@
64
78
  params[0] = new Param(span.getForegroundColor());
65
79
  }
66
80
  Object generate() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
67
- Class clazz = Class.forName(className);
68
81
  Class<?>[] paramTypes = new Class[params.length];
69
82
  Object[] initargs = new Object[params.length];
70
83
  for(int i=0; i<params.length; i++) {
71
- if(params[i].type.equals("int")) {
72
- paramTypes[i] = int.class;
73
- initargs[i] = Integer.parseInt(params[i].value);
74
- } else if(params[i].type.equals("boolean")) {
75
- paramTypes[i] = boolean.class;
76
- initargs[i] = Boolean.parseBoolean(params[i].value);
77
- } else { //String.class
78
- paramTypes[i] = Class.forName(params[i].type);
84
+ paramTypes[i] = params[i].getType();
79
- initargs[i] = params[i].value;
85
+ initargs[i] = params[i].getValue();
80
- }
86
+ }
81
- }
82
- Constructor constructor = clazz.getConstructor(paramTypes);
83
- return constructor.newInstance(initargs);
87
+ return Class.forName(className).getConstructor(paramTypes).newInstance(initargs);
84
88
  }
85
89
  @Override
86
90
  public String toString() {
@@ -160,7 +164,7 @@
160
164
  editText.setText(ssb);
161
165
 
162
166
  EditedText editedText = new EditedText(ssb);
163
- //Log.d("*** toString ***", editedText.toString());
167
+ Log.d("*** toString ***", editedText.toString());
164
168
 
165
169
  Gson gson = new Gson();
166
170
  String json = gson.toJson(editedText);

1

コード等追加

2023/03/02 11:21

投稿

jimbe
jimbe

スコア13318

test CHANGED
@@ -11,3 +11,224 @@
11
11
  と思われます。
12
12
 
13
13
  ただ、 realm で保存・再生する為に HTMLを介するのが正しいのかという点は再考する余地はあるように感じます。
14
+
15
+ ---
16
+ 操作できず表示の確認だけですが、 json を用いるようにしてみました。
17
+ Span 毎に処理を行う必要があるため、 AbsoluteSizeSpan と ForegroundColorSpan だけに対応です。
18
+ ```java
19
+ import androidx.appcompat.app.AppCompatActivity;
20
+
21
+ import android.graphics.Color;
22
+ import android.os.*;
23
+ import android.text.*;
24
+ import android.text.style.*;
25
+ import android.util.Log;
26
+ import android.widget.*;
27
+
28
+ import com.google.gson.Gson;
29
+ import com.google.gson.annotations.SerializedName;
30
+
31
+ import java.lang.reflect.*;
32
+ import java.util.regex.*;
33
+
34
+ class EditedText {
35
+ private static final class SpanObject {
36
+ private static class What {
37
+ private static final class Param {
38
+ String type, value;
39
+ Param(int value) {
40
+ type = int.class.getCanonicalName();
41
+ this.value = "" + value;
42
+ }
43
+ Param(boolean value) {
44
+ type = boolean.class.getCanonicalName();
45
+ this.value = "" + value;
46
+ }
47
+ @Override
48
+ public String toString() { return "[type="+type+",value="+value+"]"; }
49
+ }
50
+ @SerializedName("class")
51
+ String className;
52
+ Param[] params;
53
+ What(Class<?> clazz, int size) {
54
+ className = clazz.getCanonicalName();
55
+ params = new Param[size];
56
+ }
57
+ What(AbsoluteSizeSpan span) {
58
+ this(span.getClass(), 2);
59
+ params[0] = new Param(span.getSize());
60
+ params[1] = new Param(span.getDip());
61
+ }
62
+ What(ForegroundColorSpan span) {
63
+ this(span.getClass(), 1);
64
+ params[0] = new Param(span.getForegroundColor());
65
+ }
66
+ Object generate() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
67
+ Class clazz = Class.forName(className);
68
+ Class<?>[] paramTypes = new Class[params.length];
69
+ Object[] initargs = new Object[params.length];
70
+ for(int i=0; i<params.length; i++) {
71
+ if(params[i].type.equals("int")) {
72
+ paramTypes[i] = int.class;
73
+ initargs[i] = Integer.parseInt(params[i].value);
74
+ } else if(params[i].type.equals("boolean")) {
75
+ paramTypes[i] = boolean.class;
76
+ initargs[i] = Boolean.parseBoolean(params[i].value);
77
+ } else { //String.class
78
+ paramTypes[i] = Class.forName(params[i].type);
79
+ initargs[i] = params[i].value;
80
+ }
81
+ }
82
+ Constructor constructor = clazz.getConstructor(paramTypes);
83
+ return constructor.newInstance(initargs);
84
+ }
85
+ @Override
86
+ public String toString() {
87
+ StringBuilder sb = new StringBuilder("[");
88
+ sb.append("class=").append(className);
89
+ sb.append(",params=[");
90
+ for(int i=0; i<params.length; i++) sb.append(i > 0 ? "," : "").append(params[i]);
91
+ sb.append("]");
92
+ return sb.append("]").toString();
93
+ }
94
+ }
95
+ @SerializedName("object")
96
+ What what;
97
+ int start, end, flags;
98
+
99
+ @Override
100
+ public String toString() {
101
+ StringBuilder sb = new StringBuilder("[");
102
+ sb.append("what=").append(what);
103
+ sb.append(",start=").append(start);
104
+ sb.append(",end=").append(end);
105
+ sb.append(",flags=").append(flags);
106
+ return sb.append("]").toString();
107
+ }
108
+ }
109
+ private String text;
110
+ private SpanObject[] spans;
111
+
112
+ EditedText(Spanned spanned) {
113
+ text = spanned.toString();
114
+ Object[] spans = spanned.getSpans(0, spanned.length(), Object.class);
115
+ this.spans = new SpanObject[spans.length];
116
+ for(int i=0; i<spans.length; i++) {
117
+ SpanObject span = new SpanObject();
118
+ if(spans[i] instanceof AbsoluteSizeSpan) {
119
+ span.what = new SpanObject.What((AbsoluteSizeSpan)spans[i]);
120
+ } else if(spans[i] instanceof ForegroundColorSpan) {
121
+ span.what = new SpanObject.What((ForegroundColorSpan)spans[i]);
122
+ }
123
+ span.start = spanned.getSpanStart(spans[i]);
124
+ span.end = spanned.getSpanEnd(spans[i]);
125
+ span.flags = spanned.getSpanFlags(spans[i]);
126
+ this.spans[i] = span;
127
+ }
128
+ }
129
+
130
+ Spanned toSpanned() throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
131
+ SpannableStringBuilder ssb = new SpannableStringBuilder(text);
132
+ for(SpanObject span : spans) ssb.setSpan(span.what.generate(), span.start, span.end, span.flags);
133
+ return ssb;
134
+ }
135
+
136
+ @Override
137
+ public String toString() {
138
+ StringBuilder sb = new StringBuilder("[");
139
+ sb.append("text=").append(text);
140
+ sb.append(",spans=[");
141
+ for(int i=0; i<spans.length; i++) sb.append(i > 0 ? "," : "").append(spans[i]);
142
+ sb.append("]");
143
+ return sb.append("]").toString();
144
+ }
145
+ }
146
+
147
+ public class MainActivity extends AppCompatActivity {
148
+ @Override
149
+ protected void onCreate(Bundle savedInstanceState) {
150
+ super.onCreate(savedInstanceState);
151
+ setContentView(R.layout.activity_main);
152
+
153
+ EditText editText = findViewById(R.id.editText);
154
+ TextView textView = findViewById(R.id.textView);
155
+
156
+ SpannableStringBuilder ssb = new SpannableStringBuilder("ACDEFG");
157
+ ssb.setSpan(new AbsoluteSizeSpan(50, true), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
158
+ ssb.setSpan(new ForegroundColorSpan(Color.RED), 3, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
159
+ //editText.setText("1\n2\n\n3\n\n\n4\n\n\n\n5\n\n\n\n\n6");
160
+ editText.setText(ssb);
161
+
162
+ EditedText editedText = new EditedText(ssb);
163
+ //Log.d("*** toString ***", editedText.toString());
164
+
165
+ Gson gson = new Gson();
166
+ String json = gson.toJson(editedText);
167
+ Log.d("*** toJson ***", json);
168
+
169
+ EditedText newEdtedText = gson.fromJson(json, EditedText.class);
170
+ try {
171
+ textView.setText(newEdtedText.toSpanned());
172
+ } catch(ClassNotFoundException|
173
+ InvocationTargetException|
174
+ NoSuchMethodException|
175
+ IllegalAccessException|
176
+ InstantiationException e) {
177
+ e.printStackTrace();
178
+ }
179
+ }
180
+
181
+ //コントロールコードを文字化
182
+ private String toText(Spanned htmlSpanned) {
183
+ StringBuilder sb = new StringBuilder();
184
+ Pattern p = Pattern.compile("[\u0000-\u001F]");
185
+ int last = 0;
186
+ for(Matcher m=p.matcher(htmlSpanned); m.find(last); last=m.end()) {
187
+ sb.append(htmlSpanned.subSequence(last, m.start()));
188
+ char c = m.group().charAt(0);
189
+ sb.append("^").append((char)(c + '@'));
190
+ }
191
+ sb.append(htmlSpanned.subSequence(last, htmlSpanned.length()));
192
+ return sb.toString();
193
+ }
194
+ }
195
+ ```
196
+ res/layout/activity_main.xml
197
+ ```xml
198
+ <?xml version="1.0" encoding="utf-8"?>
199
+ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
200
+ xmlns:app="http://schemas.android.com/apk/res-auto"
201
+ xmlns:tools="http://schemas.android.com/tools"
202
+ android:layout_width="match_parent"
203
+ android:layout_height="match_parent"
204
+ tools:context=".MainActivity">
205
+
206
+ <EditText
207
+ android:id="@+id/editText"
208
+ android:layout_width="0dp"
209
+ android:layout_height="0dp"
210
+ android:textSize="30dp"
211
+ app:layout_constraintBottom_toTopOf="@id/textView"
212
+ app:layout_constraintEnd_toEndOf="parent"
213
+ app:layout_constraintStart_toStartOf="parent"
214
+ app:layout_constraintTop_toTopOf="parent" />
215
+ <TextView
216
+ android:id="@+id/textView"
217
+ android:layout_width="0dp"
218
+ android:layout_height="0dp"
219
+ android:textSize="30dp"
220
+ android:background="#e0e0e0"
221
+ app:layout_constraintBottom_toBottomOf="parent"
222
+ app:layout_constraintEnd_toEndOf="parent"
223
+ app:layout_constraintStart_toStartOf="parent"
224
+ app:layout_constraintTop_toBottomOf="@id/editText" />
225
+ </androidx.constraintlayout.widget.ConstraintLayout>
226
+ ```
227
+ エミュレータ実行結果
228
+ ![エミュレータ実行結果画面](https://ddjkaamml8q8x.cloudfront.net/questions/2023-03-02/05081f20-dfaf-4823-95fb-4a0c4a669686.png)
229
+ 実行時ログ
230
+ ```plain
231
+ D/*** toString ***: [text=ACDEFG,spans=[[what=[class=android.text.style.AbsoluteSizeSpan,params=[[type=int,value=50],[type=boolean,value=true]]],start=2,end=4,flags=33],[what=[class=android.text.style.ForegroundColorSpan,params=[[type=int,value=-65536]]],start=3,end=5,flags=33]]]
232
+ D/*** toJson ***: {"spans":[{"end":4,"flags":33,"start":2,"object":{"class":"android.text.style.AbsoluteSizeSpan","params":[{"type":"int","value":"50"},{"type":"boolean","value":"true"}]}},{"end":5,"flags":33,"start":3,"object":{"class":"android.text.style.ForegroundColorSpan","params":[{"type":"int","value":"-65536"}]}}],"text":"ACDEFG"}
233
+ ```
234
+