下面列出了怎么用android.text.Layout的API类实例代码及写法,或者点击链接到github查看源代码。
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top,
int baseline, int bottom, CharSequence text, int start, int end,
boolean first, Layout l) {
if (((Spanned) text).getSpanStart(this) == start) {
Paint.Style style = p.getStyle();
p.setStyle(Paint.Style.FILL);
if (mNumber != -1) {
c.drawText(mNumber + ".", x + dir, baseline, p);
} else {
c.drawText("\u2022", x + dir, baseline, p);
}
p.setStyle(style);
}
}
public void setLyricFile(File file, String charsetName) {
if (file != null && file.exists()) {
try {
setupLyricResource(new FileInputStream(file), charsetName);
for (int i = 0; i < mLyricInfo.songLines.size(); i++) {
StaticLayout staticLayout = new StaticLayout(mLyricInfo.songLines.get(i).content, mTextPaint,
(int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_MAX_LENGTH),
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
if (staticLayout.getLineCount() > 1) {
mEnableLineFeed = true;
mExtraHeight = mExtraHeight + (staticLayout.getLineCount() - 1) * mTextHeight;
}
mLineFeedRecord.add(i, mExtraHeight);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} else {
invalidateView();
}
}
public void mount(
CharSequence text,
Layout layout,
float layoutTranslationY,
ColorStateList colorStateList,
int userColor,
int highlightColor,
ClickableSpan[] clickableSpans) {
mount(
text,
layout,
0,
false,
null,
userColor,
highlightColor,
clickableSpans,
null,
null,
null,
-1,
-1,
0f,
null);
}
/**
* 绘制提示文字
*/
private void drawText(Canvas canvas, Rect frame) {
TextPaint textPaint = new TextPaint();
textPaint.setAntiAlias(true);
textPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(scannerOptions.getTipTextColor());
textPaint.setTextSize(tipTextSize);
float x = frame.left;//文字开始位置
//根据 drawTextGravityBottom 文字在扫描框上方还是上文,默认下方
float y = !scannerOptions.isTipTextToFrameTop() ? frame.bottom + tipTextMargin
: frame.top - tipTextMargin;
StaticLayout staticLayout = new StaticLayout(scannerOptions.getTipText(), textPaint, frame.width()
, Layout.Alignment.ALIGN_CENTER, 1.0f, 0, false);
canvas.save();
canvas.translate(x, y);
staticLayout.draw(canvas);
canvas.restore();
}
private <T> ArrayList<T> findSpansToClick(Layout layout, Spanned spanned, Class<T> type, Point layoutPosition) {
ArrayList<T> result = new ArrayList<>();
// Find spans around touch point for better click treatment
for (int[] deltaAttempt : deltaAttempts) {
int startX = layoutPosition.x + deltaAttempt[0], startY = layoutPosition.y + deltaAttempt[1];
T[] spans = findSpansToClickSingle(layout, spanned, type, startX, startY);
if (spans != null) {
for (T span : spans) {
if (span != null) {
result.add(span);
}
}
}
}
return result;
}
/**
* 生成每行文字对应的行号,如果行首为换行符则需要显示行号
*
* @return
*/
public Map<Integer, String> getLineNumbers() {
Map<Integer, String> maps = new HashMap<>();
Layout layout = getLayout();
if (layout == null) {
return maps;
}
int lineNumber = 1;
maps.put(0, Integer.toString(lineNumber));
int lineCount = getLineCount();
mMaxLineNumber = 1;
for (int i = 1; i < lineCount; i++) {
int charPos = layout.getLineStart(i);
if (getText().charAt(charPos - 1) == '\n') {
lineNumber++;
maps.put(i, Integer.toString(lineNumber));
mMaxLineNumber = lineNumber;
}
}
return maps;
}
private void initOffsetHeight() {
int paddingTop;
int paddingBottom;
int mHeight;
int mLayoutHeight;
//获得内容面板
Layout mLayout = getLayout();
if (mLayout == null) return;
//获得内容面板的高度
mLayoutHeight = mLayout.getHeight();
//获取上内边距
paddingTop = getTotalPaddingTop();
//获取下内边距
paddingBottom = getTotalPaddingBottom();
//获得控件的实际高度
mHeight = getMeasuredHeight();
//计算滑动距离的边界
mOffsetHeight = mLayoutHeight + paddingTop + paddingBottom - mHeight;
if (mOffsetHeight <= 0) {
scrollTo(0, 0);
}
}
@Override
public void drawLeadingMargin(@NonNull Canvas c, @NonNull Paint p, int x, int dir,
int top, int baseline, int bottom,
@NonNull CharSequence text, int start, int end,
boolean first, @NonNull Layout layout) {
Paint.Style style = p.getStyle();
int color = p.getColor();
p.setStyle(Paint.Style.FILL);
p.setColor(mColor);
c.drawRect(x, top, x + dir * mStripeWidth, bottom, p);
p.setStyle(style);
p.setColor(color);
}
private int computeReleaseOffset(int space) {
// This is simpler than it looks: we must pass whole lines.
String logPrefix = logPrefix();
Layout layout = mView.getLayout();
int count = layout.getLineCount();
int removed = 0;
LOG.v(logPrefix, "computeReleaseOffset:", "space:", space, "lineCount:", count);
int removeLine = 0;
for (int i = count - 1; i >= 0; i--) {
layout.getLineBounds(i, mTmp);
removed += mTmp.height();
if (removed >= space) {
removeLine = i;
break;
}
}
// We have to remove line i and all subsequent lines.
LOG.i(logPrefix, "computeReleaseOffset:", "removing line:", removeLine, "and subsequent.");
return Math.max(layout.getOffsetForHorizontal(removeLine, 0) - 1, 0);
}
/**
* Calculate the right boundary for this run (harder than it sounds). As we're a letter ahead,
* need to grab either current letter start or the end of the previous line. Also need to
* consider maxLines case, which inserts ellipses at the overflow point – don't include these.
*/
private int getRunRight(
Layout unrestrictedLayout, Layout maxLinesLayout, int currentLine, int index,
int line, boolean withinMax, boolean isMaxEllipsis, boolean isLastChar) {
int runRight;
if (line != currentLine || isLastChar) {
if (isMaxEllipsis) {
runRight = (int) maxLinesLayout.getPrimaryHorizontal(index);
} else {
runRight = (int) unrestrictedLayout.getLineMax(currentLine);
}
} else {
if (withinMax) {
runRight = (int) maxLinesLayout.getPrimaryHorizontal(index);
} else {
runRight = (int) unrestrictedLayout.getPrimaryHorizontal(index);
}
}
return runRight;
}
private void drawIndicatorsTextAbove(Canvas canvas, String text, TextPaint paintText, float x, float y, Layout.Alignment alignment) {
final float textHeight = calculateTextMultilineHeight(text, paintText);
y -= textHeight;
final int width = (int) paintText.measureText(text);
if (x >= getWidth() - settings.paddingCorners) {
x = (getWidth() - width - settings.paddingCorners / 2f);
} else if (x <= 0) {
x = width / 2f;
} else {
x = (x - width / 2f);
}
if (x < 0) {
x = 0;
}
if (x + width > getWidth()) {
x = getWidth() - width;
}
drawText(canvas, text, x, y, paintText, alignment);
}
private int measureStepsHeight() {
textLayouts = new StaticLayout[steps.size()];
textPaint.setTextSize(textSize);
int max = 0;
for (int i = 0; i < steps.size(); i++) {
String text = steps.get(i);
Layout.Alignment alignment =
isRtl() ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_NORMAL;
textLayouts[i] = new StaticLayout(
text,
textPaint,
getMeasuredWidth() / steps.size(),
alignment,
1,
0,
true
);
int height = textLayouts[i].getHeight();
max = Math.max(height, max);
}
return max;
}
@Override
public void applyToSelection(RTEditText editor, Selection selectedParagraphs, Layout.Alignment alignment) {
final Spannable str = editor.getText();
mSpans2Process.clear();
for (Paragraph paragraph : editor.getParagraphs()) {
// find existing AlignmentSpan and add them to mSpans2Process to be removed
List<RTSpan<Layout.Alignment>> existingSpans = getSpans(str, paragraph, SpanCollectMode.SPAN_FLAGS);
mSpans2Process.removeSpans(existingSpans, paragraph);
// if the paragraph is selected then we sure have an alignment
boolean hasExistingSpans = !existingSpans.isEmpty();
Alignment newAlignment = paragraph.isSelected(selectedParagraphs) ? alignment :
hasExistingSpans ? existingSpans.get(0).getValue() : null;
if (newAlignment != null) {
boolean isRTL = Helper.isRTL(str, paragraph.start(), paragraph.end());
AlignmentSpan alignmentSpan = new AlignmentSpan(newAlignment, isRTL);
mSpans2Process.addSpan(alignmentSpan, paragraph);
}
}
// add or remove spans
mSpans2Process.process(str);
}
Layout createTextLayout(String text) {
int textWidth = (int) textPaint.measureText(text);
int unitTextSize = (int) (textPaint.getTextSize() / 2);
spannableString.clear();
spannableString.clearSpans();
spannableString.append(text);
if (textAppearanceSpan != null) {
spannableString.setSpan(textAppearanceSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
int hrIndex = text.indexOf("h");
int minIndex = text.indexOf("m");
int secIndex = text.indexOf("s");
spannableString.setSpan(new AbsoluteSizeSpan(unitTextSize), hrIndex, hrIndex + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableString.setSpan(new AbsoluteSizeSpan(unitTextSize), minIndex, minIndex + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableString.setSpan(new AbsoluteSizeSpan(unitTextSize), secIndex, secIndex + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return new StaticLayout(spannableString, textPaint, textWidth, Layout.Alignment.ALIGN_CENTER, 0, 0, true);
}
private static String getTextDirection(Spanned text, int start, int end) {
/*final int len = end - start;
final byte[] levels = ArrayUtils.newUnpaddedByteArray(len);
final char[] buffer = TextUtils.obtain(len);
TextUtils.getChars(text, start, end, buffer, 0);
int paraDir = AndroidBidi.bidi(Layout.DIR_REQUEST_DEFAULT_LTR, buffer, levels, len,
false *//* no info *//*);*/
int paraDir = Layout.DIR_LEFT_TO_RIGHT;
switch (paraDir) {
case Layout.DIR_RIGHT_TO_LEFT:
return " dir=\"rtl\"";
case Layout.DIR_LEFT_TO_RIGHT:
default:
return " dir=\"ltr\"";
}
}
private int getScrollBoundsRight(TextView widget) {
final Layout layout = widget.getLayout();
final int topLine = getTopLine(widget);
final int bottomLine = getBottomLine(widget);
if (topLine > bottomLine) {
return 0;
}
int right = Integer.MIN_VALUE;
for (int line = topLine; line <= bottomLine; line++) {
final int lineRight = (int) Math.ceil(layout.getLineRight(line));
if (lineRight > right) {
right = lineRight;
}
}
return right;
}
/** @hide */
public boolean isTitleTruncated() {
if (mTitleTextView == null) {
return false;
}
final Layout titleLayout = mTitleTextView.getLayout();
if (titleLayout == null) {
return false;
}
final int lineCount = titleLayout.getLineCount();
for (int i = 0; i < lineCount; i++) {
if (titleLayout.getEllipsisCount(i) > 0) {
return true;
}
}
return false;
}
/**
* Sets the text size of a clone of the view's {@link TextPaint} object
* and uses a {@link StaticLayout} instance to measure the height of the text.
*
* @param source
* @param availableWidthPixels
* @param textSizePixels
* @return the height of the text when placed in a view
* with the specified width
* and when the text has the specified size.
*/
private int getTextHeightPixels(
CharSequence source,
int availableWidthPixels,
float textSizePixels) {
// Make a copy of the original TextPaint object
// since the object gets modified while measuring
// (see also the docs for TextView.getPaint()
// which states to access it read-only)
TextPaint textPaintCopy = new TextPaint(getPaint());
textPaintCopy.setTextSize(textSizePixels);
// Measure using a StaticLayout instance
StaticLayout staticLayout = new StaticLayout(
source,
textPaintCopy,
availableWidthPixels,
Layout.Alignment.ALIGN_NORMAL,
mLineSpacingMultiplier,
mLineSpacingExtra,
true);
return staticLayout.getHeight();
}
private void configureTextLayouts(final int availableWidth) {
if (!textLayoutsConfigured) {
final int totalNeededPadding = getPaddingLeft() + getPaddingRight();
// Create new static layout only if needed!
if ((titleLayout.getWidth() + totalNeededPadding) > availableWidth) {
this.titleLayout = new StaticLayout(title,
titlePaint,
availableWidth - totalNeededPadding,
Layout.Alignment.ALIGN_NORMAL,
1.15f, 0, false);
}
// Create new static layout only if needed!
if ((subtitleLayout.getWidth() + totalNeededPadding) > availableWidth) {
this.subtitleLayout = new StaticLayout(subtitle,
subtitlePaint,
availableWidth - totalNeededPadding,
Layout.Alignment.ALIGN_NORMAL,
1.15f, 0, false);
}
textLayoutsConfigured = true;
}
}
/**
* Gets the lineInfo from the index of the letter in the text
*/
public static int getLineFromIndex(int index, int lineCount, Layout layout) {
int line;
int currentIndex = 0;
for (line = 0; line < lineCount; line++) {
currentIndex += layout.getLineEnd(line) - layout.getLineStart(line);
if (currentIndex > index) {
break;
}
}
return line;
}
/**
* Find the number of lines of text which must be shown in order to display the character at
* a given index.
*/
private int getLineForIndex(int index) {
Layout layout = getLayout();
int endLine = 0;
while (endLine < layout.getLineCount() && layout.getLineEnd(endLine) < index) {
endLine++;
}
// Since endLine is an index, add 1 to get the number of lines.
return endLine + 1;
}
public static void drawMultilineText(Canvas c, String text,
float x, float y,
TextPaint paint,
FSize constrainedToSize,
MPPointF anchor, float angleDegrees) {
StaticLayout textLayout = new StaticLayout(
text, 0, text.length(),
paint,
(int) Math.max(Math.ceil(constrainedToSize.width), 1.f),
Layout.Alignment.ALIGN_NORMAL, 1.f, 0.f, false);
drawMultilineText(c, textLayout, x, y, paint, anchor, angleDegrees);
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
// Let the parent or grandparent of TextView to handles click aciton.
// Otherwise click effect like ripple will not work, and if touch area
// do not contain a url, the TextView will still get MotionEvent.
// onTouchEven must be called with MotionEvent.ACTION_DOWN for each touch
// action on it, so we analyze touched url here.
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mCurrentSpan = null;
if (getText() instanceof Spanned) {
// Get this code from android.text.method.LinkMovementMethod.
// Work fine !
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
Object[] spans = ((Spanned)getText()).getSpans(off, off, Object.class);
for (Object span : spans) {
if (Utilities.contain(SUPPORTED_SPAN_TYPE, span.getClass())) {
mCurrentSpan = span;
break;
}
}
}
}
return super.onTouchEvent(event);
}
private static int getLineCount(CharSequence text, TextPaint paint, float size, float width,
DisplayMetrics displayMetrics) {
paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, size,
displayMetrics));
StaticLayout layout = new StaticLayout(text, paint, (int) width,
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true);
return layout.getLineCount();
}
@Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
if ((level == 1 || level == 2)
&& LeadingMarginUtils.selfEnd(end, text, this)) {
paint.set(p);
theme.applyHeadingBreakStyle(paint);
final float height = paint.getStrokeWidth();
if (height > .0F) {
final int b = (int) (bottom - height + .5F);
final int left;
final int right;
if (dir > 0) {
left = x;
right = c.getWidth();
} else {
left = x - c.getWidth();
right = x;
}
rect.set(left, b, right, bottom);
c.drawRect(rect, paint);
}
}
}
@Override
public void drawBackground(Canvas canvas, Paint p, int left, int right, int top, int baseline,
int bottom, CharSequence text, int start, int end, int lnum) {
int lineNum = textView.getLineCount();
for (int i = 0; i < lineNum; i++) {
Layout layout = textView.getLayout();
canvas.drawLine(layout.getLineLeft(i), layout.getLineBottom(i) - spacingExtra + offsetY,
layout.getLineRight(i), layout.getLineBottom(i) - spacingExtra + offsetY,
this.paint);
}
}
public StaticLayout getErrorLayout(int width) {
if (TextUtils.isEmpty(errorText)) {
return null;
} else {
return new StaticLayout(errorText, errorPaint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
}
}
@Override
protected void onDraw(Canvas canvas) {
final Layout layout = getLayout();
if (layout != null) {
draw(this, canvas, layout);
}
super.onDraw(canvas);
}
public static void enableClicksOnSpans(TextView textView) {
final SpanClickHandler helper = new SpanClickHandler(textView, null);
textView.setOnTouchListener((view, event) -> {
final TextView textView1 = (TextView) view;
final Layout layout = textView1.getLayout();
if (layout != null) {
helper.layout = layout;
helper.left = textView1.getTotalPaddingLeft() + textView1.getScrollX();
helper.top = textView1.getTotalPaddingTop() + textView1.getScrollY();
return helper.handleTouchEvent(event);
}
return false;
});
}
@Override
public void handleTag(final boolean opening, final String tag, Editable output, final XMLReader xmlReader) {
if (opening) {
// opening tag
if (HtmlTextView.DEBUG) {
Log.d(HtmlTextView.TAG, "opening, output: " + output.toString());
}
if (tag.equalsIgnoreCase("ul") || tag.equalsIgnoreCase("ol") || tag.equalsIgnoreCase("dd")) {
mListParents.add(tag);
mListItemCount = 0;
} else if (tag.equalsIgnoreCase("code")) {
start(output, new Code());
} else if (tag.equalsIgnoreCase("center")) {
start(output, new Center());
}
} else {
// closing tag
if (HtmlTextView.DEBUG) {
Log.d(HtmlTextView.TAG, "closing, output: " + output.toString());
}
if (tag.equalsIgnoreCase("ul") || tag.equalsIgnoreCase("ol") || tag.equalsIgnoreCase("dd")) {
mListParents.remove(tag);
mListItemCount = 0;
} else if (tag.equalsIgnoreCase("li")) {
handleListTag(output);
} else if (tag.equalsIgnoreCase("code")) {
end(output, Code.class, new TypefaceSpan("monospace"), false);
} else if (tag.equalsIgnoreCase("center")) {
end(output, Center.class, new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), true);
}
}
}