1c046e4a310462aa6998a34ff50439eb5e2508d85mindyp/* 2c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * Copyright (C) 2013 Google Inc. 3c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * Licensed to The Android Open Source Project. 4c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * 5c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * Licensed under the Apache License, Version 2.0 (the "License"); 6c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * you may not use this file except in compliance with the License. 7c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * You may obtain a copy of the License at 8c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * 9c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * http://www.apache.org/licenses/LICENSE-2.0 10c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * 11c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * Unless required by applicable law or agreed to in writing, software 12c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * distributed under the License is distributed on an "AS IS" BASIS, 13c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * See the License for the specific language governing permissions and 15c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * limitations under the License. 16c046e4a310462aa6998a34ff50439eb5e2508d85mindyp */ 17c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 18c046e4a310462aa6998a34ff50439eb5e2508d85mindyppackage com.android.mail.ui; 19c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 20c046e4a310462aa6998a34ff50439eb5e2508d85mindypimport android.content.Context; 21c046e4a310462aa6998a34ff50439eb5e2508d85mindypimport android.text.Layout; 22ce59fca1cfad3489bc121e9b58a9c67e765ca35aAndy Huangimport android.text.Layout.Alignment; 23c046e4a310462aa6998a34ff50439eb5e2508d85mindypimport android.text.SpannableStringBuilder; 245d6773c773401a259e6b6db957c34f36c508816bAndy Huangimport android.text.Spanned; 25ce59fca1cfad3489bc121e9b58a9c67e765ca35aAndy Huangimport android.text.StaticLayout; 26c046e4a310462aa6998a34ff50439eb5e2508d85mindypimport android.text.TextUtils; 27c046e4a310462aa6998a34ff50439eb5e2508d85mindypimport android.util.AttributeSet; 28c046e4a310462aa6998a34ff50439eb5e2508d85mindypimport android.widget.TextView; 29c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 30c046e4a310462aa6998a34ff50439eb5e2508d85mindyp/** 31c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * A special MultiLine TextView that will apply ellipsize logic to only the last 32c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * line of text, such that the last line may be shorter than any previous lines. 33c046e4a310462aa6998a34ff50439eb5e2508d85mindyp */ 34c046e4a310462aa6998a34ff50439eb5e2508d85mindyppublic class EllipsizedMultilineTextView extends TextView { 35c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 36c046e4a310462aa6998a34ff50439eb5e2508d85mindyp public static final int ALL_AVAILABLE = -1; 37c046e4a310462aa6998a34ff50439eb5e2508d85mindyp private int mMaxLines; 38c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 39c046e4a310462aa6998a34ff50439eb5e2508d85mindyp public EllipsizedMultilineTextView(Context context) { 40c046e4a310462aa6998a34ff50439eb5e2508d85mindyp this(context, null); 41c046e4a310462aa6998a34ff50439eb5e2508d85mindyp } 42c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 43c046e4a310462aa6998a34ff50439eb5e2508d85mindyp public EllipsizedMultilineTextView(Context context, AttributeSet attrs) { 44c046e4a310462aa6998a34ff50439eb5e2508d85mindyp super(context, attrs); 45c046e4a310462aa6998a34ff50439eb5e2508d85mindyp } 46c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 47c046e4a310462aa6998a34ff50439eb5e2508d85mindyp @Override 48c046e4a310462aa6998a34ff50439eb5e2508d85mindyp public void setMaxLines(int maxlines) { 49c046e4a310462aa6998a34ff50439eb5e2508d85mindyp super.setMaxLines(maxlines); 50c046e4a310462aa6998a34ff50439eb5e2508d85mindyp mMaxLines = maxlines; 51c046e4a310462aa6998a34ff50439eb5e2508d85mindyp } 52c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 53c046e4a310462aa6998a34ff50439eb5e2508d85mindyp /** 54c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * Ellipsize just the last line of text in this view and set the text to the 55c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * new ellipsized value. 56c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * @param text Text to set and ellipsize 57c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * @param avail available width in pixels for the last line 58c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * @param paint Paint that has the proper properties set to measure the text 59c046e4a310462aa6998a34ff50439eb5e2508d85mindyp * for this view 60c244f29b6f9af500fedf8aad48db4c80df52c5c1Scott Kennedy * @return the {@link CharSequence} that was set on the {@link TextView} 61c046e4a310462aa6998a34ff50439eb5e2508d85mindyp */ 62c244f29b6f9af500fedf8aad48db4c80df52c5c1Scott Kennedy public CharSequence setText(final CharSequence text, int avail) { 63c046e4a310462aa6998a34ff50439eb5e2508d85mindyp if (text == null || text.length() == 0) { 64c244f29b6f9af500fedf8aad48db4c80df52c5c1Scott Kennedy return text; 65c046e4a310462aa6998a34ff50439eb5e2508d85mindyp } 66c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 67c046e4a310462aa6998a34ff50439eb5e2508d85mindyp setEllipsize(null); 685d6773c773401a259e6b6db957c34f36c508816bAndy Huang setText(text); 69c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 70c046e4a310462aa6998a34ff50439eb5e2508d85mindyp if (avail == ALL_AVAILABLE) { 71c244f29b6f9af500fedf8aad48db4c80df52c5c1Scott Kennedy return text; 72c046e4a310462aa6998a34ff50439eb5e2508d85mindyp } 73c046e4a310462aa6998a34ff50439eb5e2508d85mindyp Layout layout = getLayout(); 74c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 75c046e4a310462aa6998a34ff50439eb5e2508d85mindyp if (layout == null) { 76ce59fca1cfad3489bc121e9b58a9c67e765ca35aAndy Huang final int w = getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight(); 77ce59fca1cfad3489bc121e9b58a9c67e765ca35aAndy Huang layout = new StaticLayout(text, 0, text.length(), getPaint(), w, Alignment.ALIGN_NORMAL, 78ce59fca1cfad3489bc121e9b58a9c67e765ca35aAndy Huang 1.0f, 0f, false); 79c046e4a310462aa6998a34ff50439eb5e2508d85mindyp } 80c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 815d6773c773401a259e6b6db957c34f36c508816bAndy Huang // find the last line of text and chop it according to available space 825d6773c773401a259e6b6db957c34f36c508816bAndy Huang final int lastLineStart = layout.getLineStart(mMaxLines - 1); 835d6773c773401a259e6b6db957c34f36c508816bAndy Huang final CharSequence remainder = TextUtils.ellipsize(text.subSequence(lastLineStart, 845d6773c773401a259e6b6db957c34f36c508816bAndy Huang text.length()), getPaint(), avail, TextUtils.TruncateAt.END); 853a9238b703bc1f348d6c06b75617d346ea2bdb1eScott Kennedy 865d6773c773401a259e6b6db957c34f36c508816bAndy Huang // assemble just the text portion, without spans 875d6773c773401a259e6b6db957c34f36c508816bAndy Huang final SpannableStringBuilder builder = new SpannableStringBuilder(); 885d6773c773401a259e6b6db957c34f36c508816bAndy Huang 895d6773c773401a259e6b6db957c34f36c508816bAndy Huang builder.append(text.toString(), 0, lastLineStart); 90c046e4a310462aa6998a34ff50439eb5e2508d85mindyp 9120347e439dc5e2c86bc105d432c64f6e7f69c7ccAndy Huang if (!TextUtils.isEmpty(remainder)) { 925d6773c773401a259e6b6db957c34f36c508816bAndy Huang builder.append(remainder.toString()); 935d6773c773401a259e6b6db957c34f36c508816bAndy Huang } 945d6773c773401a259e6b6db957c34f36c508816bAndy Huang 955d6773c773401a259e6b6db957c34f36c508816bAndy Huang // Now copy the original spans into the assembled string, modified for any ellipsizing. 965d6773c773401a259e6b6db957c34f36c508816bAndy Huang // 975d6773c773401a259e6b6db957c34f36c508816bAndy Huang // Merely assembling the Spanned pieces together would result in duplicate CharacterStyle 985d6773c773401a259e6b6db957c34f36c508816bAndy Huang // spans in the assembled version if a CharacterStyle spanned across the lastLineStart 995d6773c773401a259e6b6db957c34f36c508816bAndy Huang // offset. 1005d6773c773401a259e6b6db957c34f36c508816bAndy Huang if (text instanceof Spanned) { 1015d6773c773401a259e6b6db957c34f36c508816bAndy Huang final Spanned s = (Spanned) text; 1025d6773c773401a259e6b6db957c34f36c508816bAndy Huang final Object[] spans = s.getSpans(0, s.length(), Object.class); 1035d6773c773401a259e6b6db957c34f36c508816bAndy Huang final int destLen = builder.length(); 1045d6773c773401a259e6b6db957c34f36c508816bAndy Huang for (int i = 0; i < spans.length; i++) { 1055d6773c773401a259e6b6db957c34f36c508816bAndy Huang final Object span = spans[i]; 1065d6773c773401a259e6b6db957c34f36c508816bAndy Huang final int start = s.getSpanStart(span); 1075d6773c773401a259e6b6db957c34f36c508816bAndy Huang final int end = s.getSpanEnd(span); 1085d6773c773401a259e6b6db957c34f36c508816bAndy Huang final int flags = s.getSpanFlags(span); 1095d6773c773401a259e6b6db957c34f36c508816bAndy Huang if (start <= destLen) { 1105d6773c773401a259e6b6db957c34f36c508816bAndy Huang builder.setSpan(span, start, Math.min(end, destLen), flags); 1115d6773c773401a259e6b6db957c34f36c508816bAndy Huang } 1125d6773c773401a259e6b6db957c34f36c508816bAndy Huang } 113c046e4a310462aa6998a34ff50439eb5e2508d85mindyp } 1145d6773c773401a259e6b6db957c34f36c508816bAndy Huang 1155d6773c773401a259e6b6db957c34f36c508816bAndy Huang setText(builder); 116c244f29b6f9af500fedf8aad48db4c80df52c5c1Scott Kennedy 117c244f29b6f9af500fedf8aad48db4c80df52c5c1Scott Kennedy return builder; 118c046e4a310462aa6998a34ff50439eb5e2508d85mindyp } 119c046e4a310462aa6998a34ff50439eb5e2508d85mindyp}