alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Android example source code file (TextUtils.java)

This example Android source code file (TextUtils.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Android by Example" TM.

Java - Android tags/keywords

android, annotation, cap_mode_sentences, cap_mode_words, charsequence, class, content, getchars, index_increment, non-nls-1, object, os, spannablestringbuilder, spanned, string, style, text, textpaint, truncateat, util

The TextUtils.java Android example source code

/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.text;

import com.android.internal.R;

import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.method.TextKeyListener.Capitalize;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AlignmentSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.BulletSpan;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan;
import android.text.style.LeadingMarginSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.QuoteSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.ReplacementSpan;
import android.text.style.ScaleXSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.SubscriptSpan;
import android.text.style.SuperscriptSpan;
import android.text.style.TextAppearanceSpan;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.util.Printer;

import com.android.internal.util.ArrayUtils;

import java.util.regex.Pattern;
import java.util.Iterator;

public class TextUtils {
    private TextUtils() { /* cannot be instantiated */ }

    private static String[] EMPTY_STRING_ARRAY = new String[]{};

    public static void getChars(CharSequence s, int start, int end,
                                char[] dest, int destoff) {
        Class c = s.getClass();

        if (c == String.class)
            ((String) s).getChars(start, end, dest, destoff);
        else if (c == StringBuffer.class)
            ((StringBuffer) s).getChars(start, end, dest, destoff);
        else if (c == StringBuilder.class)
            ((StringBuilder) s).getChars(start, end, dest, destoff);
        else if (s instanceof GetChars)
            ((GetChars) s).getChars(start, end, dest, destoff);
        else {
            for (int i = start; i < end; i++)
                dest[destoff++] = s.charAt(i);
        }
    }

    public static int indexOf(CharSequence s, char ch) {
        return indexOf(s, ch, 0);
    }

    public static int indexOf(CharSequence s, char ch, int start) {
        Class c = s.getClass();

        if (c == String.class)
            return ((String) s).indexOf(ch, start);

        return indexOf(s, ch, start, s.length());
    }

    public static int indexOf(CharSequence s, char ch, int start, int end) {
        Class c = s.getClass();

        if (s instanceof GetChars || c == StringBuffer.class ||
            c == StringBuilder.class || c == String.class) {
            final int INDEX_INCREMENT = 500;
            char[] temp = obtain(INDEX_INCREMENT);

            while (start < end) {
                int segend = start + INDEX_INCREMENT;
                if (segend > end)
                    segend = end;

                getChars(s, start, segend, temp, 0);

                int count = segend - start;
                for (int i = 0; i < count; i++) {
                    if (temp[i] == ch) {
                        recycle(temp);
                        return i + start;
                    }
                }

                start = segend;
            }

            recycle(temp);
            return -1;
        }

        for (int i = start; i < end; i++)
            if (s.charAt(i) == ch)
                return i;

        return -1;
    }

    public static int lastIndexOf(CharSequence s, char ch) {
        return lastIndexOf(s, ch, s.length() - 1);
    }

    public static int lastIndexOf(CharSequence s, char ch, int last) {
        Class c = s.getClass();

        if (c == String.class)
            return ((String) s).lastIndexOf(ch, last);

        return lastIndexOf(s, ch, 0, last);
    }

    public static int lastIndexOf(CharSequence s, char ch,
                                  int start, int last) {
        if (last < 0)
            return -1;
        if (last >= s.length())
            last = s.length() - 1;

        int end = last + 1;

        Class c = s.getClass();

        if (s instanceof GetChars || c == StringBuffer.class ||
            c == StringBuilder.class || c == String.class) {
            final int INDEX_INCREMENT = 500;
            char[] temp = obtain(INDEX_INCREMENT);

            while (start < end) {
                int segstart = end - INDEX_INCREMENT;
                if (segstart < start)
                    segstart = start;

                getChars(s, segstart, end, temp, 0);

                int count = end - segstart;
                for (int i = count - 1; i >= 0; i--) {
                    if (temp[i] == ch) {
                        recycle(temp);
                        return i + segstart;
                    }
                }

                end = segstart;
            }

            recycle(temp);
            return -1;
        }

        for (int i = end - 1; i >= start; i--)
            if (s.charAt(i) == ch)
                return i;

        return -1;
    }

    public static int indexOf(CharSequence s, CharSequence needle) {
        return indexOf(s, needle, 0, s.length());
    }

    public static int indexOf(CharSequence s, CharSequence needle, int start) {
        return indexOf(s, needle, start, s.length());
    }

    public static int indexOf(CharSequence s, CharSequence needle,
                              int start, int end) {
        int nlen = needle.length();
        if (nlen == 0)
            return start;

        char c = needle.charAt(0);

        for (;;) {
            start = indexOf(s, c, start);
            if (start > end - nlen) {
                break;
            }

            if (start < 0) {
                return -1;
            }

            if (regionMatches(s, start, needle, 0, nlen)) {
                return start;
            }

            start++;
        }
        return -1;
    }

    public static boolean regionMatches(CharSequence one, int toffset,
                                        CharSequence two, int ooffset,
                                        int len) {
        char[] temp = obtain(2 * len);

        getChars(one, toffset, toffset + len, temp, 0);
        getChars(two, ooffset, ooffset + len, temp, len);

        boolean match = true;
        for (int i = 0; i < len; i++) {
            if (temp[i] != temp[i + len]) {
                match = false;
                break;
            }
        }

        recycle(temp);
        return match;
    }

    /**
     * Create a new String object containing the given range of characters
     * from the source string.  This is different than simply calling
     * {@link CharSequence#subSequence(int, int) CharSequence.subSequence}
     * in that it does not preserve any style runs in the source sequence,
     * allowing a more efficient implementation.
     */
    public static String substring(CharSequence source, int start, int end) {
        if (source instanceof String)
            return ((String) source).substring(start, end);
        if (source instanceof StringBuilder)
            return ((StringBuilder) source).substring(start, end);
        if (source instanceof StringBuffer)
            return ((StringBuffer) source).substring(start, end);

        char[] temp = obtain(end - start);
        getChars(source, start, end, temp, 0);
        String ret = new String(temp, 0, end - start);
        recycle(temp);

        return ret;
    }

    /**
     * Returns a string containing the tokens joined by delimiters.
     * @param tokens an array objects to be joined. Strings will be formed from
     *     the objects by calling object.toString().
     */
    public static String join(CharSequence delimiter, Object[] tokens) {
        StringBuilder sb = new StringBuilder();
        boolean firstTime = true;
        for (Object token: tokens) {
            if (firstTime) {
                firstTime = false;
            } else {
                sb.append(delimiter);
            }
            sb.append(token);
        }
        return sb.toString();
    }

    /**
     * Returns a string containing the tokens joined by delimiters.
     * @param tokens an array objects to be joined. Strings will be formed from
     *     the objects by calling object.toString().
     */
    public static String join(CharSequence delimiter, Iterable tokens) {
        StringBuilder sb = new StringBuilder();
        boolean firstTime = true;
        for (Object token: tokens) {
            if (firstTime) {
                firstTime = false;
            } else {
                sb.append(delimiter);
            }
            sb.append(token);
        }
        return sb.toString();
    }

    /**
     * String.split() returns [''] when the string to be split is empty. This returns []. This does
     * not remove any empty strings from the result. For example split("a,", ","  ) returns {"a", ""}.
     *
     * @param text the string to split
     * @param expression the regular expression to match
     * @return an array of strings. The array will be empty if text is empty
     *
     * @throws NullPointerException if expression or text is null
     */
    public static String[] split(String text, String expression) {
        if (text.length() == 0) {
            return EMPTY_STRING_ARRAY;
        } else {
            return text.split(expression, -1);
        }
    }

    /**
     * Splits a string on a pattern. String.split() returns [''] when the string to be
     * split is empty. This returns []. This does not remove any empty strings from the result.
     * @param text the string to split
     * @param pattern the regular expression to match
     * @return an array of strings. The array will be empty if text is empty
     *
     * @throws NullPointerException if expression or text is null
     */
    public static String[] split(String text, Pattern pattern) {
        if (text.length() == 0) {
            return EMPTY_STRING_ARRAY;
        } else {
            return pattern.split(text, -1);
        }
    }

    /**
     * An interface for splitting strings according to rules that are opaque to the user of this
     * interface. This also has less overhead than split, which uses regular expressions and
     * allocates an array to hold the results.
     *
     * <p>The most efficient way to use this class is:
     *
     * <pre>
     * // Once
     * TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(delimiter);
     *
     * // Once per string to split
     * splitter.setString(string);
     * for (String s : splitter) {
     *     ...
     * }
     * </pre>
     */
    public interface StringSplitter extends Iterable<String> {
        public void setString(String string);
    }

    /**
     * A simple string splitter.
     *
     * <p>If the final character in the string to split is the delimiter then no empty string will
     * be returned for the empty string after that delimeter. That is, splitting <tt>"a,b," on
     * comma will return <tt>"a", "b", not "a", "b", "".
     */
    public static class SimpleStringSplitter implements StringSplitter, Iterator<String> {
        private String mString;
        private char mDelimiter;
        private int mPosition;
        private int mLength;

        /**
         * Initializes the splitter. setString may be called later.
         * @param delimiter the delimeter on which to split
         */
        public SimpleStringSplitter(char delimiter) {
            mDelimiter = delimiter;
        }

        /**
         * Sets the string to split
         * @param string the string to split
         */
        public void setString(String string) {
            mString = string;
            mPosition = 0;
            mLength = mString.length();
        }

        public Iterator<String> iterator() {
            return this;
        }

        public boolean hasNext() {
            return mPosition < mLength;
        }

        public String next() {
            int end = mString.indexOf(mDelimiter, mPosition);
            if (end == -1) {
                end = mLength;
            }
            String nextString = mString.substring(mPosition, end);
            mPosition = end + 1; // Skip the delimiter.
            return nextString;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    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();
    }

    /**
     * Returns true if the string is null or 0-length.
     * @param str the string to be examined
     * @return true if str is null or zero length
     */
    public static boolean isEmpty(CharSequence str) {
        if (str == null || str.length() == 0)
            return true;
        else
            return false;
    }

    /**
     * Returns the length that the specified CharSequence would have if
     * spaces and control characters were trimmed from the start and end,
     * as by {@link String#trim}.
     */
    public static int getTrimmedLength(CharSequence s) {
        int len = s.length();

        int start = 0;
        while (start < len && s.charAt(start) <= ' ') {
            start++;
        }

        int end = len;
        while (end > start && s.charAt(end - 1) <= ' ') {
            end--;
        }

        return end - start;
    }

    /**
     * Returns true if a and b are equal, including if they are both null.
     * <p>Note: In platform versions 1.1 and earlier, this method only worked well if
     * both the arguments were instances of String.</i>

* @param a first CharSequence to check * @param b second CharSequence to check * @return true if a and b are equal */ public static boolean equals(CharSequence a, CharSequence b) { if (a == b) return true; int length; if (a != null && b != null && (length = a.length()) == b.length()) { if (a instanceof String && b instanceof String) { return a.equals(b); } else { for (int i = 0; i < length; i++) { if (a.charAt(i) != b.charAt(i)) return false; } return true; } } return false; } // XXX currently this only reverses chars, not spans public static CharSequence getReverse(CharSequence source, int start, int end) { return new Reverser(source, start, end); } private static class Reverser implements CharSequence, GetChars { public Reverser(CharSequence source, int start, int end) { mSource = source; mStart = start; mEnd = end; } public int length() { return mEnd - mStart; } public CharSequence subSequence(int start, int end) { char[] buf = new char[end - start]; getChars(start, end, buf, 0); return new String(buf); } public String toString() { return subSequence(0, length()).toString(); } public char charAt(int off) { return AndroidCharacter.getMirror(mSource.charAt(mEnd - 1 - off)); } public void getChars(int start, int end, char[] dest, int destoff) { TextUtils.getChars(mSource, start + mStart, end + mStart, dest, destoff); AndroidCharacter.mirror(dest, 0, end - start); int len = end - start; int n = (end - start) / 2; for (int i = 0; i < n; i++) { char tmp = dest[destoff + i]; dest[destoff + i] = dest[destoff + len - i - 1]; dest[destoff + len - i - 1] = tmp; } } private CharSequence mSource; private int mStart; private int mEnd; } /** @hide */ public static final int ALIGNMENT_SPAN = 1; /** @hide */ public static final int FOREGROUND_COLOR_SPAN = 2; /** @hide */ public static final int RELATIVE_SIZE_SPAN = 3; /** @hide */ public static final int SCALE_X_SPAN = 4; /** @hide */ public static final int STRIKETHROUGH_SPAN = 5; /** @hide */ public static final int UNDERLINE_SPAN = 6; /** @hide */ public static final int STYLE_SPAN = 7; /** @hide */ public static final int BULLET_SPAN = 8; /** @hide */ public static final int QUOTE_SPAN = 9; /** @hide */ public static final int LEADING_MARGIN_SPAN = 10; /** @hide */ public static final int URL_SPAN = 11; /** @hide */ public static final int BACKGROUND_COLOR_SPAN = 12; /** @hide */ public static final int TYPEFACE_SPAN = 13; /** @hide */ public static final int SUPERSCRIPT_SPAN = 14; /** @hide */ public static final int SUBSCRIPT_SPAN = 15; /** @hide */ public static final int ABSOLUTE_SIZE_SPAN = 16; /** @hide */ public static final int TEXT_APPEARANCE_SPAN = 17; /** @hide */ public static final int ANNOTATION = 18; /** * Flatten a CharSequence and whatever styles can be copied across processes * into the parcel. */ public static void writeToParcel(CharSequence cs, Parcel p, int parcelableFlags) { if (cs instanceof Spanned) { p.writeInt(0); p.writeString(cs.toString()); Spanned sp = (Spanned) cs; Object[] os = sp.getSpans(0, cs.length(), Object.class); // note to people adding to this: check more specific types // before more generic types. also notice that it uses // "if" instead of "else if" where there are interfaces // so one object can be several. for (int i = 0; i < os.length; i++) { Object o = os[i]; Object prop = os[i]; if (prop instanceof CharacterStyle) { prop = ((CharacterStyle) prop).getUnderlying(); } if (prop instanceof ParcelableSpan) { ParcelableSpan ps = (ParcelableSpan)prop; p.writeInt(ps.getSpanTypeId()); ps.writeToParcel(p, parcelableFlags); writeWhere(p, sp, o); } } p.writeInt(0); } else { p.writeInt(1); if (cs != null) { p.writeString(cs.toString()); } else { p.writeString(null); } } } private static void writeWhere(Parcel p, Spanned sp, Object o) { p.writeInt(sp.getSpanStart(o)); p.writeInt(sp.getSpanEnd(o)); p.writeInt(sp.getSpanFlags(o)); } public static final Parcelable.Creator<CharSequence> CHAR_SEQUENCE_CREATOR = new Parcelable.Creator<CharSequence>() { /** * Read and return a new CharSequence, possibly with styles, * from the parcel. */ public CharSequence createFromParcel(Parcel p) { int kind = p.readInt(); if (kind == 1) return p.readString(); SpannableString sp = new SpannableString(p.readString()); while (true) { kind = p.readInt(); if (kind == 0) break; switch (kind) { case ALIGNMENT_SPAN: readSpan(p, sp, new AlignmentSpan.Standard(p)); break; case FOREGROUND_COLOR_SPAN: readSpan(p, sp, new ForegroundColorSpan(p)); break; case RELATIVE_SIZE_SPAN: readSpan(p, sp, new RelativeSizeSpan(p)); break; case SCALE_X_SPAN: readSpan(p, sp, new ScaleXSpan(p)); break; case STRIKETHROUGH_SPAN: readSpan(p, sp, new StrikethroughSpan(p)); break; case UNDERLINE_SPAN: readSpan(p, sp, new UnderlineSpan(p)); break; case STYLE_SPAN: readSpan(p, sp, new StyleSpan(p)); break; case BULLET_SPAN: readSpan(p, sp, new BulletSpan(p)); break; case QUOTE_SPAN: readSpan(p, sp, new QuoteSpan(p)); break; case LEADING_MARGIN_SPAN: readSpan(p, sp, new LeadingMarginSpan.Standard(p)); break; case URL_SPAN: readSpan(p, sp, new URLSpan(p)); break; case BACKGROUND_COLOR_SPAN: readSpan(p, sp, new BackgroundColorSpan(p)); break; case TYPEFACE_SPAN: readSpan(p, sp, new TypefaceSpan(p)); break; case SUPERSCRIPT_SPAN: readSpan(p, sp, new SuperscriptSpan(p)); break; case SUBSCRIPT_SPAN: readSpan(p, sp, new SubscriptSpan(p)); break; case ABSOLUTE_SIZE_SPAN: readSpan(p, sp, new AbsoluteSizeSpan(p)); break; case TEXT_APPEARANCE_SPAN: readSpan(p, sp, new TextAppearanceSpan(p)); break; case ANNOTATION: readSpan(p, sp, new Annotation(p)); break; default: throw new RuntimeException("bogus span encoding " + kind); } } return sp; } public CharSequence[] newArray(int size) { return new CharSequence[size]; } }; /** * Debugging tool to print the spans in a CharSequence. The output will * be printed one span per line. If the CharSequence is not a Spanned, * then the entire string will be printed on a single line. */ public static void dumpSpans(CharSequence cs, Printer printer, String prefix) { if (cs instanceof Spanned) { Spanned sp = (Spanned) cs; Object[] os = sp.getSpans(0, cs.length(), Object.class); for (int i = 0; i < os.length; i++) { Object o = os[i]; printer.println(prefix + cs.subSequence(sp.getSpanStart(o), sp.getSpanEnd(o)) + ": " + Integer.toHexString(System.identityHashCode(o)) + " " + o.getClass().getCanonicalName() + " (" + sp.getSpanStart(o) + "-" + sp.getSpanEnd(o) + ") fl=#" + sp.getSpanFlags(o)); } } else { printer.println(prefix + cs + ": (no spans)"); } } /** * Return a new CharSequence in which each of the source strings is * replaced by the corresponding element of the destinations. */ public static CharSequence replace(CharSequence template, String[] sources, CharSequence[] destinations) { SpannableStringBuilder tb = new SpannableStringBuilder(template); for (int i = 0; i < sources.length; i++) { int where = indexOf(tb, sources[i]); if (where >= 0) tb.setSpan(sources[i], where, where + sources[i].length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } for (int i = 0; i < sources.length; i++) { int start = tb.getSpanStart(sources[i]); int end = tb.getSpanEnd(sources[i]); if (start >= 0) { tb.replace(start, end, destinations[i]); } } return tb; } /** * Replace instances of "^1", "^2", etc. in the * <code>template CharSequence with the corresponding * <code>values. "^^" is used to produce a single caret in * the output. Only up to 9 replacement values are supported, * "^10" will be produce the first replacement value followed by a * '0'. * * @param template the input text containing "^1"-style * placeholder values. This object is not modified; a copy is * returned. * * @param values CharSequences substituted into the template. The * first is substituted for "^1", the second for "^2", and so on. * * @return the new CharSequence produced by doing the replacement * * @throws IllegalArgumentException if the template requests a * value that was not provided, or if more than 9 values are * provided. */ public static CharSequence expandTemplate(CharSequence template, CharSequence... values) { if (values.length > 9) { throw new IllegalArgumentException("max of 9 values are supported"); } SpannableStringBuilder ssb = new SpannableStringBuilder(template); try { int i = 0; while (i < ssb.length()) { if (ssb.charAt(i) == '^') { char next = ssb.charAt(i+1); if (next == '^') { ssb.delete(i+1, i+2); ++i; continue; } else if (Character.isDigit(next)) { int which = Character.getNumericValue(next) - 1; if (which < 0) { throw new IllegalArgumentException( "template requests value ^" + (which+1)); } if (which >= values.length) { throw new IllegalArgumentException( "template requests value ^" + (which+1) + "; only " + values.length + " provided"); } ssb.replace(i, i+2, values[which]); i += values[which].length(); continue; } } ++i; } } catch (IndexOutOfBoundsException ignore) { // happens when ^ is the last character in the string. } return ssb; } public static int getOffsetBefore(CharSequence text, int offset) { if (offset == 0) return 0; if (offset == 1) return 0; char c = text.charAt(offset - 1); if (c >= '\uDC00' && c <= '\uDFFF') { char c1 = text.charAt(offset - 2); if (c1 >= '\uD800' && c1 <= '\uDBFF') offset -= 2; else offset -= 1; } else { offset -= 1; } if (text instanceof Spanned) { ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset, ReplacementSpan.class); for (int i = 0; i < spans.length; i++) { int start = ((Spanned) text).getSpanStart(spans[i]); int end = ((Spanned) text).getSpanEnd(spans[i]); if (start < offset && end > offset) offset = start; } } return offset; } public static int getOffsetAfter(CharSequence text, int offset) { int len = text.length(); if (offset == len) return len; if (offset == len - 1) return len; char c = text.charAt(offset); if (c >= '\uD800' && c <= '\uDBFF') { char c1 = text.charAt(offset + 1); if (c1 >= '\uDC00' && c1 <= '\uDFFF') offset += 2; else offset += 1; } else { offset += 1; } if (text instanceof Spanned) { ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset, ReplacementSpan.class); for (int i = 0; i < spans.length; i++) { int start = ((Spanned) text).getSpanStart(spans[i]); int end = ((Spanned) text).getSpanEnd(spans[i]); if (start < offset && end > offset) offset = end; } } return offset; } private static void readSpan(Parcel p, Spannable sp, Object o) { sp.setSpan(o, p.readInt(), p.readInt(), p.readInt()); } /** * Copies the spans from the region <code>start...end in * <code>source to the region * <code>destoff...destoff+end-start in dest. * Spans in <code>source that begin before start * or end after <code>end but overlap this range are trimmed * as if they began at <code>start or ended at end. * * @throws IndexOutOfBoundsException if any of the copied spans * are out of range in <code>dest. */ public static void copySpansFrom(Spanned source, int start, int end, Class kind, Spannable dest, int destoff) { if (kind == null) { kind = Object.class; } Object[] spans = source.getSpans(start, end, kind); for (int i = 0; i < spans.length; i++) { int st = source.getSpanStart(spans[i]); int en = source.getSpanEnd(spans[i]); int fl = source.getSpanFlags(spans[i]); if (st < start) st = start; if (en > end) en = end; dest.setSpan(spans[i], st - start + destoff, en - start + destoff, fl); } } public enum TruncateAt { START, MIDDLE, END, MARQUEE, } public interface EllipsizeCallback { /** * This method is called to report that the specified region of * text was ellipsized away by a call to {@link #ellipsize}. */ public void ellipsized(int start, int end); } private static String sEllipsis = null; /** * Returns the original text if it fits in the specified width * given the properties of the specified Paint, * or, if it does not fit, a truncated * copy with ellipsis character added at the specified edge or center. */ public static CharSequence ellipsize(CharSequence text, TextPaint p, float avail, TruncateAt where) { return ellipsize(text, p, avail, where, false, null); } /** * Returns the original text if it fits in the specified width * given the properties of the specified Paint, * or, if it does not fit, a copy with ellipsis character added * at the specified edge or center. * If <code>preserveLength is specified, the returned copy * will be padded with zero-width spaces to preserve the original * length and offsets instead of truncating. * If <code>callback is non-null, it will be called to * report the start and end of the ellipsized range. */ public static CharSequence ellipsize(CharSequence text, TextPaint p, float avail, TruncateAt where, boolean preserveLength, EllipsizeCallback callback) { if (sEllipsis == null) { Resources r = Resources.getSystem(); sEllipsis = r.getString(R.string.ellipsis); } int len = text.length(); // Use Paint.breakText() for the non-Spanned case to avoid having // to allocate memory and accumulate the character widths ourselves. if (!(text instanceof Spanned)) { float wid = p.measureText(text, 0, len); if (wid <= avail) { if (callback != null) { callback.ellipsized(0, 0); } return text; } float ellipsiswid = p.measureText(sEllipsis); if (ellipsiswid > avail) { if (callback != null) { callback.ellipsized(0, len); } if (preserveLength) { char[] buf = obtain(len); for (int i = 0; i < len; i++) { buf[i] = '\uFEFF'; } String ret = new String(buf, 0, len); recycle(buf); return ret; } else { return ""; } } if (where == TruncateAt.START) { int fit = p.breakText(text, 0, len, false, avail - ellipsiswid, null); if (callback != null) { callback.ellipsized(0, len - fit); } if (preserveLength) { return blank(text, 0, len - fit); } else { return sEllipsis + text.toString().substring(len - fit, len); } } else if (where == TruncateAt.END) { int fit = p.breakText(text, 0, len, true, avail - ellipsiswid, null); if (callback != null) { callback.ellipsized(fit, len); } if (preserveLength) { return blank(text, fit, len); } else { return text.toString().substring(0, fit) + sEllipsis; } } else /* where == TruncateAt.MIDDLE */ { int right = p.breakText(text, 0, len, false, (avail - ellipsiswid) / 2, null); float used = p.measureText(text, len - right, len); int left = p.breakText(text, 0, len - right, true, avail - ellipsiswid - used, null); if (callback != null) { callback.ellipsized(left, len - right); } if (preserveLength) { return blank(text, left, len - right); } else { String s = text.toString(); return s.substring(0, left) + sEllipsis + s.substring(len - right, len); } } } // But do the Spanned cases by hand, because it's such a pain // to iterate the span transitions backwards and getTextWidths() // will give us the information we need. // getTextWidths() always writes into the start of the array, // so measure each span into the first half and then copy the // results into the second half to use later. float[] wid = new float[len * 2]; TextPaint temppaint = new TextPaint(); Spanned sp = (Spanned) text; int next; for (int i = 0; i < len; i = next) { next = sp.nextSpanTransition(i, len, MetricAffectingSpan.class); Styled.getTextWidths(p, temppaint, sp, i, next, wid, null); System.arraycopy(wid, 0, wid, len + i, next - i); } float sum = 0; for (int i = 0; i < len; i++) { sum += wid[len + i]; } if (sum <= avail) { if (callback != null) { callback.ellipsized(0, 0); } return text; } float ellipsiswid = p.measureText(sEllipsis); if (ellipsiswid > avail) { if (callback != null) { callback.ellipsized(0, len); } if (preserveLength) { char[] buf = obtain(len); for (int i = 0; i < len; i++) { buf[i] = '\uFEFF'; } SpannableString ss = new SpannableString(new String(buf, 0, len)); recycle(buf); copySpansFrom(sp, 0, len, Object.class, ss, 0); return ss; } else { return ""; } } if (where == TruncateAt.START) { sum = 0; int i; for (i = len; i >= 0; i--) { float w = wid[len + i - 1]; if (w + sum + ellipsiswid > avail) { break; } sum += w; } if (callback != null) { callback.ellipsized(0, i); } if (preserveLength) { SpannableString ss = new SpannableString(blank(text, 0, i)); copySpansFrom(sp, 0, len, Object.class, ss, 0); return ss; } else { SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis); out.insert(1, text, i, len); return out; } } else if (where == TruncateAt.END) { sum = 0; int i; for (i = 0; i < len; i++) { float w = wid[len + i]; if (w + sum + ellipsiswid > avail) { break; } sum += w; } if (callback != null) { callback.ellipsized(i, len); } if (preserveLength) { SpannableString ss = new SpannableString(blank(text, i, len)); copySpansFrom(sp, 0, len, Object.class, ss, 0); return ss; } else { SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis); out.insert(0, text, 0, i); return out; } } else /* where = TruncateAt.MIDDLE */ { float lsum = 0, rsum = 0; int left = 0, right = len; float ravail = (avail - ellipsiswid) / 2; for (right = len; right >= 0; right--) { float w = wid[len + right - 1]; if (w + rsum > ravail) { break; } rsum += w; } float lavail = avail - ellipsiswid - rsum; for (left = 0; left < right; left++) { float w = wid[len + left]; if (w + lsum > lavail) { break; } lsum += w; } if (callback != null) { callback.ellipsized(left, right); } if (preserveLength) { SpannableString ss = new SpannableString(blank(text, left, right)); copySpansFrom(sp, 0, len, Object.class, ss, 0); return ss; } else { SpannableStringBuilder out = new SpannableStringBuilder(sEllipsis); out.insert(0, text, 0, left); out.insert(out.length(), text, right, len); return out; } } } private static String blank(CharSequence source, int start, int end) { int len = source.length(); char[] buf = obtain(len); if (start != 0) { getChars(source, 0, start, buf, 0); } if (end != len) { getChars(source, end, len, buf, end); } if (start != end) { buf[start] = '\u2026'; for (int i = start + 1; i < end; i++) { buf[i] = '\uFEFF'; } } String ret = new String(buf, 0, len); recycle(buf); return ret; } /** * Converts a CharSequence of the comma-separated form "Andy, Bob, * Charles, David" that is too wide to fit into the specified width * into one like "Andy, Bob, 2 more". * * @param text the text to truncate * @param p the Paint with which to measure the text * @param avail the horizontal width available for the text * @param oneMore the string for "1 more" in the current locale * @param more the string for "%d more" in the current locale */ public static CharSequence commaEllipsize(CharSequence text, TextPaint p, float avail, String oneMore, String more) { int len = text.length(); char[] buf = new char[len]; TextUtils.getChars(text, 0, len, buf, 0); int commaCount = 0; for (int i = 0; i < len; i++) { if (buf[i] == ',') { commaCount++; } } float[] wid; if (text instanceof Spanned) { Spanned sp = (Spanned) text; TextPaint temppaint = new TextPaint(); wid = new float[len * 2]; int next; for (int i = 0; i < len; i = next) { next = sp.nextSpanTransition(i, len, MetricAffectingSpan.class); Styled.getTextWidths(p, temppaint, sp, i, next, wid, null); System.arraycopy(wid, 0, wid, len + i, next - i); } System.arraycopy(wid, len, wid, 0, len); } else { wid = new float[len]; p.getTextWidths(text, 0, len, wid); } int ok = 0; int okRemaining = commaCount + 1; String okFormat = ""; int w = 0; int count = 0; for (int i = 0; i < len; i++) { w += wid[i]; if (buf[i] == ',') { count++; int remaining = commaCount - count + 1; float moreWid; String format; if (remaining == 1) { format = " " + oneMore; } else { format = " " + String.format(more, remaining); } moreWid = p.measureText(format); if (w + moreWid <= avail) { ok = i + 1; okRemaining = remaining; okFormat = format; } } } if (w <= avail) { return text; } else { SpannableStringBuilder out = new SpannableStringBuilder(okFormat); out.insert(0, text, 0, ok); return out; } } /* package */ static char[] obtain(int len) { char[] buf; synchronized (sLock) { buf = sTemp; sTemp = null; } if (buf == null || buf.length < len) buf = new char[ArrayUtils.idealCharArraySize(len)]; return buf; } /* package */ static void recycle(char[] temp) { if (temp.length > 1000) return; synchronized (sLock) { sTemp = temp; } } /** * Html-encode the string. * @param s the string to be encoded * @return the encoded string */ public static String htmlEncode(String s) { StringBuilder sb = new StringBuilder(); char c; for (int i = 0; i < s.length(); i++) { c = s.charAt(i); switch (c) { case '<': sb.append("<"); //$NON-NLS-1$ break; case '>': sb.append(">"); //$NON-NLS-1$ break; case '&': sb.append("&"); //$NON-NLS-1$ break; case '\'': sb.append("'"); //$NON-NLS-1$ break; case '"': sb.append("""); //$NON-NLS-1$ break; default: sb.append(c); } } return sb.toString(); } /** * Returns a CharSequence concatenating the specified CharSequences, * retaining their spans if any. */ public static CharSequence concat(CharSequence... text) { if (text.length == 0) { return ""; } if (text.length == 1) { return text[0]; } boolean spanned = false; for (int i = 0; i < text.length; i++) { if (text[i] instanceof Spanned) { spanned = true; break; } } StringBuilder sb = new StringBuilder(); for (int i = 0; i < text.length; i++) { sb.append(text[i]); } if (!spanned) { return sb.toString(); } SpannableString ss = new SpannableString(sb); int off = 0; for (int i = 0; i < text.length; i++) { int len = text[i].length(); if (text[i] instanceof Spanned) { copySpansFrom((Spanned) text[i], 0, len, Object.class, ss, off); } off += len; } return new SpannedString(ss); } /** * Returns whether the given CharSequence contains any printable characters. */ public static boolean isGraphic(CharSequence str) { final int len = str.length(); for (int i=0; i<len; i++) { int gc = Character.getType(str.charAt(i)); if (gc != Character.CONTROL && gc != Character.FORMAT && gc != Character.SURROGATE && gc != Character.UNASSIGNED && gc != Character.LINE_SEPARATOR && gc != Character.PARAGRAPH_SEPARATOR && gc != Character.SPACE_SEPARATOR) { return true; } } return false; } /** * Returns whether this character is a printable character. */ public static boolean isGraphic(char c) { int gc = Character.getType(c); return gc != Character.CONTROL && gc != Character.FORMAT && gc != Character.SURROGATE && gc != Character.UNASSIGNED && gc != Character.LINE_SEPARATOR && gc != Character.PARAGRAPH_SEPARATOR && gc != Character.SPACE_SEPARATOR; } /** * Returns whether the given CharSequence contains only digits. */ public static boolean isDigitsOnly(CharSequence str) { final int len = str.length(); for (int i = 0; i < len; i++) { if (!Character.isDigit(str.charAt(i))) { return false; } } return true; } /** * @hide */ public static boolean isPrintableAscii(final char c) { final int asciiFirst = 0x20; final int asciiLast = 0x7E; // included return (asciiFirst <= c && c <= asciiLast) || c == '\r' || c == '\n'; } /** * @hide */ public static boolean isPrintableAsciiOnly(final CharSequence str) { final int len = str.length(); for (int i = 0; i < len; i++) { if (!isPrintableAscii(str.charAt(i))) { return false; } } return true; } /** * Capitalization mode for {@link #getCapsMode}: capitalize all * characters. This value is explicitly defined to be the same as * {@link InputType#TYPE_TEXT_FLAG_CAP_CHARACTERS}. */ public static final int CAP_MODE_CHARACTERS = InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS; /** * Capitalization mode for {@link #getCapsMode}: capitalize the first * character of all words. This value is explicitly defined to be the same as * {@link InputType#TYPE_TEXT_FLAG_CAP_WORDS}. */ public static final int CAP_MODE_WORDS = InputType.TYPE_TEXT_FLAG_CAP_WORDS; /** * Capitalization mode for {@link #getCapsMode}: capitalize the first * character of each sentence. This value is explicitly defined to be the same as * {@link InputType#TYPE_TEXT_FLAG_CAP_SENTENCES}. */ public static final int CAP_MODE_SENTENCES = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; /** * Determine what caps mode should be in effect at the current offset in * the text. Only the mode bits set in <var>reqModes will be * checked. Note that the caps mode flags here are explicitly defined * to match those in {@link InputType}. * * @param cs The text that should be checked for caps modes. * @param off Location in the text at which to check. * @param reqModes The modes to be checked: may be any combination of * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and * {@link #CAP_MODE_SENTENCES}. * * @return Returns the actual capitalization modes that can be in effect * at the current position, which is any combination of * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and * {@link #CAP_MODE_SENTENCES}. */ public static int getCapsMode(CharSequence cs, int off, int reqModes) { if (off < 0) { return 0; } int i; char c; int mode = 0; if ((reqModes&CAP_MODE_CHARACTERS) != 0) { mode |= CAP_MODE_CHARACTERS; } if ((reqModes&(CAP_MODE_WORDS|CAP_MODE_SENTENCES)) == 0) { return mode; } // Back over allowed opening punctuation. for (i = off; i > 0; i--) { c = cs.charAt(i - 1); if (c != '"' && c != '\'' && Character.getType(c) != Character.START_PUNCTUATION) { break; } } // Start of paragraph, with optional whitespace. int j = i; while (j > 0 && ((c = cs.charAt(j - 1)) == ' ' || c == '\t')) { j--; } if (j == 0 || cs.charAt(j - 1) == '\n') { return mode | CAP_MODE_WORDS; } // Or start of word if we are that style. if ((reqModes&CAP_MODE_SENTENCES) == 0) { if (i != j) mode |= CAP_MODE_WORDS; return mode; } // There must be a space if not the start of paragraph. if (i == j) { return mode; } // Back over allowed closing punctuation. for (; j > 0; j--) { c = cs.charAt(j - 1); if (c != '"' && c != '\'' && Character.getType(c) != Character.END_PUNCTUATION) { break; } } if (j > 0) { c = cs.charAt(j - 1); if (c == '.' || c == '?' || c == '!') { // Do not capitalize if the word ends with a period but // also contains a period, in which case it is an abbreviation. if (c == '.') { for (int k = j - 2; k >= 0; k--) { c = cs.charAt(k); if (c == '.') { return mode; } if (!Character.isLetter(c)) { break; } } } return mode | CAP_MODE_SENTENCES; } } return mode; } private static Object sLock = new Object(); private static char[] sTemp = null; }

Other Android examples (source code examples)

Here is a short list of links related to this Android TextUtils.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.