Java tutorial
/* * 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 android.annotation.UnsupportedAppUsage; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.text.style.ParagraphStyle; /** * A BoringLayout is a very simple Layout implementation for text that * fits on a single line and is all left-to-right characters. * You will probably never want to make one of these yourself; * if you do, be sure to call {@link #isBoring} first to make sure * the text meets the criteria. * <p>This class is used by widgets to control text layout. You should not need * to use this class directly unless you are implementing your own widget * or custom display object, in which case * you are encouraged to use a Layout instead of calling * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint) * Canvas.drawText()} directly.</p> */ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback { /** * Utility function to construct a BoringLayout instance. * * @param source the text to render * @param paint the default paint for the layout * @param outerWidth the wrapping width for the text * @param align whether to left, right, or center the text * @param spacingMult this value is no longer used by BoringLayout * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts */ public static BoringLayout make(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad) { return new BoringLayout(source, paint, outerWidth, align, spacingMult, spacingAdd, metrics, includePad); } /** * Utility function to construct a BoringLayout instance. * * @param source the text to render * @param paint the default paint for the layout * @param outerWidth the wrapping width for the text * @param align whether to left, right, or center the text * @param spacingmult this value is no longer used by BoringLayout * @param spacingadd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts * @param ellipsize whether to ellipsize the text if width of the text is longer than the * requested width * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is * not used, {@code outerWidth} is used instead */ public static BoringLayout make(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { return new BoringLayout(source, paint, outerWidth, align, spacingmult, spacingadd, metrics, includePad, ellipsize, ellipsizedWidth); } /** * Returns a BoringLayout for the specified text, potentially reusing * this one if it is already suitable. The caller must make sure that * no one is still using this Layout. * * @param source the text to render * @param paint the default paint for the layout * @param outerwidth the wrapping width for the text * @param align whether to left, right, or center the text * @param spacingMult this value is no longer used by BoringLayout * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts */ public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad) { replaceWith(source, paint, outerwidth, align, spacingMult, spacingAdd); mEllipsizedWidth = outerwidth; mEllipsizedStart = 0; mEllipsizedCount = 0; init(source, paint, align, metrics, includePad, true); return this; } /** * Returns a BoringLayout for the specified text, potentially reusing * this one if it is already suitable. The caller must make sure that * no one is still using this Layout. * * @param source the text to render * @param paint the default paint for the layout * @param outerWidth the wrapping width for the text * @param align whether to left, right, or center the text * @param spacingMult this value is no longer used by BoringLayout * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts * @param ellipsize whether to ellipsize the text if width of the text is longer than the * requested width * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is * not used, {@code outerwidth} is used instead */ public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { boolean trust; if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) { replaceWith(source, paint, outerWidth, align, spacingMult, spacingAdd); mEllipsizedWidth = outerWidth; mEllipsizedStart = 0; mEllipsizedCount = 0; trust = true; } else { replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this), paint, outerWidth, align, spacingMult, spacingAdd); mEllipsizedWidth = ellipsizedWidth; trust = false; } init(getText(), paint, align, metrics, includePad, trust); return this; } /** * @param source the text to render * @param paint the default paint for the layout * @param outerwidth the wrapping width for the text * @param align whether to left, right, or center the text * @param spacingMult this value is no longer used by BoringLayout * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts */ public BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad) { super(source, paint, outerwidth, align, spacingMult, spacingAdd); mEllipsizedWidth = outerwidth; mEllipsizedStart = 0; mEllipsizedCount = 0; init(source, paint, align, metrics, includePad, true); } /** * * @param source the text to render * @param paint the default paint for the layout * @param outerWidth the wrapping width for the text * @param align whether to left, right, or center the text * @param spacingMult this value is no longer used by BoringLayout * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is * needed to avoid clipping in some scripts * @param ellipsize whether to ellipsize the text if width of the text is longer than the * requested {@code outerwidth} * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is * not used, {@code outerwidth} is used instead */ public BoringLayout(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { /* * It is silly to have to call super() and then replaceWith(), * but we can't use "this" for the callback until the call to * super() finishes. */ super(source, paint, outerWidth, align, spacingMult, spacingAdd); boolean trust; if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) { mEllipsizedWidth = outerWidth; mEllipsizedStart = 0; mEllipsizedCount = 0; trust = true; } else { replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this), paint, outerWidth, align, spacingMult, spacingAdd); mEllipsizedWidth = ellipsizedWidth; trust = false; } init(getText(), paint, align, metrics, includePad, trust); } /* package */ void init(CharSequence source, TextPaint paint, Alignment align, BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth) { int spacing; if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) { mDirect = source.toString(); } else { mDirect = null; } mPaint = paint; if (includePad) { spacing = metrics.bottom - metrics.top; mDesc = metrics.bottom; } else { spacing = metrics.descent - metrics.ascent; mDesc = metrics.descent; } mBottom = spacing; if (trustWidth) { mMax = metrics.width; } else { /* * If we have ellipsized, we have to actually calculate the * width because the width that was passed in was for the * full text, not the ellipsized form. */ TextLine line = TextLine.obtain(); line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null); mMax = (int) Math.ceil(line.metrics(null)); TextLine.recycle(line); } if (includePad) { mTopPadding = metrics.top - metrics.ascent; mBottomPadding = metrics.bottom - metrics.descent; } } /** * Returns null if not boring; the width, ascent, and descent if boring. */ public static Metrics isBoring(CharSequence text, TextPaint paint) { return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null); } /** * Returns null if not boring; the width, ascent, and descent in the * provided Metrics object (or a new one if the provided one was null) * if boring. */ public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) { return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics); } /** * Returns true if the text contains any RTL characters, bidi format characters, or surrogate * code units. */ private static boolean hasAnyInterestingChars(CharSequence text, int textLength) { final int MAX_BUF_LEN = 500; final char[] buffer = TextUtils.obtain(MAX_BUF_LEN); try { for (int start = 0; start < textLength; start += MAX_BUF_LEN) { final int end = Math.min(start + MAX_BUF_LEN, textLength); // No need to worry about getting half codepoints, since we consider surrogate code // units "interesting" as soon we see one. TextUtils.getChars(text, start, end, buffer, 0); final int len = end - start; for (int i = 0; i < len; i++) { final char c = buffer[i]; if (c == '\n' || c == '\t' || TextUtils.couldAffectRtl(c)) { return true; } } } return false; } finally { TextUtils.recycle(buffer); } } /** * Returns null if not boring; the width, ascent, and descent in the * provided Metrics object (or a new one if the provided one was null) * if boring. * @hide */ @UnsupportedAppUsage public static Metrics isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir, Metrics metrics) { final int textLength = text.length(); if (hasAnyInterestingChars(text, textLength)) { return null; // There are some interesting characters. Not boring. } if (textDir != null && textDir.isRtl(text, 0, textLength)) { return null; // The heuristic considers the whole text RTL. Not boring. } if (text instanceof Spanned) { Spanned sp = (Spanned) text; Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class); if (styles.length > 0) { return null; // There are some PargraphStyle spans. Not boring. } } Metrics fm = metrics; if (fm == null) { fm = new Metrics(); } else { fm.reset(); } TextLine line = TextLine.obtain(); line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null); fm.width = (int) Math.ceil(line.metrics(fm)); TextLine.recycle(line); return fm; } @Override public int getHeight() { return mBottom; } @Override public int getLineCount() { return 1; } @Override public int getLineTop(int line) { if (line == 0) return 0; else return mBottom; } @Override public int getLineDescent(int line) { return mDesc; } @Override public int getLineStart(int line) { if (line == 0) return 0; else return getText().length(); } @Override public int getParagraphDirection(int line) { return DIR_LEFT_TO_RIGHT; } @Override public boolean getLineContainsTab(int line) { return false; } @Override public float getLineMax(int line) { return mMax; } @Override public float getLineWidth(int line) { return (line == 0 ? mMax : 0); } @Override public final Directions getLineDirections(int line) { return Layout.DIRS_ALL_LEFT_TO_RIGHT; } @Override public int getTopPadding() { return mTopPadding; } @Override public int getBottomPadding() { return mBottomPadding; } @Override public int getEllipsisCount(int line) { return mEllipsizedCount; } @Override public int getEllipsisStart(int line) { return mEllipsizedStart; } @Override public int getEllipsizedWidth() { return mEllipsizedWidth; } // Override draw so it will be faster. @Override public void draw(Canvas c, Path highlight, Paint highlightpaint, int cursorOffset) { if (mDirect != null && highlight == null) { c.drawText(mDirect, 0, mBottom - mDesc, mPaint); } else { super.draw(c, highlight, highlightpaint, cursorOffset); } } /** * Callback for the ellipsizer to report what region it ellipsized. */ public void ellipsized(int start, int end) { mEllipsizedStart = start; mEllipsizedCount = end - start; } private String mDirect; private Paint mPaint; /* package */ int mBottom, mDesc; // for Direct private int mTopPadding, mBottomPadding; private float mMax; private int mEllipsizedWidth, mEllipsizedStart, mEllipsizedCount; public static class Metrics extends Paint.FontMetricsInt { public int width; @Override public String toString() { return super.toString() + " width=" + width; } private void reset() { top = 0; bottom = 0; ascent = 0; descent = 0; width = 0; leading = 0; } } }