public void invalidateSpannables() { log("invalidating all spannables -- consider everything nullified"); final Spannable spans = getText(); final String text = spans.toString(); // Remove existing spans for (CharacterStyle style : mSpans) { spans.removeSpan(style); } // Loop over the text, looking for new spans for (int i = 0; i < text.length(); i++) { for (SpanComponent component : mComponents) { String equation = component.parse(text.substring(i)); if (equation != null) { MathSpannable span = component.getSpan(equation); spans.setSpan(span, i, i + equation.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); i += equation.length(); break; } } } setSelection(getSelectionStart()); }
/** * Given either a Spannable String or a regular String and a token, apply * the given CharacterStyle to the span between the tokens. * <p> * NOTE: This method was adapted from: * http://www.androidengineer.com/2010/08/easy-method-for-formatting-android.html * <p> * <p> * For example, {@code setSpanBetweenTokens("Hello ##world##!", "##", new * ForegroundColorSpan(0xFFFF0000));} will return a CharSequence {@code * "Hello world!"} with {@code world} in red. */ private CharSequence setSpanBetweenTokens(CharSequence text, String token, CharacterStyle... cs) { // Start and end refer to the points where the span will apply int tokenLen = token.length(); int start = text.toString().indexOf(token) + tokenLen; int end = text.toString().indexOf(token, start); if (start > -1 && end > -1) { // Copy the spannable string to a mutable spannable string SpannableStringBuilder ssb = new SpannableStringBuilder(text); for (CharacterStyle c : cs) ssb.setSpan(c, start, end, 0); text = ssb; } return text; }
void setBold(boolean isBold) { int index = getSelectionIndex(); if (index >= 0 && index < mSections.size()) { mSections.get(index).setBold(isBold); } Editable edit = getEditableText(); int star = getSectionStart(); int end = getSectionEnd(); if (isBold) { edit.setSpan(new StyleSpan(Typeface.BOLD), star, end, Typeface.BOLD); } else { StyleSpan[] styleSpans = edit.getSpans(star, end, StyleSpan.class); for (CharacterStyle span : styleSpans) { if (span instanceof StyleSpan && ((StyleSpan) span).getStyle() == Typeface.BOLD) edit.removeSpan(span); } } }
void setItalic(boolean isItalic) { int index = getSelectionIndex(); if (index >= 0 && index < mSections.size()) { mSections.get(index).setItalic(isItalic); } Editable edit = getEditableText(); int star = getSectionStart(); int end = getSectionEnd(); if (isItalic) { edit.setSpan(new StyleSpan(Typeface.ITALIC), star, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { StyleSpan[] styleSpans = edit.getSpans(star, end, StyleSpan.class); for (CharacterStyle span : styleSpans) { if (span instanceof StyleSpan && ((StyleSpan) span).getStyle() == Typeface.ITALIC) edit.removeSpan(span); } } }
void setItalic(boolean isItalic, int index) { if (index >= 0 && index < mSections.size()) { mSections.get(index).setItalic(isItalic); } Editable edit = getEditableText(); int star = getSectionStart(); int end = getSectionEnd(); if (isItalic) { edit.setSpan(new StyleSpan(Typeface.ITALIC), star, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { StyleSpan[] styleSpans = edit.getSpans(star, end, StyleSpan.class); for (CharacterStyle span : styleSpans) { if (span instanceof StyleSpan && ((StyleSpan) span).getStyle() == Typeface.ITALIC) edit.removeSpan(span); } } }
void setBold(boolean isBold, int index) { if (index >= 0 && index < mSections.size()) { mSections.get(index).setBold(isBold); } Editable edit = getEditableText(); int star = getSectionStart(index); int end = getSectionEnd(index); if (star >= end) return; if (isBold) { edit.setSpan(new StyleSpan(Typeface.BOLD), star, end, Typeface.BOLD); } else { StyleSpan[] styleSpans = edit.getSpans(star, end, StyleSpan.class); for (CharacterStyle span : styleSpans) { if (span instanceof StyleSpan && ((StyleSpan) span).getStyle() == Typeface.BOLD) edit.removeSpan(span); } } }
public static <A extends CharacterStyle, B extends CharacterStyle> Spannable replaceAll(Spanned original, Class<A> sourceType, SpanConverter<A, B> converter, final ClickSpan.OnClickListener listener) { SpannableString result = new SpannableString(original); A[] spans = result.getSpans(0, result.length(), sourceType); for (A span : spans) { int start = result.getSpanStart(span); int end = result.getSpanEnd(span); int flags = result.getSpanFlags(span); result.removeSpan(span); result.setSpan(converter.convert(span, listener), start, end, flags); } return (result); }
/** * 检查当前位置是否命中在spannable上, 如果是, 返回spannable的start position */ private int findStartPosition(Spannable spannable, int startWidthPosition) { CharacterStyle[] oldSpans = spannable.getSpans(startWidthPosition, spannable.length(), CharacterStyle.class); int position = startWidthPosition; for (CharacterStyle oldSpan : oldSpans) { int spanStart = spannable.getSpanStart(oldSpan); int spanEnd = spannable.getSpanEnd(oldSpan); if (spanStart <= startWidthPosition && spanEnd > startWidthPosition) { position = spanStart; } if (spanStart >= startWidthPosition) { spannable.removeSpan(oldSpan); } } //L.e("call: findStartPosition([spannable, startWidthPosition]) " + startWidthPosition + " -> " + position); return position; }
/** * Given either a Spannable String or a regular String and a token, apply * the given CharacterStyle to the span between the tokens. * <p/> * NOTE: This method was adapted from: * http://www.androidengineer.com/2010/08/easy-method-for-formatting-android.html * <p/> * <p/> * For example, {@code setSpanBetweenTokens("Hello ##world##!", "##", new * ForegroundColorSpan(0xFFFF0000));} will return a CharSequence {@code * "Hello world!"} with {@code world} in red. */ private CharSequence setSpanBetweenTokens(CharSequence text, String token, CharacterStyle... cs) { // Start and end refer to the points where the span will apply int tokenLen = token.length(); int start = text.toString().indexOf(token) + tokenLen; int end = text.toString().indexOf(token, start); if (start > -1 && end > -1) { // Copy the spannable string to a mutable spannable string SpannableStringBuilder ssb = new SpannableStringBuilder(text); for (CharacterStyle c : cs) ssb.setSpan(c, start, end, 0); text = ssb; } return text; }
/** * Convert a spanned text within a paragraph */ private void withinParagraph(final Spanned text, int start, int end) { // create sorted set of CharacterStyles SortedSet<CharacterStyle> sortedSpans = new TreeSet<>(new Comparator<CharacterStyle>() { @Override public int compare(CharacterStyle s1, CharacterStyle s2) { int start1 = text.getSpanStart(s1); int start2 = text.getSpanStart(s2); if (start1 != start2) return start1 - start2; // span which starts first comes first int end1 = text.getSpanEnd(s1); int end2 = text.getSpanEnd(s2); if (end1 != end2) return end2 - end1; // longer span comes first // if the paragraphs have the same span [start, end] we compare their name // compare the name only because local + anonymous classes have no canonical name return s1.getClass().getName().compareTo(s2.getClass().getName()); } }); List<CharacterStyle> spanList = Arrays.asList(text.getSpans(start, end, CharacterStyle.class)); sortedSpans.addAll(spanList); // process paragraphs/divs convertText(text, start, end, sortedSpans); }
private void handleEndTag(CharacterStyle style) { if (style instanceof URLSpan) { mOut.append("</a>"); } else if (style instanceof TypefaceSpan) { mOut.append("</font>"); } else if (style instanceof ForegroundColorSpan) { mOut.append("</font>"); } else if (style instanceof BackgroundColorSpan) { mOut.append("</font>"); } else if (style instanceof AbsoluteSizeSpan) { mOut.append("</font>"); } else if (style instanceof StrikethroughSpan) { mOut.append("</strike>"); } else if (style instanceof SubscriptSpan) { mOut.append("</sub>"); } else if (style instanceof SuperscriptSpan) { mOut.append("</sup>"); } else if (style instanceof UnderlineSpan) { mOut.append("</u>"); } else if (style instanceof BoldSpan) { mOut.append("</b>"); } else if (style instanceof ItalicSpan) { mOut.append("</i>"); } }
private void init(CharSequence source, int start, int end) { int initial = 20; mSpans = new Object[initial]; mSpanData = new int[initial * 3]; if (source instanceof Spanned) { Spanned sp = (Spanned) source; for (Object span : sp.getSpans(start, end, Object.class)) { if (span instanceof CharacterStyle || span instanceof ParagraphStyle) { int st = sp.getSpanStart(span); int en = sp.getSpanEnd(span); int fl = sp.getSpanFlags(span); if (st < start) st = start; if (en > end) en = end; setSpan(span, st - start, en - start, fl); } } } }
/** * Convert a spanned text within a paragraph */ private void withinParagraph(final Spanned text, int start, int end) { // create sorted set of CharacterStyles SortedSet<CharacterStyle> sortedSpans = new TreeSet<CharacterStyle>(new Comparator<CharacterStyle>() { @Override public int compare(CharacterStyle s1, CharacterStyle s2) { int start1 = text.getSpanStart(s1); int start2 = text.getSpanStart(s2); if (start1 != start2) return start1 - start2; // span which starts first comes first int end1 = text.getSpanEnd(s1); int end2 = text.getSpanEnd(s2); if (end1 != end2) return end2 - end1; // longer span comes first // if the paragraphs have the same span [start, end] we compare their name // compare the name only because local + anonymous classes have no canonical name return s1.getClass().getName().compareTo(s2.getClass().getName()); } }); List<CharacterStyle> spanList = Arrays.asList(text.getSpans(start, end, CharacterStyle.class)); sortedSpans.addAll(spanList); // process paragraphs/divs convertText(text, start, end, sortedSpans); }
/** * {@inheritDoc} * @param s */ @Override public void afterTextChanged(Editable s) { for (CharacterStyle style: mLastStyle) { s.removeSpan(style); } List<MarkdownSyntaxModel> models = MarkdownSyntaxGenerator.syntaxModelsForString(s.toString()); if (models.size() == 0) { return; } mLastStyle.clear(); for (MarkdownSyntaxModel model : models) { MarkdownSyntaxType type = model.getSyntaxType(); Range range = model.getRange(); // CharacterStyle style = MarkdownSyntaxGenerator.styleFromSyntaxType(type); int low = range.getLower(); int upper = range.getUpper(); // mLastStyle.add(style); // s.setSpan(style, low, upper, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } }
public static SpannableStringBuilder highlight(String text, String target) { SpannableStringBuilder spannable = new SpannableStringBuilder(text); CharacterStyle span = null; for (int i = 0; i < specials.length; i++) { if (target.contains(specials[i])) { target = target.replace(specials[i], "\\" + specials[i]); } } Pattern p = Pattern.compile(target.toLowerCase()); Matcher m = p.matcher(text.toLowerCase()); while (m.find()) { span = new ForegroundColorSpan(Color.rgb(253, 113, 34));// 需要重复! spannable.setSpan(span, m.start(), m.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } return spannable; }
/** * Replace the control codes * * @param m * @param ssb * @param style */ private static void replaceControlCodes(Matcher m, SpannableStringBuilder ssb, CharacterStyle style) { ArrayList<Integer> toremove = new ArrayList<Integer>(); while ( m.find() ) { toremove.add(0, m.start()); // Remove the ending control character unless it's \x0F if( m.group(2) != null && m.group(2) != m.group(3) ) { toremove.add(0, m.end() - 1); } ssb.setSpan(style, m.start(), m.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } for( Integer i : toremove ) { ssb.delete(i, i + 1); } }
@Override public int getSize(@NonNull Rect outRect, @NonNull Paint paint, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @Nullable Paint.FontMetricsInt fm) { int width = super.getSize(outRect, paint, text, start, end, fm); if (styles != null) { for (CharacterStyle style : styles) { if (style instanceof SupportSpan) { width = Math.max(width, ((SupportSpan) style).getSize(frame, paint, text, start, end, fm)); } else if (style instanceof ReplacementSpan) { width = Math.max(width, ((ReplacementSpan) style).getSize(paint, text, start, end, fm)); } else if (paint instanceof TextPaint) { if (style instanceof MetricAffectingSpan) { ((MetricAffectingSpan) style).updateMeasureState((TextPaint) paint); } } } } frame.right = width; return width; }
@Override public int getSize(@NonNull Paint paint, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @Nullable Paint.FontMetricsInt fm) { width = 0; for (CharacterStyle style : styles) { if (style instanceof ReplacementSpan) { width = Math.max(width, ((ReplacementSpan) style).getSize(paint, text, start, end, fm)); } else if (paint instanceof TextPaint) { if (style instanceof MetricAffectingSpan) { ((MetricAffectingSpan) style).updateMeasureState((TextPaint) paint); } } } if (fm != null) { paint.getFontMetricsInt(fm); } paint.getFontMetricsInt(fontMetricsInt); width = Math.max(width, (int) Math.ceil(paint.measureText(text, start, end))); frame.right = width; frame.top = fontMetricsInt.top; frame.bottom = fontMetricsInt.bottom; if (bitmap == null) { bitmap = Bitmap.createBitmap(width, frame.bottom - frame.top, Bitmap.Config.ARGB_8888); bitmapCanvas = new Canvas(bitmap); } return width; }
@Override public void draw(@NonNull Canvas canvas, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, int top, int y, int bottom, @NonNull Paint paint) { // bitmapCanvas.drawColor(Color.GRAY, PorterDuff.Mode.CLEAR); int color = /*paint instanceof TextPaint ? ((TextPaint) paint).bgColor :*/ paint.getColor(); bitmapCanvas.drawColor(color); for (CharacterStyle style : styles) { if (style instanceof ReplacementSpan) { ((ReplacementSpan) style).draw(bitmapCanvas, text, start, end, 0, top, y, bottom, paint); } else if (paint instanceof TextPaint) { style.updateDrawState((TextPaint) paint); } } paint.setXfermode(xfermode); bitmapCanvas.drawText(text, start, end, 0, y, paint); canvas.drawBitmap(bitmap, x, 0, null); }
/** * Convert a spanned text within a paragraph */ private void withinParagraph(final Spanned text, int start, int end) { // create sorted set of CharacterStyles SortedSet<CharacterStyle> sortedSpans = new TreeSet<>((s1, s2) -> { int start1 = text.getSpanStart(s1); int start2 = text.getSpanStart(s2); if (start1 != start2) return start1 - start2; // span which starts first comes first int end1 = text.getSpanEnd(s1); int end2 = text.getSpanEnd(s2); if (end1 != end2) return end2 - end1; // longer span comes first // if the paragraphs have the same span [start, end] we compare their name // compare the name only because local + anonymous classes have no canonical name return s1.getClass().getName().compareTo(s2.getClass().getName()); }); List<CharacterStyle> spanList = Arrays.asList(text.getSpans(start, end, CharacterStyle.class)); sortedSpans.addAll(spanList); // process paragraphs/divs convertText(text, start, end, sortedSpans); }
private void writeStyles(CharacterStyle styles[], DataOutputStream dos) throws IOException { dos.writeInt(styles.length); for(CharacterStyle characterStyle : styles) { writeSingleCharacterStyle(characterStyle,dos); // if (characterStyle.getUnderlying()!=null) { // CharacterStyle underLying = characterStyle.getUnderlying(); // if (!mWrittenTags.containsKey(underLying)) { // writeSingleCharacterStyle(underLying,dos); // } // int writtenTag = mWrittenTags.get(underLying); // dos.writeInt(-2); // dos.writeInt(writtenTag); // } else { // // } dos.writeInt(0xf00f); // sync mark } dos.writeInt(0xf00e); // sync mark }
public static <A extends CharacterStyle, B extends CharacterStyle> Spannable replaceAll( CharSequence original, Class<A> sourceType, SpanConverter<A, B> converter) { SpannableString result = new SpannableString(original); A[] spans = result.getSpans(0, result.length(), sourceType); for (A span : spans) { int start = result.getSpanStart(span); int end = result.getSpanEnd(span); int flags = result.getSpanFlags(span); result.removeSpan(span); result.setSpan(converter.convert(span), start, end, flags); } return (result); }
@CalledByNative private void populateUnderlinesFromSpans(CharSequence text, long underlines) { if (DEBUG_LOGS) { Log.i(TAG, "populateUnderlinesFromSpans: text [%s], underlines [%d]", text, underlines); } if (!(text instanceof SpannableString)) return; SpannableString spannableString = ((SpannableString) text); CharacterStyle spans[] = spannableString.getSpans(0, text.length(), CharacterStyle.class); for (CharacterStyle span : spans) { if (span instanceof BackgroundColorSpan) { nativeAppendBackgroundColorSpan(underlines, spannableString.getSpanStart(span), spannableString.getSpanEnd(span), ((BackgroundColorSpan) span).getBackgroundColor()); } else if (span instanceof UnderlineSpan) { nativeAppendUnderlineSpan(underlines, spannableString.getSpanStart(span), spannableString.getSpanEnd(span)); } } }
private static void setPreferenceTitleStyle(Preference preference, boolean bold, /*boolean underline,*/ boolean errorColor) { if (preference != null) { CharSequence title = preference.getTitle(); Spannable sbt = new SpannableString(title); Object spansToRemove[] = sbt.getSpans(0, title.length(), Object.class); for (Object span : spansToRemove) { if (span instanceof CharacterStyle) sbt.removeSpan(span); } if (bold/* || underline*/) { //if (bold) sbt.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, sbt.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); /*if (underline) sbt.setSpan(new UnderlineSpan(), 0, sbt.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);*/ if (errorColor) sbt.setSpan(new ForegroundColorSpan(Color.RED), 0, sbt.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); preference.setTitle(sbt); } else { preference.setTitle(sbt); } } }
private void setTextStyle(TextView textView, boolean errorColor) { if (textView != null) { CharSequence title = textView.getText(); Spannable sbt = new SpannableString(title); Object spansToRemove[] = sbt.getSpans(0, title.length(), Object.class); for (Object span : spansToRemove) { if (span instanceof CharacterStyle) sbt.removeSpan(span); } if (errorColor) { sbt.setSpan(new ForegroundColorSpan(Color.RED), 0, sbt.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); textView.setText(sbt); } else { textView.setText(sbt); } } }
private void setTitleStyle(Preference preference, boolean bold, boolean underline, boolean systemSettings) { if (preference != null) { CharSequence title = preference.getTitle(); if (systemSettings) { String s = title.toString(); if (!s.contains("(S)")) title = TextUtils.concat("(S) ", title); } Spannable sbt = new SpannableString(title); Object spansToRemove[] = sbt.getSpans(0, title.length(), Object.class); for (Object span : spansToRemove) { if (span instanceof CharacterStyle) sbt.removeSpan(span); } if (bold || underline) { if (bold) sbt.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, sbt.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); if (underline) sbt.setSpan(new UnderlineSpan(), 0, sbt.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); preference.setTitle(sbt); } else { preference.setTitle(sbt); } } }
/** * Replace the control codes * * @param m * @param ssb * @param style */ private static void replaceControlCodes(Matcher m, SpannableStringBuilder ssb, CharacterStyle style) { ArrayList<Integer> toremove = new ArrayList<Integer>(); while (m.find()) { toremove.add(0, m.start()); // Remove the ending control character unless it's \x0F if (m.group(2) != null && m.group(2) != m.group(3)) { toremove.add(0, m.end()-1); } ssb.setSpan(style, m.start(), m.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } for (Integer i : toremove) { ssb.delete(i, i+1); } }
public static <A extends CharacterStyle, B extends CharacterStyle> Spannable replaceAll(Spanned original, Class<A> sourceType, SpanConverter<A, B> converter) { SpannableString result=new SpannableString(original); A[] spans=result.getSpans(0, result.length(), sourceType); for (A span : spans) { int start=result.getSpanStart(span); int end=result.getSpanEnd(span); int flags=result.getSpanFlags(span); result.removeSpan(span); result.setSpan(converter.convert(span), start, end, flags); } return(result); }