나는 내 모든 앱에서 많이해온 텍스트의 일부에 색상을 지정하고 싶을 때마다 코드로이 작업을 수행한다는 아이디어가 마음에 들지 않았습니다. 정의 된 색상) 그래서 내가 직접 만들었습니다. 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>
- "nothing"스타일을 지정할 필요가 없기 때문에 모든 콘텐츠
이제 속성에 대해 이걸 사용하겠습니다 ..
([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>() {
// Use a matcher internally.
final Matcher matcher = p.matcher(input);
// Keep a match around that supports any interleaving of hasNext/next calls.
MatchResult pending;
public boolean hasNext() {
// Lazily fill pending, and avoid calling find() multiple times if the
// clients call hasNext() repeatedly before sampling via next().
if (pending == null && matcher.find()) {
pending = matcher.toMatchResult();
}
return pending != null;
}
public MatchResult next() {
// Fill pending if necessary (as when clients call next() without
// checking hasNext()), throw if not possible.
if (!hasNext()) { throw new NoSuchElementException(); }
// Consume pending so next call to hasNext() does a find().
MatchResult next = pending;
pending = null;
return next;
}
/** Required to satisfy the interface, but unsupported. */
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) {
// Intercept and process text
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();
// Used to correct content position after tossing tags
int totalOffset = 0;
// Iterate through text
for (MatchResult match : matches(Markable.Patterns.XML, parcel)) {
// Get tag name
String tag = match.group(1);
// Match with a defined tag name "case-sensitive"
if (!tag.equals(Markable.Tags.MARKABLE)) {
// Break if no match
break;
}
// Extract data
String attributes = match.group(2);
String content = match.group(3);
int outset = match.start(0);
int ending = match.end(0);
int offset = totalOffset; // offset=0 since no preceded changes happened
int contentLength = match.group(3).length();
// Calculate offset for the next element
totalOffset = (ending - outset) - contentLength;
// Add to markable sheets
MarkableSheet sheet =
new MarkableSheet(attributes, content, outset, ending, offset, contentLength);
markableSheets.put(tag, sheet);
// Toss the tag and keep content
Matcher reMatcher = Markable.Patterns.XML.matcher(parcel);
parcel = reMatcher.replaceFirst(content);
}
// Initialize spannable with the modified text
Markable = new SpannableString(parcel);
// Iterate through markable sheets
for (MarkableSheet sheet : markableSheets.values()) {
// Iterate through attributes
for (MatchResult match : matches(Markable.Patterns.ATTRIBUTES, sheet.getAttributes())) {
String attribute = match.group(1);
String value = match.group(3);
// Apply styles
stylate(attribute,
value,
sheet.getOutset(),
sheet.getOffset(),
sheet.getContentLength());
}
}
return Markable;
}
마지막으로 스타일링, 그래서 여기 에이 답변을 위해 만든 매우 간단한 스타일러가 있습니다.
public void stylate(String attribute, String value, int outset, int offset, int length) {
// Correct position
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>
로모그래퍼 태그를 포장해야합니다 CDATA Section
및 탈출 "
로\
.
필자는 불필요한 코드를 채울 필요없이 모든 다른 방식으로 텍스트의 일부를 처리하는 모듈 식 솔루션으로 만들었습니다.