mirror of
https://github.com/material-components/material-components-android.git
synced 2026-01-16 18:01:42 +08:00
286 lines
8.7 KiB
Java
286 lines
8.7 KiB
Java
/*
|
|
* Copyright (C) 2019 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 com.google.android.material.internal;
|
|
|
|
import static androidx.core.util.Preconditions.checkNotNull;
|
|
|
|
import android.os.Build;
|
|
import android.os.Build.VERSION_CODES;
|
|
import androidx.annotation.IntRange;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.RestrictTo;
|
|
import androidx.annotation.RestrictTo.Scope;
|
|
import android.text.Layout;
|
|
import android.text.Layout.Alignment;
|
|
import android.text.StaticLayout;
|
|
import android.text.TextDirectionHeuristic;
|
|
import android.text.TextDirectionHeuristics;
|
|
import android.text.TextPaint;
|
|
import android.text.TextUtils;
|
|
import java.lang.reflect.Constructor;
|
|
|
|
/**
|
|
* Class to create StaticLayout using StaticLayout.Builder on API23+ and a hidden StaticLayout
|
|
* constructor before that.
|
|
*
|
|
* <p>Usage:
|
|
*
|
|
* <pre>{@code
|
|
* StaticLayout staticLayout =
|
|
* StaticLayoutBuilderCompat.obtain("Lorem Ipsum", new TextPaint(), 100)
|
|
* .setAlignment(Alignment.ALIGN_NORMAL)
|
|
* .build();
|
|
* }</pre>
|
|
*
|
|
* @hide
|
|
*/
|
|
@RestrictTo(Scope.LIBRARY_GROUP)
|
|
final class StaticLayoutBuilderCompat {
|
|
|
|
private static final String TEXT_DIR_CLASS = "android.text.TextDirectionHeuristic";
|
|
private static final String TEXT_DIRS_CLASS = "android.text.TextDirectionHeuristics";
|
|
private static final String TEXT_DIR_FIRSTSTRONG_LTR = "FIRSTSTRONG_LTR";
|
|
|
|
private static boolean initialized;
|
|
|
|
@Nullable private static Constructor<StaticLayout> constructor;
|
|
@Nullable private static Object textDirection;
|
|
|
|
private final CharSequence source;
|
|
private final TextPaint paint;
|
|
private final int width;
|
|
private int start;
|
|
private int end;
|
|
|
|
private Alignment alignment;
|
|
private int maxLines;
|
|
private boolean includePad;
|
|
@Nullable private TextUtils.TruncateAt ellipsize;
|
|
|
|
private StaticLayoutBuilderCompat(CharSequence source, TextPaint paint, int width) {
|
|
this.source = source;
|
|
this.paint = paint;
|
|
this.width = width;
|
|
this.start = 0;
|
|
this.end = source.length();
|
|
this.alignment = Alignment.ALIGN_NORMAL;
|
|
this.maxLines = Integer.MAX_VALUE;
|
|
this.includePad = true;
|
|
this.ellipsize = null;
|
|
}
|
|
|
|
/**
|
|
* Obtain a builder for constructing StaticLayout objects.
|
|
*
|
|
* @param source The text to be laid out, optionally with spans
|
|
* @param paint The base paint used for layout
|
|
* @param width The width in pixels
|
|
* @return a builder object used for constructing the StaticLayout
|
|
*/
|
|
@NonNull
|
|
public static StaticLayoutBuilderCompat obtain(
|
|
@NonNull CharSequence source, @NonNull TextPaint paint, @IntRange(from = 0) int width) {
|
|
return new StaticLayoutBuilderCompat(source, paint, width);
|
|
}
|
|
|
|
/**
|
|
* Set the alignment. The default is {@link Layout.Alignment#ALIGN_NORMAL}.
|
|
*
|
|
* @param alignment Alignment for the resulting {@link StaticLayout}
|
|
* @return this builder, useful for chaining
|
|
*/
|
|
@NonNull
|
|
public StaticLayoutBuilderCompat setAlignment(@NonNull Alignment alignment) {
|
|
this.alignment = alignment;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set whether to include extra space beyond font ascent and descent (which is needed to avoid
|
|
* clipping in some languages, such as Arabic and Kannada). The default is {@code true}.
|
|
*
|
|
* @param includePad whether to include padding
|
|
* @return this builder, useful for chaining
|
|
* @see android.widget.TextView#setIncludeFontPadding
|
|
*/
|
|
@NonNull
|
|
public StaticLayoutBuilderCompat setIncludePad(boolean includePad) {
|
|
this.includePad = includePad;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the index of the start of the text
|
|
*
|
|
* @return this builder, useful for chaining
|
|
*/
|
|
@NonNull
|
|
public StaticLayoutBuilderCompat setStart(@IntRange(from = 0) int start) {
|
|
this.start = start;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the index + 1 of the end of the text
|
|
*
|
|
* @return this builder, useful for chaining
|
|
* @see android.widget.TextView#setIncludeFontPadding
|
|
*/
|
|
@NonNull
|
|
public StaticLayoutBuilderCompat setEnd(@IntRange(from = 0) int end) {
|
|
this.end = end;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set maximum number of lines. This is particularly useful in the case of ellipsizing, where it
|
|
* changes the layout of the last line. The default is unlimited.
|
|
*
|
|
* @param maxLines maximum number of lines in the layout
|
|
* @return this builder, useful for chaining
|
|
* @see android.widget.TextView#setMaxLines
|
|
*/
|
|
@NonNull
|
|
public StaticLayoutBuilderCompat setMaxLines(@IntRange(from = 0) int maxLines) {
|
|
this.maxLines = maxLines;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set ellipsizing on the layout. Causes words that are longer than the view is wide, or exceeding
|
|
* the number of lines (see #setMaxLines).
|
|
*
|
|
* @param ellipsize type of ellipsis behavior
|
|
* @return this builder, useful for chaining
|
|
* @see android.widget.TextView#setEllipsize
|
|
*/
|
|
@NonNull
|
|
public StaticLayoutBuilderCompat setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
|
|
this.ellipsize = ellipsize;
|
|
return this;
|
|
}
|
|
|
|
/** A method that allows to create a StaticLayout with maxLines on all supported API levels. */
|
|
public StaticLayout build() throws StaticLayoutBuilderCompatException {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
// Marshmallow introduced StaticLayout.Builder which allows us not to use
|
|
// the hidden constructor.
|
|
StaticLayout.Builder builder = StaticLayout.Builder.obtain(source, start, end, paint, width);
|
|
builder.setAlignment(alignment);
|
|
builder.setIncludePad(includePad);
|
|
if (ellipsize != null) {
|
|
builder.setEllipsize(ellipsize);
|
|
}
|
|
builder.setMaxLines(maxLines);
|
|
return builder.build();
|
|
}
|
|
|
|
createConstructorWithReflection();
|
|
|
|
// Use the hidden constructor on older API levels.
|
|
try {
|
|
return checkNotNull(constructor)
|
|
.newInstance(
|
|
source,
|
|
start,
|
|
end,
|
|
paint,
|
|
width,
|
|
alignment,
|
|
checkNotNull(textDirection),
|
|
1.0f,
|
|
0.0f,
|
|
includePad,
|
|
ellipsize,
|
|
width,
|
|
maxLines);
|
|
} catch (Exception cause) {
|
|
throw new StaticLayoutBuilderCompatException(cause);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* set constructor to this hidden {@link StaticLayout constructor.}
|
|
*
|
|
* <pre>{@code
|
|
* StaticLayout(
|
|
* CharSequence source,
|
|
* int bufstart,
|
|
* int bufend,
|
|
* TextPaint paint,
|
|
* int outerwidth,
|
|
* Alignment align,
|
|
* TextDirectionHeuristic textDir,
|
|
* float spacingmult,
|
|
* float spacingadd,
|
|
* boolean includepad,
|
|
* TextUtils.TruncateAt ellipsize,
|
|
* int ellipsizedWidth,
|
|
* int maxLines)
|
|
* }</pre>
|
|
*/
|
|
private static void createConstructorWithReflection() throws StaticLayoutBuilderCompatException {
|
|
if (initialized) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final Class<?> textDirClass;
|
|
if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
|
|
textDirClass = TextDirectionHeuristic.class;
|
|
textDirection = TextDirectionHeuristics.FIRSTSTRONG_LTR;
|
|
} else {
|
|
ClassLoader loader = StaticLayoutBuilderCompat.class.getClassLoader();
|
|
textDirClass = loader.loadClass(TEXT_DIR_CLASS);
|
|
Class<?> textDirsClass = loader.loadClass(TEXT_DIRS_CLASS);
|
|
textDirection = textDirsClass.getField(TEXT_DIR_FIRSTSTRONG_LTR).get(textDirsClass);
|
|
}
|
|
|
|
final Class<?>[] signature =
|
|
new Class<?>[] {
|
|
CharSequence.class,
|
|
int.class,
|
|
int.class,
|
|
TextPaint.class,
|
|
int.class,
|
|
Alignment.class,
|
|
textDirClass,
|
|
float.class,
|
|
float.class,
|
|
boolean.class,
|
|
TextUtils.TruncateAt.class,
|
|
int.class,
|
|
int.class
|
|
};
|
|
|
|
constructor = StaticLayout.class.getDeclaredConstructor(signature);
|
|
constructor.setAccessible(true);
|
|
initialized = true;
|
|
} catch (Exception cause) {
|
|
throw new StaticLayoutBuilderCompatException(cause);
|
|
}
|
|
}
|
|
|
|
static class StaticLayoutBuilderCompatException extends Exception {
|
|
|
|
StaticLayoutBuilderCompatException(Throwable cause) {
|
|
super("Error thrown initializing StaticLayout", cause);
|
|
}
|
|
}
|
|
}
|