ฉันไม่ชอบความคิดในการทำสิ่งนี้ด้วยรหัสทุกครั้งที่ฉันต้องการระบายสีส่วนของข้อความที่ฉันทำมามากมายในแอพทั้งหมดของฉัน (และเนื่องจากในบางกรณีข้อความจะถูกตั้งค่าในรันไทม์ด้วยอินไลน์ที่แตกต่างกัน - สีที่กำหนด) MarkableTextView
ดังนั้นผมจึงสร้างของตัวเอง
แนวคิดคือ:
- ตรวจหาแท็ก XML จากสตริง
- ระบุและจับคู่ชื่อแท็ก
- แยกและบันทึกแอตทริบิวต์และตำแหน่งของข้อความ
- ลบแท็กและเก็บเนื้อหา
- วนซ้ำผ่านแอตทริบิวต์และใช้สไตล์
นี่คือกระบวนการทีละขั้นตอน:
ก่อนอื่นฉันต้องการวิธีค้นหาแท็ก XML ในสตริงที่กำหนดและRegex
ทำเคล็ดลับ ..
<([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)(?:\s+([^>]*))?>([^>][^<]*)</\1\s*>
เพื่อให้ตรงกับแท็ก XML ข้างต้นจะต้องมีเกณฑ์ดังต่อไปนี้:
- ชื่อแท็กที่ถูกต้องเหมือน
<a>
<a >
<a-a>
<a ..attrs..>
แต่ไม่ใช่< a>
<1>
- แท็กปิดที่มีชื่อตรงกันเช่น
<a></a>
แต่ไม่ใช่<a></b>
- เนื้อหาใด ๆ เนื่องจากไม่จำเป็นต้องจัดรูปแบบ "ไม่มีอะไร"
ตอนนี้สำหรับคุณสมบัติเราจะใช้อันนี้ ..
([a-zA-Z]+)\s*=\s*(['"])\s*([^'"]+?)\s*\2
มีแนวคิดเหมือนกันและโดยทั่วไปฉันไม่จำเป็นต้องไปไกลสำหรับทั้งคู่เนื่องจากคอมไพเลอร์จะดูแลส่วนที่เหลือหากมีสิ่งใดผิดรูปแบบ
ตอนนี้เราต้องการคลาสที่สามารถเก็บข้อมูลที่แยกได้:
public class MarkableSheet {
private String attributes;
private String content;
private int outset;
private int ending;
private int offset;
private int contentLength;
public MarkableSheet(String attributes, String content, int outset, int ending, int offset, int contentLength) {
this.attributes = attributes;
this.content = content;
this.outset = outset;
this.ending = ending;
this.offset = offset;
this.contentLength = contentLength;
}
public String getAttributes() {
return attributes;
}
public String getContent() {
return content;
}
public int getOutset() {
return outset;
}
public int getContentLength() {
return contentLength;
}
public int getEnding() {
return ending;
}
public int getOffset() {
return offset;
}
}
ก่อนอื่นเราจะเพิ่มตัววนซ้ำสุดเจ๋งที่ฉันใช้มานานเพื่อวนรอบการแข่งขัน (จำผู้แต่งไม่ได้) :
public static Iterable<MatchResult> matches(final Pattern p, final CharSequence input) {
return new Iterable<MatchResult>() {
public Iterator<MatchResult> iterator() {
return new Iterator<MatchResult>() {
final Matcher matcher = p.matcher(input);
MatchResult pending;
public boolean hasNext() {
if (pending == null && matcher.find()) {
pending = matcher.toMatchResult();
}
return pending != null;
}
public MatchResult next() {
if (!hasNext()) { throw new NoSuchElementException(); }
MatchResult next = pending;
pending = null;
return next;
}
public void remove() { throw new UnsupportedOperationException(); }
};
}
};
}
MarkableTextView:
public class MarkableTextView extends AppCompatTextView {
public MarkableTextView(Context context) {
super(context);
}
public MarkableTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MarkableTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void setText(CharSequence text, BufferType type) {
text = prepareText(text.toString());
super.setText(text, type);
}
public Spannable Markable;
private Spannable prepareText(String text) {
String parcel = text;
Multimap<String, MarkableSheet> markableSheets = ArrayListMultimap.create();
int totalOffset = 0;
for (MatchResult match : matches(Markable.Patterns.XML, parcel)) {
String tag = match.group(1);
if (!tag.equals(Markable.Tags.MARKABLE)) {
break;
}
String attributes = match.group(2);
String content = match.group(3);
int outset = match.start(0);
int ending = match.end(0);
int offset = totalOffset;
int contentLength = match.group(3).length();
totalOffset = (ending - outset) - contentLength;
MarkableSheet sheet =
new MarkableSheet(attributes, content, outset, ending, offset, contentLength);
markableSheets.put(tag, sheet);
Matcher reMatcher = Markable.Patterns.XML.matcher(parcel);
parcel = reMatcher.replaceFirst(content);
}
Markable = new SpannableString(parcel);
for (MarkableSheet sheet : markableSheets.values()) {
for (MatchResult match : matches(Markable.Patterns.ATTRIBUTES, sheet.getAttributes())) {
String attribute = match.group(1);
String value = match.group(3);
stylate(attribute,
value,
sheet.getOutset(),
sheet.getOffset(),
sheet.getContentLength());
}
}
return Markable;
}
ในที่สุดการจัดแต่งทรงผมนี่คือสไตเลอร์ที่เรียบง่ายมากที่ฉันสร้างขึ้นเพื่อคำตอบนี้:
public void stylate(String attribute, String value, int outset, int offset, int length) {
outset -= offset;
length += outset;
if (attribute.equals(Markable.Tags.TEXT_STYLE)) {
if (value.contains(Markable.Tags.BOLD) && value.contains(Markable.Tags.ITALIC)) {
Markable.setSpan(
new StyleSpan(Typeface.BOLD_ITALIC),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
else if (value.contains(Markable.Tags.BOLD)) {
Markable.setSpan(
new StyleSpan(Typeface.BOLD),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
else if (value.contains(Markable.Tags.ITALIC)) {
Markable.setSpan(
new StyleSpan(Typeface.ITALIC),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (value.contains(Markable.Tags.UNDERLINE)) {
Markable.setSpan(
new UnderlineSpan(),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
if (attribute.equals(Markable.Tags.TEXT_COLOR)) {
if (value.equals(Markable.Tags.ATTENTION)) {
Markable.setSpan(
new ForegroundColorSpan(ContextCompat.getColor(
getContext(),
R.color.colorAttention)),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
else if (value.equals(Markable.Tags.INTERACTION)) {
Markable.setSpan(
new ForegroundColorSpan(ContextCompat.getColor(
getContext(),
R.color.colorInteraction)),
outset,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
และนี่คือลักษณะของMarkable
คลาสที่มีคำจำกัดความ:
public class Markable {
public static class Patterns {
public static final Pattern XML =
Pattern.compile("<([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)(?:\\s+([^>]*))?>([^>][^<]*)</\\1\\s*>");
public static final Pattern ATTRIBUTES =
Pattern.compile("(\\S+)\\s*=\\s*(['\"])\\s*(.+?)\\s*\\2");
}
public static class Tags {
public static final String MARKABLE = "markable";
public static final String TEXT_STYLE = "textStyle";
public static final String BOLD = "bold";
public static final String ITALIC = "italic";
public static final String UNDERLINE = "underline";
public static final String TEXT_COLOR = "textColor";
public static final String ATTENTION = "attention";
public static final String INTERACTION = "interaction";
}
}
สิ่งที่เราต้องการตอนนี้คือการอ้างอิงสตริงและโดยพื้นฐานแล้วมันควรมีลักษณะดังนี้:
<string name="markable_string">
<![CDATA[Hello <markable textStyle=\"underline\" textColor=\"interaction\">world</markable>!]]>
</string>
อย่าลืมปิดแท็กด้วย a CDATA Section
และ Escape "
ด้วย\
ด้วย
ฉันทำให้สิ่งนี้เป็นโซลูชันแบบแยกส่วนเพื่อประมวลผลส่วนต่างๆของข้อความด้วยวิธีต่างๆทั้งหมดโดยไม่จำเป็นต้องยัดโค้ดที่ไม่จำเป็นไว้ข้างหลัง