- 浏览: 1184730 次
文章分类
最新评论
-
wu1236:
edf6fdd16aa09efb48a9e64eec5e4f9 ...
发现问题——创新的原动力 -
wu1236:
edf6fdd16aa09efb48a9e64eec5e4f9 ...
不知名的一夜 -
wu1236:
edf6fdd16aa09efb48a9e64eec5e4f9 ...
android-仿美丽说按下效果的顶部导航栏 -
wu1236:
edf6fdd16aa09efb48a9e64eec5e4f9 ...
android-仿美丽说按下效果的顶部导航栏 -
atgoingguoat:
资源大小:164KB上传日期:2012-04-25又是骗分的。 ...
android-仿美丽说按下效果的顶部导航栏
适应多行长文本的Android TextView
本文来自http://blog.csdn.net/hellogv/ ,引用必须注明出处!
大家经常会用到系统默认的TextView,TextView可以很好地适应单行长文本(尾部自动打上省略号),以及可以完整显示多行文本(TextView的宽高足够大)。但如果是很多行的文本而TextView又足够大的时候,则会出现以下这种情况.......超出的文本受TextView大小限制,不能完全显示。
本文主要实现一个能够适应多行长文本的TextView,自动缩减长文本并在结尾补上省略号。如下图:红色部分为普通的TextView,绿色部分为本文实现的TextView。
本文的源码可以到http://download.csdn.net/detail/hellogv/4298631 下载,本文的TextViewMultilineEllipse.java改自http://code.google.com/p/android-textview-multiline-ellipse/以及http://code.google.com/p/android/的MyClipTextView.java,相对于前面2者,本文使用哈希表来保存每次onMeasure()计算所得的宽高,大幅减少重复计算宽高。
本文的主Activity的源码如下:
public class AutoFixTextViewActivity extends Activity { private LinearLayout linearLayout1; private TextViewMultilineEllipse tvMultilineEllipse; private TextView tvNormal; //水调歌头,大家懂的 private final String text="明月几时有?把酒问青天。不知天上宫阙,今夕是何年。\n" +"我欲乘风归去,又恐琼楼玉宇,高处不胜寒。\n" +"起舞弄清影,何似在人间。\n" +"转朱阁,低绮户,照无眠。不应有恨,何事长向别时圆?\n" +"人有悲欢离合,月有阴晴圆缺,此事古难全。\n" +"但愿人长久,千里共婵娟。"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.setTitle("适应多行文本的Android TextView---hellogv"); //共同的宽高 LayoutParams lp=new LayoutParams(LayoutParams.FILL_PARENT,100); //----用TextView来显示换行长文本----// tvNormal=(TextView)this.findViewById(R.id.tvNormal); tvNormal.setLayoutParams(lp); //限制TextView的宽高 tvNormal.setEllipsize(TextUtils.TruncateAt.END); tvNormal.setSingleLine(false); tvNormal.setMaxLines(5); tvNormal.setText(text); //----用TextViewMultilineEllipse来显示换行长文本----// linearLayout1=(LinearLayout) this.findViewById(R.id.linearLayout1); tvMultilineEllipse = new TextViewMultilineEllipse(this); tvMultilineEllipse.setLayoutParams(lp);//限制TextView的宽高 tvMultilineEllipse.setEllipsis("...");//...替换剩余字符串 tvMultilineEllipse.setMaxLines(5); tvMultilineEllipse.setTextSize((int)tvNormal.getTextSize());//设置文字大小 tvMultilineEllipse.setTextColor(Color.WHITE); tvMultilineEllipse.setText(text);//设置文本 //在代码里添加tvMultilineEllipse,暂时不支持Layout里直接添加 linearLayout1.addView(tvMultilineEllipse); } }
PS:TextViewMultilineEllipse是在代码里动态构建和使用,而不能直接在Layout.xml里使用。
main.xml的源码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:id="@+id/tvNormal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Medium Text" android:textAppearance="?android:attr/textAppearanceMedium" /> <LinearLayout android:id="@+id/linearLayout1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dip" > </LinearLayout> </LinearLayout>
TextViewMultilineEllipse.java源码如下,有点多,读者可以直接忽略:
public class TextViewMultilineEllipse extends TextView{ 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; /**hashMapW是优化的关键点,通过哈希表来减少计算次数*/ private HashMap<Integer,Integer> hashMapW=new HashMap<Integer,Integer>(); 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); setDrawingCacheEnabled(true); } /** * 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<int[]> 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) { if(hashMapW.containsKey(availableWidth)) return hashMapW.get(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); } hashMapW.put(availableWidth, widthUsed + getPaddingLeft() + getPaddingRight()); 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<int[]> 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<int[]>(); } /** * 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<int[]> 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(); } } }
相关推荐
android TextView android TextView
android TextView 支持CSS样式
在TextView中,如果文本很长,可能需要实现垂直滚动显示文本的效果。这里需要在XML布局文件中为TextView设置如下几个属性。 Android:scrollbars=vertical android:scrollbarStyle=X 其中X为outsideOverlay或...
Android textview 文字中间加上图片
MTextView,解决Android TextView提前换行的问题
Android TextView 去掉自适应默认的fontpadding的实现方法 最近在项目中使用textview时发现在使用Android:layout_height=”wrap_content”这个属性设置后,textview会有默认的padding,也就是fontpadding。这样就会...
android实现TextView文字部分渐变色;LinearGradientFontSpan SpannableString
Android Textview实现文本间距
android textview图文混排支持GIF图 原生方式实现 glide支持
android textview 部分文本变色,点击可跳转到指定的链接
*支持Textview设置富文本点击 *设置Textview 中部分字设置可以点击、变色,设置setMovementMethod(LinkMovementMethod.getInstance())后滑动与Textview局部点击冲突...都已经解决. *解决点击空白区域依然有点击事件 ...
android textview 文字排版 换行
android下textview 显示html片段,可以显示文字和图片混排,图片可以是网络图片
通过textview设置状态选择
可直接运行,方便直接添加到项目中 解决了android 加载Html页面只出现文字,图片加载不出来,直接显示小方块的问题
有时候看到原型图上要求图片和文字对齐,但是直接使用TextView的话总是有一段空白,这里直接继承TextView重写onDraw方法去掉上下边距,但是简单粗暴,导致只能显示文字,没有TextView其他的功能了。
Android TextView 换行不对齐的问题
android TextView 折叠展开
Android TextView显示html的demo ,演示如何在TextView上显示html效果,包括图片,