The story begins with a product requirement to make a little red book text folding function, and that leads to the next series of things. But after the implementation, I also know a lot about TextView interception, the specific effects are as follows:
To sum up a few points to pay attention to when implementing:
- According to “… After a certain number of lines are intercepted, it is displayed directly at the end of the last line
- Fold up appears on the next line of the entire text and is right-aligned
- Animation effects that unfold and collapse
If the generalization isn’t perfect, please also point out that you can skip to the end of this article and look at the ExpandableTextView code
Text interception
Referring to a number of articles, many implementations cut the largest line of text and add a button to the next line of text. This doesn’t work, so you can PASS it.
TextView set Android :maxLines and ellipSize to End, but… Replace with… Unfortunately, the system does not provide direct replacement… The API.
However, as you can see from the source code of the TextView that deals with android: EllipSize property, StaticLayout is used as a tool class to help us achieve this effect. StaticLayout is an Android utility class that handles text newlines.
There are BoringLayout, StaticLayout and DynamicLayout utility classes
BoringLayout
Is used for single-line displayStaticLayout
It’s for text that can’t be changedDynamicLayout
Is for editable text and updates itself.
Now we need to know how to use StaticLayout, there are three constructors we can use directly, right
public StaticLayout(CharSequence source, TextPaint paint,
int width,
Alignment align, float spacingmult, float spacingadd,
boolean includepad) {
this(source, 0, source.length(), paint, width, align,
spacingmult, spacingadd, includepad);
}
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align,
float spacingmult, float spacingadd,
boolean includepad) {
this(source, bufstart, bufend, paint, outerwidth, align,
spacingmult, spacingadd, includepad, null.0);
}
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align,
float spacingmult, float spacingadd,
boolean includepad,
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
this(source, bufstart, bufend, paint, outerwidth, align,
TextDirectionHeuristics.FIRSTSTRONG_LTR,
spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
}
Copy the code
Before you use it, you should know a little about the function of the parameters in the method
- CharSequence source: string that requires a line
- Int bufstart: the first position in the string that requires a line
- Int buza: Where does the string that needs branches end
- TextPaint paint: Paintbrush object
- Int outerWidth: The width of the layout, which is automatically wrapped when characters exceed the width, i.e. the width of the content to display
- Alignment align: Indicates the Alignment
ALIGN_CENTER
,ALIGN_NORMAL
,ALIGN_OPPOSITE
Three kinds of - Float spacingmult: multiple of line spacing, equivalent to
android:lineSpacingMultiplier
- Float spacingAdd: Additional line spacing, equivalent to
android:lineSpacingExtra
- Boolean incluDEPad: Whether to include padding
- TruncateAt ellipsize: ellipse position,
TruncateAt
Is aenum
, there areSTART
,MIDDLE
,END
,MARQUEE
(Running lantern) andEND_SMALL
But it was hidden - Int ellipsizedWidth: Position to start ellipsis
All we need to do is use the constructor with the fewest arguments
private Layout createStaticLayout(SpannableStringBuilder spannable) {
int contentWidth = initWidth - getPaddingLeft() - getPaddingRight();
return new StaticLayout(spannable, getPaint(), contentWidth, Layout.Alignment.ALIGN_NORMAL,
getLineSpacingMultiplier(), getLineSpacingExtra(), false);
}
Copy the code
Once we get the text’s StaticLayout object, we can use the getLineCount() method to know if the text will exceed our maxLines, Use the getLineEnd(int line) method to find the position of the last character of the last line in the text. The key codes are as follows:
Layout layout = createStaticLayout(tempText);
mExpandable = layout.getLineCount() > maxLines;
if(mExpandable){int endPos = layout.getLineEnd(maxlines-1); mCloseSpannableStr = charSequenceToSpannable(originalText.subSequence(0, endPos)); SpannableStringBuilder tempText2 = charSequenceToSpannable(mCloseSpannableStr).append(ELLIPSIS_STRING);if(mOpenSuffixSpan ! = null) { tempText2.append(tempText2); } Layout = createStaticLayout(tempText2);while (tempLayout.getLineCount() > maxLines) {
int lastSpace = mCloseSpannableStr.length() - 1;
if (lastSpace == -1) {
break;
}
mCloseSpannableStr = charSequenceToSpannable(originalText.subSequence(0, lastSpace));
tempText2 = charSequenceToSpannable(mCloseSpannableStr).append(ELLIPSIS_STRING);
if(mOpenSuffixSpan ! = null) { tempText2.append(mOpenSuffixSpan); } tempLayout = createStaticLayout(tempText2); } mCLoseHeight = templayout.getheight () + getPaddingTop() + getPaddingBottom(); mCloseSpannableStr.append(ELLIPSIS_STRING);if (mOpenSuffixSpan != null) {
mCloseSpannableStr.append(mOpenSuffixSpan);
}
}
Copy the code
In this way, the problem of text interception is solved. The mCloseSpannableStr in the code is the text object that needs to be displayed after being folded. In consideration of the possibility of expressions or pictures in the text, SpannableStringBuilder is used as the text object.
“Fold up” right alignment
For packed text, use SpannableString and set new AlignmentSpan.Standard(layout.alignment.ALIGN_OPPOSITE) to display the packed text as right-aligned. You need to add ‘\n’ between the original text and the enclosing text.
private void updateCloseSuffixSpan(a) {
if (TextUtils.isEmpty(mCloseSuffixStr)) {
mCloseSuffixSpan = null;
return;
}
mCloseSuffixSpan = new SpannableString(mCloseSuffixStr);
mCloseSuffixSpan.setSpan(new ForegroundColorSpan(mCloseSuffixColor), 0, mCloseSuffixStr.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
if (mCloseInNewLine) {
AlignmentSpan alignmentSpan = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE);
mCloseSuffixSpan.setSpan(alignmentSpan, 0, mCloseSuffixStr.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); }}Copy the code
Animation effects
Animations are relatively easy to perform by changing the height of the TextView in the applyTransformation method
class ExpandCollapseAnimation extends Animation {
private final View mTargetView;// Animate the view
private final int mStartHeight;// Start height of animation execution
private final int mEndHeight;// The height after the animation ends
ExpandCollapseAnimation(View target, int startHeight, int endHeight) {
mTargetView = target;
mStartHeight = startHeight;
mEndHeight = endHeight;
setDuration(400);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
// Calculate the height that should be displayed each time, change the height of the execution view, implement the animation
mTargetView.getLayoutParams().height = (int) ((mEndHeight - mStartHeight) * interpolatedTime + mStartHeight); mTargetView.requestLayout(); }}Copy the code
The height of the expanded and folded TextView is retrieved from getHeight() of the StaticLayout text handler. There is also the TextView height and text update before and after the animation, the specific code is as follows:
/** Perform the unfold animation */
private void executeOpenAnim(a) {
// Create an unfolding animation
if (mOpenAnim == null) {
mOpenAnim = new ExpandCollapseAnimation(this, mCLoseHeight, mOpenHeight);
mOpenAnim.setFillAfter(true);
mOpenAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
ExpandableTextView.super.setMaxLines(Integer.MAX_VALUE);
setText(mOpenSpannableStr);
}
@Override
public void onAnimationEnd(Animation animation) {
// After the animation ends, the textView sets the state to expand
getLayoutParams().height = mOpenHeight;
requestLayout();
animating = false;
}
@Override
public void onAnimationRepeat(Animation animation) {}}); }if (animating) {
return;
}
animating = true;
clearAnimation();
// Perform the animation
startAnimation(mOpenAnim);
}
/** Perform the fold animation */
private void executeCloseAnim(a) {
// Create a collapse animation
if (mCloseAnim == null) {
mCloseAnim = new ExpandCollapseAnimation(this, mOpenHeight, mCLoseHeight);
mCloseAnim.setFillAfter(true);
mCloseAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}@Override
public void onAnimationEnd(Animation animation) {
animating = false;
ExpandableTextView.super.setMaxLines(mMaxLines);
setText(mCloseSpannableStr);
getLayoutParams().height = mCLoseHeight;
requestLayout();
}
@Override
public void onAnimationRepeat(Animation animation) {}}); }if (animating) {
return;
}
animating = true;
clearAnimation();
// Perform the animation
startAnimation(mCloseAnim);
}
Copy the code
The final result
Directions for use
methods | instructions |
---|---|
initWidth(int width) |
Initialize theExpandableText Width must be insetOriginalText() Before the call |
setMaxLines(int maxLines) |
Sets the maximum number of rows to display |
setOpenSuffix(String openSuffix) |
Set up theNeed to openIs displayed. The default isan |
setOpenSuffixColor(@ColorInt int openSuffixColor) |
Set up theNeed to openDisplays the text color of the text |
setCloseSuffix(String closeSuffix) |
Set up theNeed to pack upIs displayed. The default isPack up |
setCloseSuffixColor(@ColorInt int closeSuffixColor) |
Set up theNeed to pack upDisplays the text color of the text |
setCloseInNewLine(boolean closeInNewLine) |
Set up theNeed to pack upWhen folded up the text is another line |
setOpenAndCloseCallback(OpenAndCloseCallback callback) |
Set the expanded & folded click Callback |
setCharSequenceToSpannableHandler(CharSequenceToSpannableHandler handler) |
Set text conversion toSpannable The preprocessing callback can handle special text styles |
Finally finished
After adding animation effects, use setMovementMethod (LinkMovementMethod getInstance ()); Add click events for expansion and collapse, resulting in incorrect animation for collapse. TextView’s scrollY value was changed during the animation execution, so we can call the setScrollY(0) method of TextView in applyTransformation. No problem has been found in this way.
Also consider ExpandableTextView should not handle emoji expressions and so on some special text, so provides CharSequenceToSpannableHandler extension interface, can be extended to handle the display of the text.
The above isExpandableTextView
The whole implementation process and ideas, share, if there is a better way to welcome the discussion in the comments
Expandabletext-example
Android TextView can be customized to expand the fold TextView, expand the fold button to follow the text content to achieve the view all and fold function