这个好像是sdk的一个老bug,我上网查过的,可以自己写个继承TextView的类,再实现其中几个方法解决
(注意得在java文件中设置maxLines):
package com.jebeljing.TextViewTest;
import java.util.ArrayList;
import java.util.List;
import android.content.Context; [code=Java][/code]
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 ellipsizeListeners = new ArrayList();
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) {
System.out.println(layout.getLineCount()+"\t"+maxLines);
workingText = fullText.substring(0, layout.getLineEnd(maxLines - 1)).trim();
Layout layout2=createWorkingLayout(workingText + ELLIPSIS);
while (layout2.getLineCount() > maxLines) {
System.out.println(layout2.getLineCount()+"\t"+maxLines);
int lastSpace = workingText.lastIndexOf(' ');
System.out.println(lastSpace);
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 } }
}
}
这是一个android 的bug ,如果你只是检查英文可以用这种方法,如果你是检查的中文,日文这种需要分词滴,就复杂了
同时可以参考:
package com.test;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint.Align;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.View.MeasureSpec;
import java.util.ArrayList;
import java.util.List;
public class MyClipTextView extends View{
private TextPaint mTextPaint;
private String mText;
private int mAscent;
private String mStrEllipsis;
private String mStrEllipsisMore;
private int mMaxLines;
private boolean mDrawEllipsizeMoreString;
private int mColorEllipsizeMore;
private boolean mRightAlignEllipsizeMoreString;
private boolean mExpanded;
private LineBreaker mBreakerExpanded;
private LineBreaker mBreakerCollapsed;
public MyClipTextView(Context context) {
super(context);
// TODO Auto-generated constructor stub
mExpanded = false;
mDrawEllipsizeMoreString = true;
mRightAlignEllipsizeMoreString = false;
mMaxLines = -1;
mStrEllipsis = "...";
mStrEllipsisMore = "";
mColorEllipsizeMore = 0xFF0000FF;
mBreakerExpanded = new LineBreaker();
mBreakerCollapsed = new LineBreaker();
// Default font size and color.
mTextPaint = new TextPaint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(13);
mTextPaint.setColor(0xFF000000);
mTextPaint.setTextAlign(Align.LEFT);
}
/**
* Sets the text to display in this widget.
* @param text The text to display.
*/
public void setText(String text) {
mText = text;
requestLayout();
invalidate();
}
/**
* Sets the text size for this widget.
* @param size Font size.
*/
public void setTextSize(int size) {
mTextPaint.setTextSize(size);
requestLayout();
invalidate();
}
/**
* Sets the text color for this widget.
* @param color ARGB value for the text.
*/
public void setTextColor(int color) {
mTextPaint.setColor(color);
invalidate();
}
/**
* The string to append when ellipsizing. Must be shorter than the available
* width for a single line!
* @param ellipsis The ellipsis string to use, like "...", or "-----".
*/
public void setEllipsis(String ellipsis) {
mStrEllipsis = ellipsis;
}
/**
* Optional extra ellipsize string. This
* @param ellipsisMore
*/
public void setEllipsisMore(String ellipsisMore) {
mStrEllipsisMore = ellipsisMore;
}
/**
* The maximum number of lines to allow, height-wise.
* @param maxLines
*/
public void setMaxLines(int maxLines) {
mMaxLines = maxLines;
}
/**
* Turn drawing of the optional ellipsizeMore string on or off.
* @param drawEllipsizeMoreString Yes or no.
*/
public void setDrawEllipsizeMoreString(boolean drawEllipsizeMoreString) {
mDrawEllipsizeMoreString = drawEllipsizeMoreString;
}
/**
* Font color to use for the optional ellipsizeMore string.
* @param color ARGB color.
*/
public void setColorEllpsizeMore(int color) {
mColorEllipsizeMore = color;
}
/**
* When drawing the ellipsizeMore string, either draw it wherever ellipsizing on the last
* line occurs, or always right align it. On by default.
* @param rightAlignEllipsizeMoreString Yes or no.
*/
public void setRightAlignEllipsizeMoreString(boolean rightAlignEllipsizeMoreString) {
mRightAlignEllipsizeMoreString = rightAlignEllipsizeMoreString;
}
/**
* @see android.view.View#measure(int, int)
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
/**
* Determines the width of this view
* @param measureSpec A measureSpec packed into an int
* @return The width of the view, honoring constraints from measureSpec
*/
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be.
result = specSize;
// Format the text using this exact width, and the current mode.
breakWidth(specSize);
}
else {
if (specMode == MeasureSpec.AT_MOST) {
// Use the AT_MOST size - if we had very short text, we may need even less
// than the AT_MOST value, so return the minimum.
result = breakWidth(specSize);
result = Math.min(result, specSize);
}
else {
// We're not given any width - so in this case we assume we have an unlimited
// width?
breakWidth(specSize);
}
}
return result;
}
/**
* Determines the height of this view
* @param measureSpec A measureSpec packed into an int
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
mAscent = (int) mTextPaint.ascent();
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be, so nothing to do.
result = specSize;
}
else {
// The lines should already be broken up. Calculate our max desired height
// for our current mode.
int numLines;
if (mExpanded) {
numLines = mBreakerExpanded.getLines().size();
}
else {
numLines = mBreakerCollapsed.getLines().size();
}
result = numLines * (int) (-mAscent + mTextPaint.descent())
+ getPaddingTop()
+ getPaddingBottom();
// Respect AT_MOST value if that was what is called for by measureSpec.
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
/**
* Render the text
*
* @see android.view.View#onDraw(android.graphics.Canvas)
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
List lines;
LineBreaker breaker;
if (mExpanded) {
breaker = mBreakerExpanded;
lines = mBreakerExpanded.getLines();
}
else {
breaker = mBreakerCollapsed;
lines = mBreakerCollapsed.getLines();
}
float x = getPaddingLeft();
float y = getPaddingTop() + (-mAscent);
for (int i = 0; i < lines.size(); i++) {
// Draw the current line.
int[] pair = lines.get(i);
canvas.drawText(mText, pair[0], pair[1]+1, x, y, mTextPaint);
// Draw the ellipsis if necessary.
if (i == lines.size() - 1) {
if (breaker.getRequiredEllipsis()) {
canvas.drawText(mStrEllipsis, x + breaker.getLengthLastEllipsizedLine(), y, mTextPaint);
if (mDrawEllipsizeMoreString) {
int lastColor = mTextPaint.getColor();
mTextPaint.setColor(mColorEllipsizeMore);
if (mRightAlignEllipsizeMoreString) {
// Seems to not be right...
canvas.drawText(mStrEllipsisMore, canvas.getWidth()-(breaker.getLengthEllipsisMore()+getPaddingRight()+getPaddingLeft()), y, mTextPaint);
}
else {
canvas.drawText(mStrEllipsisMore, x + breaker.getLengthLastEllipsizedLinePlusEllipsis(), y, mTextPaint);
}
mTextPaint.setColor(lastColor);
}
}
}
y += (-mAscent + mTextPaint.descent());
if (y > canvas.getHeight()) {
break;
}
}
}
public boolean getIsExpanded() {
return mExpanded;
}
public void expand() {
mExpanded = true;
requestLayout();
invalidate();
}
public void collapse() {
mExpanded = false;
requestLayout();
invalidate();
}
private int breakWidth(int availableWidth) {
int widthUsed = 0;
if (mExpanded) {
widthUsed =
mBreakerExpanded.breakText(
mText,
availableWidth - getPaddingLeft() - getPaddingRight(),
mTextPaint);
}
else {
widthUsed =
mBreakerCollapsed.breakText(
mText,
mStrEllipsis,
mStrEllipsisMore,
mMaxLines,
availableWidth - getPaddingLeft() - getPaddingRight(),
mTextPaint);
}
return widthUsed + getPaddingLeft() + getPaddingRight();
}
/**
* Used internally to break a string into a list of integer pairs. The pairs are
* start and end locations for lines given the current available layout width.
*/
private static class LineBreaker
{
/** Was the input text long enough to need an ellipsis? */
private boolean mRequiredEllipsis;
/** Beginning and end indices for the input string. */
private ArrayList mLines;
/** The width in pixels of the last line, used to draw the ellipsis if necessary. */
private float mLengthLastLine;
/** The width of the ellipsis string, so we know where to draw the ellipsisMore string
* if necessary.
*/
private float mLengthEllipsis;
/** The width of the ellipsizeMore string, same use as above. */
private float mLengthEllipsisMore;
public LineBreaker() {
mRequiredEllipsis = false;
mLines = new ArrayList();
}
/**
* Used for breaking text in 'expanded' mode, which needs no ellipse.
* Uses as many lines as is necessary to accomodate the entire input
* string.
* @param input String to be broken.
* @param maxWidth Available layout width.
* @param tp Current paint object with styles applied to it.
*/
public int breakText(String input,
int maxWidth,
TextPaint tp)
{
return breakText(input, null, null, -1, maxWidth, tp);
}
/**
* Used for breaking text, honors ellipsizing. The string will be broken into lines using
* the available width. The last line will subtract the physical width of the ellipsis
* string from maxWidth to reserve room for the ellipsis. If the ellpsisMore string is set,
* then space will also be reserved for its length as well.
* @param input String to be broken.
* @param ellipsis Ellipsis string, like "..."
* @param ellipsisMore Optional space reservation after the ellipsis, like " Read More!"
* @param maxLines Max number of lines to allow before ellipsizing.
* @param maxWidth Available layout width.
* @param tp Current paint object with styles applied to it.
*/
public int breakText(String input,
String ellipsis,
String ellipsisMore,
int maxLines,
int maxWidth,
TextPaint tp)
{
mLines.clear();
mRequiredEllipsis = false;
mLengthLastLine = 0.0f;
mLengthEllipsis = 0.0f;
mLengthEllipsisMore = 0.0f;
// If maxWidth is -1, interpret that as meaning to render the string on a single
// line. Skip everything.
if (maxWidth == -1) {
mLines.add(new int[]{ 0, input.length() });
return (int)(tp.measureText(input) + 0.5f);
}
// Measure the ellipsis string, and the ellipsisMore string if valid.
if (ellipsis != null) {
mLengthEllipsis = tp.measureText(ellipsis);
}
if (ellipsisMore != null) {
mLengthEllipsisMore = tp.measureText(ellipsisMore);
}
// Start breaking.
int posStartThisLine = -1;
float lengthThisLine = 0.0f;
boolean breakWords = true;
int pos = 0;
while (pos < input.length()) {
if (posStartThisLine == -1) {
posStartThisLine = pos;
}
if (mLines.size() == maxLines) {
mRequiredEllipsis = true;
break;
}
float widthOfChar = tp.measureText(input.charAt(pos) + "");
boolean newLineRequired = false;
if(!hasChinese(input)){/**english*/
// Check for a new line character or if we've run over max width.
if (input.charAt(pos) == '\n') {
newLineRequired = true;
// We want the current line to go up to the character right before the
// new line char, and we want the next line to start at the char after
// this new line char.
mLines.add(new int[] { posStartThisLine, pos-1 });
}else if (lengthThisLine + widthOfChar >= maxWidth) {
newLineRequired = true;
// We need to backup if we are in the middle of a word.
if (input.charAt(pos) == ' ' || breakWords == false) {
// Backup one character, because it doesn't fit on this line.
pos--;
// So this line includes up to the character before the space.
mLines.add(new int[] { posStartThisLine, pos });
}else {
// Backup until we are at a space.
Log.v("*******", "*********************************now char = " + input.charAt(pos));
while (input.charAt(pos) != ' ') {
pos--;
}
// This line includes up to the space.
mLines.add(new int[] { posStartThisLine, pos });
}
}
}else{/**chinese*/
// Check for a new line character or if we've run over max width.
if (input.charAt(pos) == '\n') {
newLineRequired = true;
// We want the current line to go up to the character right before the
// new line char, and we want the next line to start at the char after
// this new line char.
mLines.add(new int[] { posStartThisLine, pos-1 });
}else if (lengthThisLine + widthOfChar >= maxWidth) {
newLineRequired = true;
// This line includes up to the space.
mLines.add(new int[] { posStartThisLine, pos });
}
}
if (newLineRequired) {
// The next cycle should reset the position if it sees it's -1 (to whatever i is).
posStartThisLine = -1;
// Reset line length for next iteration.
lengthThisLine = 0.0f;
// When we get to the last line, subtract the width of the ellipsis.
if (mLines.size() == maxLines - 1) {
maxWidth -= (mLengthEllipsis + mLengthEllipsisMore);
// We also don't need to break on a full word, it'll look a little
// cleaner if all breaks on the final lines break in the middle of
// the last word.
breakWords = false;
}
}else {
if(!hasChinese(input)){/**english*/
lengthThisLine += widthOfChar;
}else{/**chinese*/
lengthThisLine += (widthOfChar + 0.5f);
}
// If we're on the last character of the input string, add on whatever we have leftover.
if (pos == input.length() - 1) {
mLines.add(new int[] { posStartThisLine, pos });
}
}
pos++;
}
// If we ellipsized, then add the ellipsis string to the end.
if (mRequiredEllipsis) {
int[] pairLast = mLines.get(mLines.size()-1);
mLengthLastLine = tp.measureText(input.substring(pairLast[0], pairLast[1] + 1));
}
// If we required only one line, return its length, otherwise we used
// whatever the maxWidth supplied was.
if (mLines.size() == 0) {
return 0;
}
else if (mLines.size() == 1) {
return (int)(tp.measureText(input) + 0.5f);
}
else {
return maxWidth;
}
}
public boolean getRequiredEllipsis() {
return mRequiredEllipsis;
}
public List getLines() {
return mLines;
}
public float getLengthLastEllipsizedLine() {
return mLengthLastLine;
}
public float getLengthLastEllipsizedLinePlusEllipsis() {
return mLengthLastLine + mLengthEllipsis;
}
public float getLengthEllipsis() {
return mLengthEllipsis;
}
public float getLengthEllipsisMore() {
return mLengthEllipsisMore;
}
/**
* ÅжÏÎı¾ÖÐÊÇ·ñº¬ÓÐÖÐÎÄ
*/
private boolean hasChinese(String input){
return input.getBytes().length != input.length();
}
}
}
package com.test;
import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
public class MyClipActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout llContent = (LinearLayout) findViewById(R.id.llvg);
MyClipTextView tv2 = new MyClipTextView(this);
tv2.setLayoutParams(new LayoutParams(100, 100));
tv2.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
tv2.setEllipsis("...");
tv2.setEllipsisMore("");
tv2.setMaxLines(5);
tv2.setText(getResources().getString(R.string.testcn1));
tv2.setPadding(10, 10, 10, 10);
tv2.setBackgroundColor(0xFFFCDFB2);
llContent.addView(tv2);
}
}
package ru.gzt.newsreader.widgets;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint.Align;
import android.text.TextPaint;
import android.util.Log;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
public class TextViewMultilineEllipse extends View {
private TextPaint mTextPaint;
private String mText;
private int mAscent;
private String mStrEllipsis;
private String mStrEllipsisMore;
private int mMaxLines;
private boolean mDrawEllipsizeMoreString;
private int mColorEllipsizeMore;
private boolean mRightAlignEllipsizeMoreString;
private boolean mExpanded;
private LineBreaker mBreakerExpanded;
private LineBreaker mBreakerCollapsed;
public TextViewMultilineEllipse(Context context) {
super(context);
// TODO Auto-generated constructor stub
mExpanded = false;
mDrawEllipsizeMoreString = true;
mRightAlignEllipsizeMoreString = false;
mMaxLines = -1;
mStrEllipsis = "...";
mStrEllipsisMore = "";
mColorEllipsizeMore = 0xFF0000FF;
mBreakerExpanded = new LineBreaker();
mBreakerCollapsed = new LineBreaker();
// Default font size and color.
mTextPaint = new TextPaint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(13);
mTextPaint.setColor(0xFF000000);
mTextPaint.setTextAlign(Align.LEFT);
}
/**
* Sets the text to display in this widget.
*
* @param text
* The text to display.
*/
public void setText(String text) {
mText = text;
requestLayout();
invalidate();
}
/**
* Sets the text size for this widget.
*
* @param size
* Font size.
*/
public void setTextSize(int size) {
mTextPaint.setTextSize(size);
requestLayout();
invalidate();
}
/**
* Sets the text color for this widget.
*
* @param color
* ARGB value for the text.
*/
public void setTextColor(int color) {
mTextPaint.setColor(color);
invalidate();
}
/**
* The string to append when ellipsizing. Must be shorter than the available
* width for a single line!
*
* @param ellipsis
* The ellipsis string to use, like "...", or "-----".
*/
public void setEllipsis(String ellipsis) {
mStrEllipsis = ellipsis;
}
/**
* Optional extra ellipsize string. This
*
* @param ellipsisMore
*/
public void setEllipsisMore(String ellipsisMore) {
mStrEllipsisMore = ellipsisMore;
}
/**
* The maximum number of lines to allow, height-wise.
*
* @param maxLines
*/
public void setMaxLines(int maxLines) {
mMaxLines = maxLines;
}
/**
* Turn drawing of the optional ellipsizeMore string on or off.
*
* @param drawEllipsizeMoreString
* Yes or no.
*/
public void setDrawEllipsizeMoreString(boolean drawEllipsizeMoreString) {
mDrawEllipsizeMoreString = drawEllipsizeMoreString;
}
/**
* Font color to use for the optional ellipsizeMore string.
*
* @param color
* ARGB color.
*/
public void setColorEllpsizeMore(int color) {
mColorEllipsizeMore = color;
}
/**
* When drawing the ellipsizeMore string, either draw it wherever ellipsizing
* on the last line occurs, or always right align it. On by default.
*
* @param rightAlignEllipsizeMoreString
* Yes or no.
*/
public void setRightAlignEllipsizeMoreString(boolean rightAlignEllipsizeMoreString) {
mRightAlignEllipsizeMoreString = rightAlignEllipsizeMoreString;
}
/**
* @see android.view.View#measure(int, int)
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
/**
* Determines the width of this view
*
* @param measureSpec
* A measureSpec packed into an int
* @return The width of the view, honoring constraints from measureSpec
*/
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be.
result = specSize;
// Format the text using this exact width, and the current mode.
breakWidth(specSize);
} else {
if (specMode == MeasureSpec.AT_MOST) {
// Use the AT_MOST size - if we had very short text, we may need even
// less
// than the AT_MOST value, so return the minimum.
result = breakWidth(specSize);
result = Math.min(result, specSize);
} else {
// We're not given any width - so in this case we assume we have an
// unlimited
// width?
breakWidth(specSize);
}
}
return result;
}
/**
* Determines the height of this view
*
* @param measureSpec
* A measureSpec packed into an int
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
mAscent = (int) mTextPaint.ascent();
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be, so nothing to do.
result = specSize;
} else {
// The lines should already be broken up. Calculate our max desired height
// for our current mode.
int numLines;
if (mExpanded) {
numLines = mBreakerExpanded.getLines().size();
} else {
numLines = mBreakerCollapsed.getLines().size();
}
result = numLines * (int) (-mAscent + mTextPaint.descent()) + getPaddingTop() + getPaddingBottom();
// Respect AT_MOST value if that was what is called for by measureSpec.
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
/**
* Render the text
*
* @see android.view.View#onDraw(android.graphics.Canvas)
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
List lines;
LineBreaker breaker;
if (mExpanded) {
breaker = mBreakerExpanded;
lines = mBreakerExpanded.getLines();
} else {
breaker = mBreakerCollapsed;
lines = mBreakerCollapsed.getLines();
}
float x = getPaddingLeft();
float y = getPaddingTop() + (-mAscent);
for (int i = 0; i < lines.size(); i++) {
// Draw the current line.
int[] pair = lines.get(i);
canvas.drawText(mText, pair[0], pair[1] + 1, x, y, mTextPaint);
// Draw the ellipsis if necessary.
if (i == lines.size() - 1) {
if (breaker.getRequiredEllipsis()) {
canvas.drawText(mStrEllipsis, x + breaker.getLengthLastEllipsizedLine(), y, mTextPaint);
if (mDrawEllipsizeMoreString) {
int lastColor = mTextPaint.getColor();
mTextPaint.setColor(mColorEllipsizeMore);
if (mRightAlignEllipsizeMoreString) {
// Seems to not be right...
canvas.drawText(mStrEllipsisMore, canvas.getWidth() - (breaker.getLengthEllipsisMore() + getPaddingRight() + getPaddingLeft()), y, mTextPaint);
} else {
canvas.drawText(mStrEllipsisMore, x + breaker.getLengthLastEllipsizedLinePlusEllipsis(), y, mTextPaint);
}
mTextPaint.setColor(lastColor);
}
}
}
y += (-mAscent + mTextPaint.descent());
if (y > canvas.getHeight()) {
break;
}
}
}
public boolean getIsExpanded() {
return mExpanded;
}
public void expand() {
mExpanded = true;
requestLayout();
invalidate();
}
public void collapse() {
mExpanded = false;
requestLayout();
invalidate();
}
private int breakWidth(int availableWidth) {
int widthUsed = 0;
if (mExpanded) {
widthUsed = mBreakerExpanded.breakText(mText, availableWidth - getPaddingLeft() - getPaddingRight(), mTextPaint);
} else {
widthUsed = mBreakerCollapsed.breakTextFast(mText, mStrEllipsis, mStrEllipsisMore, mMaxLines, availableWidth - getPaddingLeft() - getPaddingRight(),
mTextPaint);
}
return widthUsed + getPaddingLeft() + getPaddingRight();
}
/**
* Used internally to break a string into a list of integer pairs. The pairs
* are start and end locations for lines given the current available layout
* width.
*/
private static class LineBreaker {
/** Was the input text long enough to need an ellipsis? */
private boolean mRequiredEllipsis;
/** Beginning and end indices for the input string. */
private ArrayList mLines;
/**
* The width in pixels of the last line, used to draw the ellipsis if
* necessary.
*/
private float mLengthLastLine;
/**
* The width of the ellipsis string, so we know where to draw the
* ellipsisMore string if necessary.
*/
private float mLengthEllipsis;
/** The width of the ellipsizeMore string, same use as above. */
private float mLengthEllipsisMore;
public LineBreaker() {
mRequiredEllipsis = false;
mLines = new ArrayList();
}
/**
* Used for breaking text in 'expanded' mode, which needs no ellipse. Uses
* as many lines as is necessary to accomodate the entire input string.
*
* @param input
* String to be broken.
* @param maxWidth
* Available layout width.
* @param tp
* Current paint object with styles applied to it.
*/
public int breakText(String input, int maxWidth, TextPaint tp) {
// return breakText(input, null, null, -1, maxWidth, tp);
return breakTextFast(input, maxWidth, tp);
}
public final int breakTextFast(String input, int maxWidth, TextPaint tp) {
CharSequence textCharArray = input.subSequence(0, input.length());
int inputLength = textCharArray.length();
mLines.clear();
int offset = 0;
while (offset < inputLength) {
int numOfChars = tp.breakText(textCharArray, offset, textCharArray.length(), true, maxWidth, null);
mLines.add(new int[] { offset, (offset += numOfChars) - 1 });
}
return maxWidth;
}
public final int breakTextFast(String input, String ellipsis, String ellipsisMore, int maxLines, int maxWidth, TextPaint tp) {
CharSequence textCharArray = input.subSequence(0, input.length());
int inputLength = textCharArray.length();
mRequiredEllipsis = false;
mLengthLastLine = 0.0f;
mLengthEllipsis = 0.0f;
mLengthEllipsisMore = 0.0f;
if (ellipsis != null) {
mLengthEllipsis = tp.measureText(ellipsis);
}
if (ellipsisMore != null) {
mLengthEllipsis = tp.measureText(ellipsisMore);
}
float maxLineWidth = 0;
float[] measuredWidth = new float[1];
measuredWidth[0] = 0;
mLines.clear();
int offset = 0, k = 0;
while (k++ < maxLines && offset < inputLength) {
int numOfChars = tp.breakText(textCharArray, offset, textCharArray.length(), true, maxWidth, measuredWidth);
maxLineWidth = maxLineWidth > measuredWidth[0] ? maxLineWidth : measuredWidth[0];
mLines.add(new int[] { offset, (offset += numOfChars) - 1 });
}
int[] location = mLines.get(mLines.size() - 1);
if (k >= maxLines && location[1] != inputLength - 1) {
mRequiredEllipsis = true;
location[1] = location[0] + tp.breakText(textCharArray, location[0], location[1], true, maxWidth - (mLengthEllipsis + mLengthEllipsis), measuredWidth)
- 1;
maxLineWidth = maxLineWidth > measuredWidth[0] ? maxLineWidth : measuredWidth[0];
}
mLengthLastLine = measuredWidth[0];
return (int) maxLineWidth;
}
public boolean getRequiredEllipsis() {
return mRequiredEllipsis;
}
public List getLines() {
return mLines;
}
public float getLengthLastEllipsizedLine() {
return mLengthLastLine;
}
public float getLengthLastEllipsizedLinePlusEllipsis() {
return mLengthLastLine + mLengthEllipsis;
}
public float getLengthEllipsis() {
return mLengthEllipsis;
}
public float getLengthEllipsisMore() {
return mLengthEllipsisMore;
}
/**
* ÅжÏÎı¾ÖÐÊÇ·ñº¬ÓÐÖÐÎÄ
*/
private boolean hasChinese(String input) {
return input.getBytes().length != input.length();
}
}
}
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 ellipsizeListeners = new ArrayList();
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
}
}