textview中有很多行,我只让它显示三行,最后显示...,我设置了android:maxLines="3"和android:ellipsize="end",但出现的问题是只显示两行就显示“...”

http://topic.csdn.net/u/20110909/18/7734814e-caa2-4687-8aea-27d404d34f97.html

textview中有很多行,我只让它显示三行,最后显示...,我设置了android:maxLines="3"和android:ellipsize="end",但出现的问题是只显示两行就显示“...” :


android:id="@+id/jgli_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/jgli_pic"
android:layout_alignParentTop="true"
android:layout_marginTop="5dip"
android:layout_alignParentRight="true"
android:layout_marginRight="5dip"
android:maxLines="3"
android:ellipsize="end"
android:text="@+id/bb_price"
style="@style/listview_item_text"
/>

可以把android:ellipsize="end" 去掉,代码控制,也可以:

这个好像是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 ,如果你只是检查英文可以用这种方法,如果你是检查的中文,日文这种需要分词滴,就复杂了

同时可以参考:

http://code.google.com/p/android/issues/detail?id=2254

http://code.google.com/p/android-textview-multiline-ellipse/source/checkout


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
        }
}



你可能感兴趣的:(android,android,string,float,layout,input,constraints)