Tüm uygulamalarımda çok yaptığım metnin kısımlarını renklendirmek istediğim her seferinde bunu kodla yapma fikrinden hoşlanmadım (ve bazı durumlarda metin çalışma zamanında farklı satır içi ile ayarlandığı için) tanımlanmış renkler) bu yüzden kendiminkini yarattım MarkableTextView
.
Fikir şuydu:
- XML etiketlerini dizeden algılama
- Etiket adını tanımlayın ve eşleştirin
- Metnin niteliklerini ve konumunu ayıklayın ve kaydedin
- Etiketi kaldırın ve içeriği saklayın
- Nitelikleri yineleyin ve stilleri uygulayın
İşte adım adım süreç:
Öncelikle, belirli bir dizedeki XML etiketlerini bulmanın bir yolunu bulmalıydım ve Regex
hile yaptım ..
<([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)(?:\s+([^>]*))?>([^>][^<]*)</\1\s*>
Yukarıdakinin bir XML etiketiyle eşleşmesi için aşağıdaki kriterlere sahip olması gerekir:
- Gibi
<a>
<a >
<a-a>
<a ..attrs..>
ama değil geçerli etiket adı< a>
<1>
- Eşleşen bir ada sahip olan
<a></a>
ancak olmayan kapanış etiketi<a></b>
- "Hiçbir şey" stiline gerek olmadığından herhangi bir içerik
Şimdi öznitelikler için bunu kullanacağız ..
([a-zA-Z]+)\s*=\s*(['"])\s*([^'"]+?)\s*\2
Aynı konsepte sahip ve genellikle her ikisi için de uzağa gitmeme gerek kalmadı çünkü herhangi bir şey formatın dışına çıkarsa derleyici geri kalanı halledecektir.
Şimdi çıkarılan verileri tutabilecek bir sınıfa ihtiyacımız var:
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;
}
}
Her şeyden önce, eşleşmeler arasında dolaşmak için uzun süredir kullandığım bu harika yineleyiciyi ekleyeceğiz (yazarı hatırlayamıyorum) :
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;
}
Son olarak, şekillendirme, işte bu cevap için yaptığım çok basit bir şekillendirici:
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);
}
}
}
Ve Markable
tanımları içeren sınıf şu şekilde görünür:
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";
}
}
Şimdi ihtiyacımız olan tek şey bir dizgeye başvurmaktır ve temelde şöyle görünmelidir:
<string name="markable_string">
<![CDATA[Hello <markable textStyle=\"underline\" textColor=\"interaction\">world</markable>!]]>
</string>
Bir etiketleri sarmak için emin olun CDATA Section
ve kaçış "
ile \
.
Bunu, gereksiz kodların arkasına doldurulmasına gerek kalmadan metnin bölümlerini farklı şekillerde işlemek için modüler bir çözüm olarak yaptım.