1 /* 2 * Copyright (C) 2013 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mail.ui; 19 20 import android.content.Context; 21 import android.text.Layout; 22 import android.text.Layout.Alignment; 23 import android.text.SpannableStringBuilder; 24 import android.text.Spanned; 25 import android.text.StaticLayout; 26 import android.text.TextUtils; 27 import android.util.AttributeSet; 28 import android.widget.TextView; 29 30 /** 31 * A special MultiLine TextView that will apply ellipsize logic to only the last 32 * line of text, such that the last line may be shorter than any previous lines. 33 */ 34 public class EllipsizedMultilineTextView extends TextView { 35 36 public static final int ALL_AVAILABLE = -1; 37 private int mMaxLines; 38 EllipsizedMultilineTextView(Context context)39 public EllipsizedMultilineTextView(Context context) { 40 this(context, null); 41 } 42 EllipsizedMultilineTextView(Context context, AttributeSet attrs)43 public EllipsizedMultilineTextView(Context context, AttributeSet attrs) { 44 super(context, attrs); 45 } 46 47 @Override setMaxLines(int maxlines)48 public void setMaxLines(int maxlines) { 49 super.setMaxLines(maxlines); 50 mMaxLines = maxlines; 51 } 52 53 /** 54 * Ellipsize just the last line of text in this view and set the text to the 55 * new ellipsized value. 56 * @param text Text to set and ellipsize 57 * @param avail available width in pixels for the last line 58 * @param paint Paint that has the proper properties set to measure the text 59 * for this view 60 * @return the {@link CharSequence} that was set on the {@link TextView} 61 */ setText(final CharSequence text, int avail)62 public CharSequence setText(final CharSequence text, int avail) { 63 if (text == null || text.length() == 0) { 64 return text; 65 } 66 67 setEllipsize(null); 68 setText(text); 69 70 if (avail == ALL_AVAILABLE) { 71 return text; 72 } 73 Layout layout = getLayout(); 74 75 if (layout == null) { 76 final int w = getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight(); 77 layout = new StaticLayout(text, 0, text.length(), getPaint(), w, Alignment.ALIGN_NORMAL, 78 1.0f, 0f, false); 79 } 80 81 // find the last line of text and chop it according to available space 82 final int lastLineStart = layout.getLineStart(mMaxLines - 1); 83 final CharSequence remainder = TextUtils.ellipsize(text.subSequence(lastLineStart, 84 text.length()), getPaint(), avail, TextUtils.TruncateAt.END); 85 86 // assemble just the text portion, without spans 87 final SpannableStringBuilder builder = new SpannableStringBuilder(); 88 89 builder.append(text.toString(), 0, lastLineStart); 90 91 if (!TextUtils.isEmpty(remainder)) { 92 builder.append(remainder.toString()); 93 } 94 95 // Now copy the original spans into the assembled string, modified for any ellipsizing. 96 // 97 // Merely assembling the Spanned pieces together would result in duplicate CharacterStyle 98 // spans in the assembled version if a CharacterStyle spanned across the lastLineStart 99 // offset. 100 if (text instanceof Spanned) { 101 final Spanned s = (Spanned) text; 102 final Object[] spans = s.getSpans(0, s.length(), Object.class); 103 final int destLen = builder.length(); 104 for (int i = 0; i < spans.length; i++) { 105 final Object span = spans[i]; 106 final int start = s.getSpanStart(span); 107 final int end = s.getSpanEnd(span); 108 final int flags = s.getSpanFlags(span); 109 if (start <= destLen) { 110 builder.setSpan(span, start, Math.min(end, destLen), flags); 111 } 112 } 113 } 114 115 setText(builder); 116 117 return builder; 118 } 119 }