下面列出了怎么用android.text.SpannedString的API类实例代码及写法,或者点击链接到github查看源代码。
@Override
public final void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
@NonNull Paint paint) {
if ((start | end | (end - start) | (text.length() - end)) < 0) {
throw new IndexOutOfBoundsException();
}
if (text instanceof String || text instanceof SpannedString
|| text instanceof SpannableString) {
nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
paint.mBidiFlags, paint.getNativeInstance());
} else if (text instanceof GraphicsOperations) {
((GraphicsOperations) text).drawText(this, start, end, x, y,
paint);
} else {
char[] buf = TemporaryBuffer.obtain(end - start);
TextUtils.getChars(text, start, end, buf, 0);
nDrawText(mNativeCanvasWrapper, buf, 0, end - start, x, y,
paint.mBidiFlags, paint.getNativeInstance());
TemporaryBuffer.recycle(buf);
}
}
@Test
public void textWithQuote() {
SpannedString result = builder.markdownToSpans("Text\n> Quote");
assertEquals("Text\nQuote", result.toString());
Object[] spans = result.getSpans(0, result.length(), Object.class);
assertEquals(3, spans.length);
StyleSpan styleSpan = (StyleSpan) spans[0];
assertEquals(Typeface.ITALIC, styleSpan.getStyle());
assertEquals(5, result.getSpanStart(styleSpan));
assertEquals(10, result.getSpanEnd(styleSpan));
LeadingMarginSpan leadingMarginSpan = (LeadingMarginSpan) spans[1];
assertEquals(5, result.getSpanStart(leadingMarginSpan));
assertEquals(10, result.getSpanEnd(leadingMarginSpan));
RelativeSizeSpan relativeSizeSpan = (RelativeSizeSpan) spans[2];
assertEquals(5, result.getSpanStart(relativeSizeSpan));
assertEquals(10, result.getSpanEnd(relativeSizeSpan));
}
private static List<Pair<String, Bitmap>> getTextViewImageSpanBitmap(TextView textView) {
List<Pair<String, Bitmap>> bitmaps = new ArrayList<>();
try {
CharSequence text = textView.getText();
if (text instanceof SpannedString) {
Field mSpansField = Class.forName("android.text.SpannableStringInternal").getDeclaredField("mSpans");
mSpansField.setAccessible(true);
Object[] spans = (Object[]) mSpansField.get(text);
for (Object span : spans) {
if (span instanceof ImageSpan) {
bitmaps.add(new Pair<>("SpanBitmap", getDrawableBitmap(((ImageSpan) span).getDrawable())));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return bitmaps;
}
public static Matcher<View> withColors(final int... colors) {
return new BoundedMatcher<View, TextView>(TextView.class) {
@Override public boolean matchesSafely(TextView textView) {
SpannedString text = (SpannedString) textView.getText();
ForegroundColorSpan[] spans = text.getSpans(0, text.length(), ForegroundColorSpan.class);
if (spans.length != colors.length) {
return false;
}
for (int i = 0; i < colors.length; ++i) {
if (spans[i].getForegroundColor() != colors[i]) {
return false;
}
}
return true;
}
@Override public void describeTo(Description description) {
description.appendText("has colors:");
for (int color : colors) {
description.appendText(" " + getHexColor(color));
}
}
};
}
/**
* Returns displayable styled text from the provided HTML string.
* <br/>
* Note: Also handles the case when a String has multiple HTML entities that translate to one
* character e.g. {@literal &#39;} which is essentially an apostrophe that should normally
* occur as {@literal '}
*
* @param html The source string having HTML content.
* @return Formatted HTML.
*/
@NonNull
public static Spanned formatHtml(@NonNull String html) {
final String REGEX = "(&#?[a-zA-Z0-9]+;)";
final Pattern PATTERN = Pattern.compile(REGEX);
Spanned formattedHtml = new SpannedString(html);
String previousHtml = null;
// Break the loop if there isn't an HTML entity in the text or when all the HTML entities
// have been decoded. Also break the loop in the special case when a String having the
// same format as an HTML entity is left but it isn't essentially a decodable HTML entity
// e.g. &#asdfasd;
while (PATTERN.matcher(html).find() && !html.equals(previousHtml)) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
formattedHtml = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
} else {
formattedHtml = Html.fromHtml(html);
}
previousHtml = html;
html = formattedHtml.toString();
}
return formattedHtml;
}
@Test
public void withContentDescriptionString() {
View view = new View(context);
view.setContentDescription(null);
assertTrue(withContentDescription(nullValue(String.class)).matches(view));
String testText = "test text!";
view.setContentDescription(testText);
assertTrue(withContentDescription(is(testText)).matches(view));
assertFalse(withContentDescription(is("blah")).matches(view));
assertFalse(withContentDescription(is("")).matches(view));
// Test withContentDescription(String) directly.
assertTrue(withContentDescription(testText).matches(view));
view.setContentDescription(null);
String nullString = null;
assertTrue(withContentDescription(nullString).matches(view));
assertFalse(withContentDescription(testText).matches(view));
// Test when the view's content description is not a String type.
view.setContentDescription(new SpannedString(testText));
assertTrue(withContentDescription(testText).matches(view));
assertFalse(withContentDescription("different text").matches(view));
}
/**
* Trigger page load of specific thread. Called by thread list or url links.
* See startRefresh() and ./request/ThreadPageRequest for volley implementation.
* @param threadId Thread ID for requested thread. (Required)
* @param page Page number to load, Optional, if -1 will go to last post, if 0 will go to newest unread post.
* @param userId (Optional) UserId for filtering.
* @param fromUrl True if request was sent by internal URL request. Used to decide if we should push current state into backstack.
*/
public void loadThread(int threadId, int page, int userId, boolean fromUrl) {
if(fromUrl && isThreadLoaded()){
threadBackstack.push(saveThreadState(new Bundle()));
}else{
threadBackstack.clear();
}
this.ignorePageProgress = true;
this.threadId = threadId;
this.page = page;
this.userId = userId;
this.maxPage = 0;
this.forumId = 0;
this.bookmarked = false;
this.threadTitle = new SpannedString(getString(R.string.thread_view_loading));
setTitle(threadTitle);
invalidateOptionsMenu();
updateNavbar();
startRefresh();
threadView.loadUrl("about:blank");
}
/**
* Trigger page load of specific thread by redirecting from a postID. Called by thread list or url links.
* See startRefresh() and ./request/ThreadPageRequest for volley implementation.
* @param postId Post ID to redirect to. (Required)
* @param fromUrl True if request was sent by internal URL request. Used to decide if we should push current state into backstack.
*/
public void loadPost(long postId, boolean fromUrl){
if(fromUrl && isThreadLoaded()){
threadBackstack.push(saveThreadState(new Bundle()));
}else{
threadBackstack.clear();
}
this.ignorePageProgress = true;
this.postId = postId;
this.threadId = 0;
this.page = 0;
this.maxPage = 0;
this.forumId = 0;
this.bookmarked = false;
this.threadTitle = new SpannedString(getString(R.string.thread_view_loading));
setTitle(threadTitle);
invalidateOptionsMenu();
updateNavbar();
startRefresh();
threadView.loadUrl("about:blank");
}
private static Spanned removeTrailingNewlines(Spanned text) {
int trailingNewlineCharacterCount = 0;
for (int i = text.length() - 1; i >= 0; i--) {
char c = text.charAt(i);
if ((c == '\n') || (c == '\r')) {
trailingNewlineCharacterCount++;
} else {
break;
}
}
if (trailingNewlineCharacterCount == 0) {
return text;
}
return new SpannedString(
text.subSequence(0, text.length() - trailingNewlineCharacterCount));
}
/**
* Commits a result to the final transcript.
*
* <p>NOTE: This does not clear the hypothesis. Users who get partial results (hypotheses) should
* prefer calling setCurrentHypothesis(...) and then finalizeCurrentHypothesis().
*/
public void addFinalizedResult(TranscriptionResult resultSingleUtterance) {
String lineBreak = obtainLineBreaksFromLastFinalizedResult(resultSingleUtterance);
resultsDeque.add(
new CachedResult(
resultSingleUtterance.toBuilder().build(),
formatSingleFinalized(resultSingleUtterance, !lineBreak.isEmpty()),
SpannedString.valueOf(lineBreak)));
lastSpeakerId = getLastSpeakerIdTag(resultSingleUtterance);
}
/** Returns the current finalized text with the hypothesis appended to the end. */
public Spanned getFormattedTranscript() {
SpannableStringBuilder builder = new SpannableStringBuilder();
for (CachedResult timestampedAndCachedResult : resultsDeque) {
builder.append(timestampedAndCachedResult.getFormattedText());
}
builder.append(getFormattedHypothesis());
return new SpannedString(builder);
}
/** Returns the latest sentence from transcription result. */
public Spanned getMostRecentTranscriptSegment() {
SpannableStringBuilder builder = new SpannableStringBuilder();
builder.append(getFormattedHypothesis());
if (!TextUtils.isEmpty(builder)) {
return new SpannedString(builder);
}
if (!resultsDeque.isEmpty()) {
CachedResult timestampedAndCachedResult = resultsDeque.getLast();
builder.append(timestampedAndCachedResult.getFormattedText());
}
return new SpannedString(builder);
}
public SpannedString markdownToSpans(@NonNull final String string) {
TextMarkdown markdown = parser.parse(string);
// In the SpannableStringBuilder, the text and the markup are mutable.
SpannableStringBuilder builder = new SpannableStringBuilder();
for (int i = 0; i < markdown.getElements().size(); i++) {
buildSpans(markdown.getElements().get(i), builder);
}
return new SpannedString(builder);
}
@Test
public void text() {
SpannedString result = builder.markdownToSpans("Text");
Object[] spans = result.getSpans(0, result.length(), Object.class);
assertEquals(0, spans.length);
}
@Test
public void textWithBulletPoints() {
SpannedString result = builder.markdownToSpans("Points\n* one\n+ two");
assertEquals("Points\none\ntwo", result.toString());
Object[] spans = result.getSpans(0, result.length(), Object.class);
assertEquals(2, spans.length);
BulletPointSpan bulletSpan = (BulletPointSpan) spans[0];
assertEquals(7, result.getSpanStart(bulletSpan));
assertEquals(11, result.getSpanEnd(bulletSpan));
BulletPointSpan bulletSpan2 = (BulletPointSpan) spans[1];
assertEquals(11, result.getSpanStart(bulletSpan2));
assertEquals(14, result.getSpanEnd(bulletSpan2));
}
@Test
public void textWithCode() {
SpannedString result = builder.markdownToSpans("Text `code`");
assertEquals("Text code", result.toString());
Object[] spans = result.getSpans(0, result.length(), Object.class);
assertEquals(1, spans.length);
CodeBlockSpan codeSpan = (CodeBlockSpan) spans[0];
assertEquals(5, result.getSpanStart(codeSpan));
assertEquals(9, result.getSpanEnd(codeSpan));
}
/**
* 设置hint大小
*
* @param size
* @param v
* @param res
*/
public static void setViewHintSize(Context context, int size, TextView v, int res) {
SpannableString ss = new SpannableString(getResources(context).getString(
res));
// 新建一个属性对象,设置文字的大小
AbsoluteSizeSpan ass = new AbsoluteSizeSpan(size, true);
// 附加属性到文本
ss.setSpan(ass, 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置hint
v.setHint(new SpannedString(ss)); // 一定要进行转换,否则属性会消失
}
public static CharSequence stringOrSpannedString(CharSequence source) {
if (source == null)
return null;
if (source instanceof SpannedString)
return source;
if (source instanceof Spanned)
return new SpannedString(source);
return source.toString();
}
/**
* 设置hint大小
*
* @param size
* @param v
* @param res
*/
public static void setViewHintSize(Context context, int size, TextView v, int res) {
SpannableString ss = new SpannableString(getResources(context).getString(
res));
// 新建一个属性对象,设置文字的大小
AbsoluteSizeSpan ass = new AbsoluteSizeSpan(size, true);
// 附加属性到文本
ss.setSpan(ass, 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置hint
v.setHint(new SpannedString(ss)); // 一定要进行转换,否则属性会消失
}
@Override
public void onReportSuccess(final SpannedString spannedString) {
isReporting = false;
Assertions.assertNotNull(mReportButton).setEnabled(true);
Assertions.assertNotNull(mLoadingIndicator).setVisibility(View.GONE);
Assertions.assertNotNull(mReportTextView).setText(spannedString);
}
@Override
public void onReportError(final SpannedString spannedString) {
isReporting = false;
Assertions.assertNotNull(mReportButton).setEnabled(true);
Assertions.assertNotNull(mLoadingIndicator).setVisibility(View.GONE);
Assertions.assertNotNull(mReportTextView).setText(spannedString);
}
/**
* 设置hint大小
*
* @param size
* @param v
* @param res
*/
public static void setViewHintSize(Context context, int size, TextView v, int res) {
SpannableString ss = new SpannableString(getResources(context).getString(
res));
// 新建一个属性对象,设置文字的大小
AbsoluteSizeSpan ass = new AbsoluteSizeSpan(size, true);
// 附加属性到文本
ss.setSpan(ass, 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置hint
v.setHint(new SpannedString(ss)); // 一定要进行转换,否则属性会消失
}
/**
* 设置hint大小
*
* @param size
* @param v
* @param res
*/
public static void setViewHintSize(Context context, int size, TextView v, int res) {
SpannableString ss = new SpannableString(getResources(context).getString(
res));
// 新建一个属性对象,设置文字的大小
AbsoluteSizeSpan ass = new AbsoluteSizeSpan(size, true);
// 附加属性到文本
ss.setSpan(ass, 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置hint
v.setHint(new SpannedString(ss)); // 一定要进行转换,否则属性会消失
}
/**
* 设置hint大小
*
* @param size
* @param v
* @param res
*/
public static void setViewHintSize(Context context, int size, TextView v, int res) {
SpannableString ss = new SpannableString(getResources(context).getString(
res));
// 新建一个属性对象,设置文字的大小
AbsoluteSizeSpan ass = new AbsoluteSizeSpan(size, true);
// 附加属性到文本
ss.setSpan(ass, 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置hint,一定要进行转换,否则属性会消失
v.setHint(new SpannedString(ss));
}
@Override
public Spanned convertHtmlToDisplayType(String htmlString, String normalString)
{
if(htmlString == null)
{
return new SpannedString(normalString);
}
return Html.fromHtml(htmlString);
}
@Override
public RTText convertTo(RTFormat destFormat, RTMediaFactory<RTImage, RTAudio, RTVideo> mediaFactory) {
if (destFormat instanceof RTFormat.Html) {
return ConverterTextToHtml.convert(this);
} else if (destFormat instanceof RTFormat.Spanned) {
return new RTSpanned(new SpannedString(getText()));
}
return super.convertTo(destFormat, mediaFactory);
}
@Override
public RTText convertTo(RTFormat destFormat, RTMediaFactory<RTImage, RTAudio, RTVideo> mediaFactory) {
if (destFormat instanceof RTFormat.Html) {
return ConverterTextToHtml.convert(this);
} else if (destFormat instanceof RTFormat.Spanned) {
return new RTSpanned(new SpannedString(getText()));
}
return super.convertTo(destFormat, mediaFactory);
}
public static Spanned toSpanned(final TextView container, final Drafty content,
final ClickListener clicker) {
if (content == null) {
return new SpannedString("");
}
if (content.isPlain()) {
return new SpannedString(content.toString());
}
TreeNode result = content.format(new SpanFormatter(container, clicker));
return result.toSpanned();
}
/**
* 设置hint大小
*
* @param size
* @param v
* @param res
*/
public static void setViewHintSize(Context context, int size, TextView v, int res) {
SpannableString ss = new SpannableString(getResources(context).getString(
res));
// 新建一个属性对象,设置文字的大小
AbsoluteSizeSpan ass = new AbsoluteSizeSpan(size, true);
// 附加属性到文本
ss.setSpan(ass, 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置hint
v.setHint(new SpannedString(ss)); // 一定要进行转换,否则属性会消失
}
/**
* 设置hint大小
*
* @param size
* @param v
* @param res
*/
public static void setViewHintSize(int size, TextView v, int res) {
SpannableString ss = new SpannableString(getResources().getString(
res));
// 新建一个属性对象,设置文字的大小
AbsoluteSizeSpan ass = new AbsoluteSizeSpan(size, true);
// 附加属性到文本
ss.setSpan(ass, 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置hint
v.setHint(new SpannedString(ss)); // 一定要进行转换,否则属性会消失
}