android ellipsize multiline textview
我需要省略多行textview。 我的组件足够大,可以用椭圆显示至少4行,但是只显示2行。 我试图更改组件的最小和最大行数,但没有任何改变。
这是解决问题的方法。它是TextView的子类,实际上可用于椭圆化。我发现在较早的答案中列出的android-textview-multiline-ellipse代码在某些情况下以及在GPL下都存在问题,这在我们大多数人中并不实际。随意自由地使用此代码,无需注明出处,也可以根据需要使用Apache许可。请注意,当文本变成椭圆形时,会有一个侦听器通知您,我自己发现这很有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | import java.util.ArrayList; import java.util.List; import android.content.Context; import android.graphics.Canvas; import android.text.Layout; import android.text.Layout.Alignment; import android.text.StaticLayout; import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; import android.widget.TextView; public class EllipsizingTextView extends TextView { private static final String ELLIPSIS ="..."; public interface EllipsizeListener { void ellipsizeStateChanged(boolean ellipsized); } private final List<EllipsizeListener> ellipsizeListeners = new ArrayList<EllipsizeListener>(); private boolean isEllipsized; private boolean isStale; private boolean programmaticChange; private String fullText; private int maxLines = -1; private float lineSpacingMultiplier = 1.0f; private float lineAdditionalVerticalPadding = 0.0f; public EllipsizingTextView(Context context) { super(context); } public EllipsizingTextView(Context context, AttributeSet attrs) { super(context, attrs); } public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void addEllipsizeListener(EllipsizeListener listener) { if (listener == null) { throw new NullPointerException(); } ellipsizeListeners.add(listener); } public void removeEllipsizeListener(EllipsizeListener listener) { ellipsizeListeners.remove(listener); } public boolean isEllipsized() { return isEllipsized; } @Override public void setMaxLines(int maxLines) { super.setMaxLines(maxLines); this.maxLines = maxLines; isStale = true; } public int getMaxLines() { return maxLines; } @Override public void setLineSpacing(float add, float mult) { this.lineAdditionalVerticalPadding = add; this.lineSpacingMultiplier = mult; super.setLineSpacing(add, mult); } @Override protected void onTextChanged(CharSequence text, int start, int before, int after) { super.onTextChanged(text, start, before, after); if (!programmaticChange) { fullText = text.toString(); isStale = true; } } @Override protected void onDraw(Canvas canvas) { if (isStale) { super.setEllipsize(null); resetText(); } super.onDraw(canvas); } private void resetText() { int maxLines = getMaxLines(); String workingText = fullText; boolean ellipsized = false; if (maxLines != -1) { Layout layout = createWorkingLayout(workingText); if (layout.getLineCount() > maxLines) { workingText = fullText.substring(0, layout.getLineEnd(maxLines - 1)).trim(); while (createWorkingLayout(workingText + ELLIPSIS).getLineCount() > maxLines) { int lastSpace = workingText.lastIndexOf(' '); if (lastSpace == -1) { break; } workingText = workingText.substring(0, lastSpace); } workingText = workingText + ELLIPSIS; ellipsized = true; } } if (!workingText.equals(getText())) { programmaticChange = true; try { setText(workingText); } finally { programmaticChange = false; } } isStale = false; if (ellipsized != isEllipsized) { isEllipsized = ellipsized; for (EllipsizeListener listener : ellipsizeListeners) { listener.ellipsizeStateChanged(ellipsized); } } } private Layout createWorkingLayout(String workingText) { return new StaticLayout(workingText, getPaint(), getWidth() - getPaddingLeft() - getPaddingRight(), Alignment.ALIGN_NORMAL, lineSpacingMultiplier, lineAdditionalVerticalPadding, false); } @Override public void setEllipsize(TruncateAt where) { // Ellipsize settings are not respected } } |
在我的应用程序中,我遇到了类似的问题:2行字符串,如果字符串太长,最后添加" ..."。
我将此代码在xml文件中转换为textview标签:
1 2 3 | android:maxLines="2" android:ellipsize="end" android:singleLine="false" |
我也遇到了这个问题。关于它,还有一个相当老的错误尚未解决:错误2254
试试这个,它对我有用,我有4行,并且在最后/第四行的末尾添加了" ..."。它与士气的答案相同,但是我在那里有singeLine =" false"。
1 2 3 4 5 6 7 | <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:maxLines="4" android:ellipsize="marquee" android:singleLine="false" android:text="Hi make this a very long string that wraps at least 4 lines, seriously make it really really long so it gets cut off at the fourth line not joke. Just do it!" /> |
最后,我为自己建立了一个简短的解决方案。
您只需要手动椭圆化所需的行,您的maxLine属性将剪切您的文本。
本示例将您的文本最多剪切3行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | final TextView title = (TextView)findViewById(R.id.text); title.setText("A really long text"); ViewTreeObserver vto = title.getViewTreeObserver(); vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { ViewTreeObserver obs = title.getViewTreeObserver(); obs.removeGlobalOnLayoutListener(this); if(title.getLineCount() > 3){ Log.d("","Line["+title.getLineCount()+"]"+title.getText()); int lineEndIndex = title.getLayout().getLineEnd(2); String text = title.getText().subSequence(0, lineEndIndex-3)+"..."; title.setText(text); Log.d("","NewText:"+text); } } }); |
我结合了Micah Hainline,Alex B?lu?和Paul Imhoff的解决方案来创建椭圆形多行
您只需要设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | /* * Copyright (C) 2011 Micah Hainline * Copyright (C) 2012 Triposo * Copyright (C) 2013 Paul Imhoff * Copyright (C) 2014 Shahin Yousefi * * 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. */ import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.support.annotation.NonNull; import android.text.Layout; import android.text.Layout.Alignment; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.StaticLayout; import android.text.TextUtils; import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; import android.widget.TextView; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; public class EllipsizingTextView extends TextView { private static final CharSequence ELLIPSIS ="\u2026"; private static final Pattern DEFAULT_END_PUNCTUATION = Pattern.compile("[\\.!?,;:\u2026]*$", Pattern.DOTALL); private final List<EllipsizeListener> mEllipsizeListeners = new ArrayList<>(); private EllipsizeStrategy mEllipsizeStrategy; private boolean isEllipsized; private boolean isStale; private boolean programmaticChange; private CharSequence mFullText; private int mMaxLines; private float mLineSpacingMult = 1.0f; private float mLineAddVertPad = 0.0f; private Pattern mEndPunctPattern; public EllipsizingTextView(Context context) { this(context, null); } public EllipsizingTextView(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.textViewStyle); } public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray a = context.obtainStyledAttributes(attrs, new int[]{ android.R.attr.maxLines }, defStyle, 0); setMaxLines(a.getInt(0, Integer.MAX_VALUE)); a.recycle(); setEndPunctuationPattern(DEFAULT_END_PUNCTUATION); } public void setEndPunctuationPattern(Pattern pattern) { mEndPunctPattern = pattern; } public void addEllipsizeListener(@NonNull EllipsizeListener listener) { mEllipsizeListeners.add(listener); } public void removeEllipsizeListener(EllipsizeListener listener) { mEllipsizeListeners.remove(listener); } public boolean isEllipsized() { return isEllipsized; } @SuppressLint("Override") public int getMaxLines() { return mMaxLines; } @Override public void setMaxLines(int maxLines) { super.setMaxLines(maxLines); mMaxLines = maxLines; isStale = true; } public boolean ellipsizingLastFullyVisibleLine() { return mMaxLines == Integer.MAX_VALUE; } @Override public void setLineSpacing(float add, float mult) { mLineAddVertPad = add; mLineSpacingMult = mult; super.setLineSpacing(add, mult); } @Override public void setText(CharSequence text, BufferType type) { if (!programmaticChange) { mFullText = text; isStale = true; } super.setText(text, type); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (ellipsizingLastFullyVisibleLine()) isStale = true; } @Override public void setPadding(int left, int top, int right, int bottom) { super.setPadding(left, top, right, bottom); if (ellipsizingLastFullyVisibleLine()) isStale = true; } @Override protected void onDraw(@NonNull Canvas canvas) { if (isStale) resetText(); super.onDraw(canvas); } private void resetText() { int maxLines = getMaxLines(); CharSequence workingText = mFullText; boolean ellipsized = false; if (maxLines != -1) { if (mEllipsizeStrategy == null) setEllipsize(null); workingText = mEllipsizeStrategy.processText(mFullText); ellipsized = !mEllipsizeStrategy.isInLayout(mFullText); } if (!workingText.equals(getText())) { programmaticChange = true; try { setText(workingText); } finally { programmaticChange = false; } } isStale = false; if (ellipsized != isEllipsized) { isEllipsized = ellipsized; for (EllipsizeListener listener : mEllipsizeListeners) { listener.ellipsizeStateChanged(ellipsized); } } } @Override public void setEllipsize(TruncateAt where) { if (where == null) { mEllipsizeStrategy = new EllipsizeNoneStrategy(); return; } switch (where) { case END: mEllipsizeStrategy = new EllipsizeEndStrategy(); break; case START: mEllipsizeStrategy = new EllipsizeStartStrategy(); break; case MIDDLE: mEllipsizeStrategy = new EllipsizeMiddleStrategy(); break; case MARQUEE: super.setEllipsize(where); isStale = false; default: mEllipsizeStrategy = new EllipsizeNoneStrategy(); break; } } public interface EllipsizeListener { void ellipsizeStateChanged(boolean ellipsized); } private abstract class EllipsizeStrategy { public CharSequence processText(CharSequence text) { return !isInLayout(text) ? createEllipsizedText(text) : text; } public boolean isInLayout(CharSequence text) { Layout layout = createWorkingLayout(text); return layout.getLineCount() <= getLinesCount(); } protected Layout createWorkingLayout(CharSequence workingText) { return new StaticLayout(workingText, getPaint(), getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), Alignment.ALIGN_NORMAL, mLineSpacingMult, mLineAddVertPad, false /* includepad */); } protected int getLinesCount() { if (ellipsizingLastFullyVisibleLine()) { int fullyVisibleLinesCount = getFullyVisibleLinesCount(); return fullyVisibleLinesCount == -1 ? 1 : fullyVisibleLinesCount; } else { return mMaxLines; } } protected int getFullyVisibleLinesCount() { Layout layout = createWorkingLayout(""); int height = getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); int lineHeight = layout.getLineBottom(0); return height / lineHeight; } protected abstract CharSequence createEllipsizedText(CharSequence fullText); } private class EllipsizeNoneStrategy extends EllipsizeStrategy { @Override protected CharSequence createEllipsizedText(CharSequence fullText) { return fullText; } } private class EllipsizeEndStrategy extends EllipsizeStrategy { @Override protected CharSequence createEllipsizedText(CharSequence fullText) { Layout layout = createWorkingLayout(fullText); int cutOffIndex = layout.getLineEnd(mMaxLines - 1); int textLength = fullText.length(); int cutOffLength = textLength - cutOffIndex; if (cutOffLength < ELLIPSIS.length()) cutOffLength = ELLIPSIS.length(); String workingText = TextUtils.substring(fullText, 0, textLength - cutOffLength).trim(); String strippedText = stripEndPunctuation(workingText); while (!isInLayout(strippedText + ELLIPSIS)) { int lastSpace = workingText.lastIndexOf(' '); if (lastSpace == -1) break; workingText = workingText.substring(0, lastSpace).trim(); strippedText = stripEndPunctuation(workingText); } workingText = strippedText + ELLIPSIS; SpannableStringBuilder dest = new SpannableStringBuilder(workingText); if (fullText instanceof Spanned) { TextUtils.copySpansFrom((Spanned) fullText, 0, workingText.length(), null, dest, 0); } return dest; } public String stripEndPunctuation(CharSequence workingText) { return mEndPunctPattern.matcher(workingText).replaceFirst(""); } } private class EllipsizeStartStrategy extends EllipsizeStrategy { @Override protected CharSequence createEllipsizedText(CharSequence fullText) { Layout layout = createWorkingLayout(fullText); int cutOffIndex = layout.getLineEnd(mMaxLines - 1); int textLength = fullText.length(); int cutOffLength = textLength - cutOffIndex; if (cutOffLength < ELLIPSIS.length()) cutOffLength = ELLIPSIS.length(); String workingText = TextUtils.substring(fullText, cutOffLength, textLength).trim(); while (!isInLayout(ELLIPSIS + workingText)) { int firstSpace = workingText.indexOf(' '); if (firstSpace == -1) break; workingText = workingText.substring(firstSpace, workingText.length()).trim(); } workingText = ELLIPSIS + workingText; SpannableStringBuilder dest = new SpannableStringBuilder(workingText); if (fullText instanceof Spanned) { TextUtils.copySpansFrom((Spanned) fullText, textLength - workingText.length(), textLength, null, dest, 0); } return dest; } } private class EllipsizeMiddleStrategy extends EllipsizeStrategy { @Override protected CharSequence createEllipsizedText(CharSequence fullText) { Layout layout = createWorkingLayout(fullText); int cutOffIndex = layout.getLineEnd(mMaxLines - 1); int textLength = fullText.length(); int cutOffLength = textLength - cutOffIndex; if (cutOffLength < ELLIPSIS.length()) cutOffLength = ELLIPSIS.length(); cutOffLength += cutOffIndex % 2; // Make it even. String firstPart = TextUtils.substring( fullText, 0, textLength / 2 - cutOffLength / 2).trim(); String secondPart = TextUtils.substring( fullText, textLength / 2 + cutOffLength / 2, textLength).trim(); while (!isInLayout(firstPart + ELLIPSIS + secondPart)) { int lastSpaceFirstPart = firstPart.lastIndexOf(' '); int firstSpaceSecondPart = secondPart.indexOf(' '); if (lastSpaceFirstPart == -1 || firstSpaceSecondPart == -1) break; firstPart = firstPart.substring(0, lastSpaceFirstPart).trim(); secondPart = secondPart.substring(firstSpaceSecondPart, secondPart.length()).trim(); } SpannableStringBuilder firstDest = new SpannableStringBuilder(firstPart); SpannableStringBuilder secondDest = new SpannableStringBuilder(secondPart); if (fullText instanceof Spanned) { TextUtils.copySpansFrom((Spanned) fullText, 0, firstPart.length(), null, firstDest, 0); TextUtils.copySpansFrom((Spanned) fullText, textLength - secondPart.length(), textLength, null, secondDest, 0); } return TextUtils.concat(firstDest, ELLIPSIS, secondDest); } } } |
完整源代码:EllipsizingTextView.java
就我而言,无需用Java编写代码。一切正常。不需要
1 2 3 4 5 6 | <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="4" android:text="@string/very_long_text" /> |
但是,Android Studio(v3.0)的布局预览中似乎存在一个错误:
在我的设备上使用Android 7.1.1可以正常运行:
对于那些感兴趣的人,这是Micah的可爱解决方案的C#Xamarin.Android端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | public delegate void EllipsizeEvent(bool ellipsized); public class EllipsizingTextView : TextView { private const string Ellipsis ="..."; public event EllipsizeEvent EllipsizeStateChanged; private bool isEllipsized; private bool isStale; private bool programmaticChange; private string fullText; private int maxLines = -1; private float lineSpacingMultiplier = 1.0f; private float lineAdditionalVerticalPadding; public EllipsizingTextView(Context context) : base(context) { } public EllipsizingTextView(Context context, IAttributeSet attrs) : base(context, attrs) { } public EllipsizingTextView(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle) { } public EllipsizingTextView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { } public bool IsEllipsized { get { return isEllipsized; } } public override void SetMaxLines(int maxLines) { base.SetMaxLines(maxLines); this.maxLines = maxLines; isStale = true; } public int GetMaxLines() { return maxLines; } public override void SetLineSpacing(float add, float mult) { lineAdditionalVerticalPadding = add; lineSpacingMultiplier = mult; base.SetLineSpacing(add, mult); } protected override void OnTextChanged(ICharSequence text, int start, int before, int after) { base.OnTextChanged(text, start, before, after); if (!programmaticChange) { fullText = text.ToString(); isStale = true; } } protected override void OnDraw(Canvas canvas) { if (isStale) { base.Ellipsize = null; ResetText(); } base.OnDraw(canvas); } private void ResetText() { int maxLines = GetMaxLines(); string workingText = fullText; bool ellipsized = false; if (maxLines != -1) { Layout layout = CreateWorkingLayout(workingText); if (layout.LineCount > maxLines) { workingText = fullText.Substring(0, layout.GetLineEnd(maxLines - 1)).Trim(); while (CreateWorkingLayout(workingText + Ellipsis).LineCount > maxLines) { int lastSpace = workingText.LastIndexOf(' '); if (lastSpace == -1) { break; } workingText = workingText.Substring(0, lastSpace); } workingText = workingText + Ellipsis; ellipsized = true; } } if (workingText != Text) { programmaticChange = true; try { Text = workingText; } finally { programmaticChange = false; } } isStale = false; if (ellipsized != isEllipsized) { isEllipsized = ellipsized; if (EllipsizeStateChanged != null) EllipsizeStateChanged(ellipsized); } } private Layout CreateWorkingLayout(string workingText) { return new StaticLayout(workingText, Paint, Width - PaddingLeft - PaddingRight, Layout.Alignment.AlignNormal, lineSpacingMultiplier, lineAdditionalVerticalPadding, false); } public override TextUtils.TruncateAt Ellipsize { get { return base.Ellipsize; } set { } } } |
根据Micah Hainline的解决方案和alebs评论,我想到了以下适用于Spanned文本的方法,例如
请注意,这仅适用于
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | public class EllipsizingTextView extends TextView { private static final Spanned ELLIPSIS = new SpannedString("…"); public interface EllipsizeListener { void ellipsizeStateChanged(boolean ellipsized); } private final List<EllipsizeListener> ellipsizeListeners = new ArrayList<EllipsizeListener>(); private boolean isEllipsized; private boolean isStale; private boolean programmaticChange; private Spanned fullText; private int maxLines; private float lineSpacingMultiplier = 1.0f; private float lineAdditionalVerticalPadding = 0.0f; public EllipsizingTextView(Context context) { this(context, null); } public EllipsizingTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); super.setEllipsize(null); TypedArray a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.maxLines }); setMaxLines(a.getInt(0, Integer.MAX_VALUE)); } public void addEllipsizeListener(EllipsizeListener listener) { if (listener == null) { throw new NullPointerException(); } ellipsizeListeners.add(listener); } public void removeEllipsizeListener(EllipsizeListener listener) { ellipsizeListeners.remove(listener); } public boolean isEllipsized() { return isEllipsized; } @Override public void setMaxLines(int maxLines) { super.setMaxLines(maxLines); this.maxLines = maxLines; isStale = true; } public int getMaxLines() { return maxLines; } public boolean ellipsizingLastFullyVisibleLine() { return maxLines == Integer.MAX_VALUE; } @Override public void setLineSpacing(float add, float mult) { this.lineAdditionalVerticalPadding = add; this.lineSpacingMultiplier = mult; super.setLineSpacing(add, mult); } @Override public void setText(CharSequence text, BufferType type) { if (!programmaticChange && text instanceof Spanned) { fullText = (Spanned) text; isStale = true; } super.setText(text, type); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (ellipsizingLastFullyVisibleLine()) { isStale = true; } } public void setPadding(int left, int top, int right, int bottom) { super.setPadding(left, top, right, bottom); if (ellipsizingLastFullyVisibleLine()) { isStale = true; } } @Override protected void onDraw(Canvas canvas) { if (isStale) { resetText(); } super.onDraw(canvas); } private void resetText() { Spanned workingText = fullText; boolean ellipsized = false; Layout layout = createWorkingLayout(workingText); int linesCount = getLinesCount(); if (layout.getLineCount() > linesCount) { // We have more lines of text than we are allowed to display. workingText = (Spanned) fullText.subSequence(0, layout.getLineEnd(linesCount - 1)); while (createWorkingLayout((Spanned) TextUtils.concat(workingText, ELLIPSIS)).getLineCount() > linesCount) { int lastSpace = workingText.toString().lastIndexOf(' '); if (lastSpace == -1) { break; } workingText = (Spanned) workingText.subSequence(0, lastSpace); } workingText = (Spanned) TextUtils.concat(workingText, ELLIPSIS); ellipsized = true; } if (!workingText.equals(getText())) { programmaticChange = true; try { setText(workingText); } finally { programmaticChange = false; } } isStale = false; if (ellipsized != isEllipsized) { isEllipsized = ellipsized; for (EllipsizeListener listener : ellipsizeListeners) { listener.ellipsizeStateChanged(ellipsized); } } } /** * Get how many lines of text we are allowed to display. */ private int getLinesCount() { if (ellipsizingLastFullyVisibleLine()) { int fullyVisibleLinesCount = getFullyVisibleLinesCount(); if (fullyVisibleLinesCount == -1) { return 1; } else { return fullyVisibleLinesCount; } } else { return maxLines; } } /** * Get how many lines of text we can display so their full height is visible. */ private int getFullyVisibleLinesCount() { Layout layout = createWorkingLayout(new SpannedString("")); int height = getHeight() - getPaddingTop() - getPaddingBottom(); int lineHeight = layout.getLineBottom(0); return height / lineHeight; } private Layout createWorkingLayout(Spanned workingText) { return new StaticLayout(workingText, getPaint(), getWidth() - getPaddingLeft() - getPaddingRight(), Alignment.ALIGN_NORMAL, lineSpacingMultiplier, lineAdditionalVerticalPadding, false /* includepad */); } @Override public void setEllipsize(TruncateAt where) { // Ellipsize settings are not respected } } |
要在第二行的末尾添加
1 2 | android:maxLines="2" android:ellipsize="end" |
扩展TextView并覆盖以下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | CharSequence origText =""; int maxLines = 2; @Override public void setText(CharSequence text, BufferType type) { super.setText(text, type); origText = text; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); CharSequence text = origText; onPreDraw(); while(getLineCount() > maxLines) { text = text.subSequence(0, text.length()-1); super.setText(text +"..."); super.onMeasure(widthMeasureSpec, heightMeasureSpec); onPreDraw(); } } |
这是我的解决方案。您可以在我的github上下载演示。
https://github.com/krossford/KrossLib/tree/master/android-project
该屏幕截图是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | package com.krosshuang.krosslib.lib.view; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.widget.TextView; import java.util.ArrayList; /* 如何使用? How to use it? > 1.在xml或者java代码中常规使用 > 1.use it like other views on xml and java code. > 2.[必须] setMaxLines 方法替代在xml中的"android:maxLines" 属性 > 2.[must] call the setMaxLines method to instead of the xml property android:maxLines. > 3.[可选] 注意调用 setMultilineEllipsizeMode() 方法,具体请查看注释 > 3.[option] you can invoke setMultilineEllipsizeMode method, but I have not implement it. */ /** * android自己的TextView对多行ellipsize处理的不好 * Created by krosshuang on 2015/12/17. */ public class EllipsizeEndTextView extends TextView { private static final String LOG_TAG ="EllipsizeTextView"; /** 每一行都有省略号 */ //TODO 该特性待完成 public static final int MODE_EACH_LINE = 1; /** 最后一行才有省略号 */ public static final int MODE_LAST_LINE = 2; private static final String ELLIPSIZE ="..."; private ArrayList<String> mTextLines = new ArrayList<String>(); private CharSequence mSrcText = null; private int mMultilineEllipsizeMode = MODE_LAST_LINE; private int mMaxLines = 1; private boolean mNeedIgnoreTextChangeAndSelfInvoke = false; public EllipsizeEndTextView(Context context) { super(context); } public EllipsizeEndTextView(Context context, AttributeSet attrs) { super(context, attrs); } public EllipsizeEndTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { if (!mNeedIgnoreTextChangeAndSelfInvoke) { super.onTextChanged(text, start, lengthBefore, lengthAfter); mSrcText = text; } } @Override public void setMaxLines(int maxlines) { super.setMaxLines(maxlines); mMaxLines = maxlines; } public int getSupportedMaxLines() { return mMaxLines; } @Override protected void onDraw(Canvas canvas) { setVisibleText(); super.onDraw(canvas); mNeedIgnoreTextChangeAndSelfInvoke = false; } private void setVisibleText() { if (mSrcText == null) { return; } //获得可使用的width get available width final int aw = getWidth() - getPaddingLeft() - getPaddingRight(); String srcText = mSrcText.toString(); //将原始的字符串先按原始数据中存在的换行符弄成多行字符串 String[] lines = srcText.split(" "); //Log.i(LOG_TAG,"原始数据有:" + lines.length +" 行" + Arrays.toString(lines)); int maxLines = getSupportedMaxLines(); //将原始文本分成几行后加入list mTextLines.clear(); for (int i = 0; i < lines.length; i++) { mTextLines.add(lines[i]); } switch (mMultilineEllipsizeMode) { case MODE_EACH_LINE: break; default: case MODE_LAST_LINE: //开始遍历 String eachLine = null; for (int i = 0; i < mTextLines.size() && i < maxLines - 1; i++) { eachLine = mTextLines.get(i); if (getPaint().measureText(eachLine, 0, eachLine.length()) > aw) { //当前行超过可用宽度 boolean isOut = true; int end = eachLine.length() - 1; while (isOut) { if (getPaint().measureText(eachLine.substring(0, end), 0, end) > aw) { end--; } else { isOut = false; } } mTextLines.set(i, eachLine.substring(0, end)); //当前行设置为裁剪后的 mTextLines.add(i + 1, eachLine.substring(end, eachLine.length())); //将裁剪剩余的部分,加入下一行,刚好接下来发生的遍历就可以处理它,相当于一个递归 } } //遍历处理结束,所有的行都是在可用宽度以内的 break; } //根据 maxLines 和 结果的行数,决定最小需要多少行 int resultSize = Math.min(maxLines, mTextLines.size()); //对最后一行做处理 String lastLine = mTextLines.get(resultSize - 1); //最后一行有两种情况需要加... //1.最后一行数据本身很长,超过了可用宽度,那么裁剪后尾部加上... //2.最后一行不是很长,并没有超过可用宽度,但是它底下还有行没有显示,因此加上... if (getPaint().measureText(lastLine, 0, lastLine.length()) > aw || resultSize < mTextLines.size()) { boolean isOut = true; int end = lastLine.length(); while (isOut) { if (getPaint().measureText(lastLine.substring(0, end) + ELLIPSIZE, 0, end + 3) > aw) { end--; } else { isOut = false; } } mTextLines.set(resultSize - 1, lastLine.substring(0, end) + ELLIPSIZE); } //开始构建结果 StringBuilder sb = new StringBuilder(); for (int i = 0; i < resultSize ; i++) { sb.append(mTextLines.get(i)); if (i != resultSize - 1) { sb.append(' '); } } //构建完成,set if (sb.toString().equals(getText())) { return; } else { mNeedIgnoreTextChangeAndSelfInvoke = true; setText(sb.toString()); } } /** * 设置ellipsize mode,暂时不支持 * @deprecated * */ public void setMultilineEllipsizeMode(int mode) { mMultilineEllipsizeMode = mode; } } |
只需在您的活动中添加代码
1 | Textview.setEllipsize(TextUtils.TruncateAt.END) |
这将在文本视图的末尾添加省略号
这个也处理了我的html,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | /* * Copyright (C) 2013 Google Inc. * Licensed to 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.android.mail.ui; import android.content.Context; import android.content.res.TypedArray; import android.text.Layout; import android.text.Layout.Alignment; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.StaticLayout; import android.text.TextUtils; import android.util.AttributeSet; import android.widget.TextView; /** * A special MultiLine TextView that will apply ellipsize logic to only the last * line of text, such that the last line may be shorter than any previous lines. */ public class EllipsizedMultilineTextView extends TextView { public static final int ALL_AVAILABLE = -1; private int mMaxLines; public EllipsizedMultilineTextView(Context context) { super(context); } public EllipsizedMultilineTextView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } public EllipsizedMultilineTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs); } private final void init(Context context, AttributeSet attrs) { final TypedArray a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.maxLines }); setMaxLines(a.getInt(0, 2)); } @Override public void setMaxLines(int maxlines) { super.setMaxLines(maxlines); mMaxLines = maxlines; } /** * Ellipsize just the last line of text in this view and set the text to the * new ellipsized value. * @param text Text to set and ellipsize * @param avail available width in pixels for the last line * @param paint Paint that has the proper properties set to measure the text * for this view * @return the {@link CharSequence} that was set on the {@link TextView} */ public CharSequence setText(final CharSequence text, int avail) { if (text == null || text.length() == 0) { return text; } setEllipsize(null); setText(text); if (avail == ALL_AVAILABLE) { return text; } Layout layout = getLayout(); if (layout == null) { final int w = getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight(); layout = new StaticLayout(text, 0, text.length(), getPaint(), w, Alignment.ALIGN_NORMAL, 1.0f, 0f, false); } // find the last line of text and chop it according to available space final int lastLineStart = layout.getLineStart(mMaxLines - 1); final CharSequence remainder = TextUtils.ellipsize(text.subSequence(lastLineStart, text.length()), getPaint(), avail, TextUtils.TruncateAt.END); // assemble just the text portion, without spans final SpannableStringBuilder builder = new SpannableStringBuilder(); builder.append(text.toString(), 0, lastLineStart); if (!TextUtils.isEmpty(remainder)) { builder.append(remainder.toString()); } // Now copy the original spans into the assembled string, modified for any ellipsizing. // // Merely assembling the Spanned pieces together would result in duplicate CharacterStyle // spans in the assembled version if a CharacterStyle spanned across the lastLineStart // offset. if (text instanceof Spanned) { final Spanned s = (Spanned) text; final Object[] spans = s.getSpans(0, s.length(), Object.class); final int destLen = builder.length(); for (int i = 0; i < spans.length; i++) { final Object span = spans[i]; final int start = s.getSpanStart(span); final int end = s.getSpanEnd(span); final int flags = s.getSpanFlags(span); if (start <= destLen) { builder.setSpan(span, start, Math.min(end, destLen), flags); } } } setText(builder); return builder; } } |
原始来源LINK
代码效果很好!如果不仅必须更改Text,则可以重载onSizeChanged方法。
1 2 3 4 5 | @Override protected void onSizeChanged (int w, int h, int oldw, int oldh) { isStale = true; super.onSizeChanged(w, h, oldw, oldh); } |
尝试以这种方式实施
1 2 3 | android:maxLines="1" android:ellipsize="end" android:scrollHorizontally="true" |
scrollHorizo??ntally是
来自Micah Hainline的最高答案很不错,但更好的是用户aleb在Micahs答案下的评论中发布的从该库构建的库:
I created an Android library with this component and changed it to be
able to show as many lines of text as possible and ellipsize the last
one; see github.com/triposo/barone
还有更多功能,如果只需要TextView,就在这里。
也许这会帮助别人比我更快地找到它:-)
我曾经也有过一样的问题。我通过删除android:ellipsize =" marquee"来修复它
这很晚了,但是我发现了来自Android的Apache许可类,该类已在股票邮件应用中使用:https://android.googlesource.com/platform/packages/apps/UnifiedEmail/+/184ec73/src/com/android/邮件/ui/EllipsizedMultilineTextView.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | /* * Copyright (C) 2013 Google Inc. * Licensed to 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.android.mail.ui; import android.content.Context; import android.text.Layout; import android.text.Layout.Alignment; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.StaticLayout; import android.text.TextUtils; import android.util.AttributeSet; import android.widget.TextView; /** * A special MultiLine TextView that will apply ellipsize logic to only the last * line of text, such that the last line may be shorter than any previous lines. */ public class EllipsizedMultilineTextView extends TextView { public static final int ALL_AVAILABLE = -1; private int mMaxLines; public EllipsizedMultilineTextView(Context context) { this(context, null); } public EllipsizedMultilineTextView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void setMaxLines(int maxlines) { super.setMaxLines(maxlines); mMaxLines = maxlines; } /** * Ellipsize just the last line of text in this view and set the text to the * new ellipsized value. * @param text Text to set and ellipsize * @param avail available width in pixels for the last line * @param paint Paint that has the proper properties set to measure the text * for this view * @return the {@link CharSequence} that was set on the {@link TextView} */ public CharSequence setText(final CharSequence text, int avail) { if (text == null || text.length() == 0) { return text; } setEllipsize(null); setText(text); if (avail == ALL_AVAILABLE) { return text; } Layout layout = getLayout(); if (layout == null) { final int w = getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight(); layout = new StaticLayout(text, 0, text.length(), getPaint(), w, Alignment.ALIGN_NORMAL, 1.0f, 0f, false); } // find the last line of text and chop it according to available space final int lastLineStart = layout.getLineStart(mMaxLines - 1); final CharSequence remainder = TextUtils.ellipsize(text.subSequence(lastLineStart, text.length()), getPaint(), avail, TextUtils.TruncateAt.END); // assemble just the text portion, without spans final SpannableStringBuilder builder = new SpannableStringBuilder(); builder.append(text.toString(), 0, lastLineStart); if (!TextUtils.isEmpty(remainder)) { builder.append(remainder.toString()); } // Now copy the original spans into the assembled string, modified for any ellipsizing. // // Merely assembling the Spanned pieces together would result in duplicate CharacterStyle // spans in the assembled version if a CharacterStyle spanned across the lastLineStart // offset. if (text instanceof Spanned) { final Spanned s = (Spanned) text; final Object[] spans = s.getSpans(0, s.length(), Object.class); final int destLen = builder.length(); for (int i = 0; i < spans.length; i++) { final Object span = spans[i]; final int start = s.getSpanStart(span); final int end = s.getSpanEnd(span); final int flags = s.getSpanFlags(span); if (start <= destLen) { builder.setSpan(span, start, Math.min(end, destLen), flags); } } } setText(builder); return builder; } } |
您应该检查一些属性:android:lines,android:minLines,android:maxLines。要显示最多4行并将其椭圆化,您只需要android:maxLines和android:ellipsize:
1 2 3 4 5 6 7 | <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:maxLines="4" android:ellipsize="marquee" android:text="Hai!" /> |
您需要在textview中包括以下内容:
1 | android:singleLine="false" |
默认情况下为true。您需要明确将其设置为false。