适用于长文本的TextView
2015-10-14 16:55
375 查看
UI绘制操作分为三步来走,分别是测量、布局和绘制
参数widthMeasureSpec和heightMeasureSpec分别用来描述宽度测量规范和高度测量规范。测量规范使用一个int值来表法,这个int值包含了两个分量。
第一个是mode分量,使用最高2位来表示。测量模式有三种,分别是MeasureSpec.UNSPECIFIED(0)、MeasureSpec.EXACTLY(1)、和MeasureSpec.AT_MOST(2)。
第二个是size分量,使用低30位来表示。当mode分量等于MeasureSpec.EXACTLY时,size分量的值就是父视图要求当前控件要设置的宽度或者高度;当mode分量等于MeasureSpec.AT_MOST时,size分量的值就是父视图限定当前控件可以设置的最大宽度或者高度;当mode分量等于MeasureSpec.UNSPECIFIED时,父视图不限定当前控件所设置的宽度或者高度,这时候当前控件一般就按照实际需求来设置自己的宽度和高度。
我们知道,控件是按照树形结构组织在一起的,其中,子控件的位置由父控件来设置,也就是说,只有容器类控件才需要执行布局操作,这是通过重写父类View的成员函数onLayout来实现的。
我们常见的FrameLayout、LinearLayout、RelativeLayout、TableLayout和AbsoluteLayout,都是属于容器类控件,因此,它们都需要重写父类View的成员函数onLayout。由于TextView控件不是容器类控件,因此,它可以不重写父类View的成员函数onLayout。
Demo下载地址:
http://download.csdn.net/detail/yy471101598/9180779
1.测量
为了能告诉父视图自己的所占据的空间的大小,所有控件都必须要重写父类View的成员函数onMeasure。public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { ...... @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; //计算TextView控件的宽度和高度 ...... setMeasuredDimension(width, height); } ...... }
参数widthMeasureSpec和heightMeasureSpec分别用来描述宽度测量规范和高度测量规范。测量规范使用一个int值来表法,这个int值包含了两个分量。
第一个是mode分量,使用最高2位来表示。测量模式有三种,分别是MeasureSpec.UNSPECIFIED(0)、MeasureSpec.EXACTLY(1)、和MeasureSpec.AT_MOST(2)。
第二个是size分量,使用低30位来表示。当mode分量等于MeasureSpec.EXACTLY时,size分量的值就是父视图要求当前控件要设置的宽度或者高度;当mode分量等于MeasureSpec.AT_MOST时,size分量的值就是父视图限定当前控件可以设置的最大宽度或者高度;当mode分量等于MeasureSpec.UNSPECIFIED时,父视图不限定当前控件所设置的宽度或者高度,这时候当前控件一般就按照实际需求来设置自己的宽度和高度。
2.布局
前面的测量工作实际上是确定了控件的大小,但是控件的位置还未确定。控件的位置是通过布局这个操作来完成的。我们知道,控件是按照树形结构组织在一起的,其中,子控件的位置由父控件来设置,也就是说,只有容器类控件才需要执行布局操作,这是通过重写父类View的成员函数onLayout来实现的。
我们常见的FrameLayout、LinearLayout、RelativeLayout、TableLayout和AbsoluteLayout,都是属于容器类控件,因此,它们都需要重写父类View的成员函数onLayout。由于TextView控件不是容器类控件,因此,它可以不重写父类View的成员函数onLayout。
3.绘制
有了前面两个操作之后,控件的位置的大小就确定下来了,接下来就可以对它们的UI进行绘制了。控件为了能够绘制自己的UI,必须要重写父类View的成员函数onDraw。自定义控件TextViewMultilineEllipse
/** * 支持多行结尾省略号的TextView,改自http://code.google.com/p/android-textview-multiline-ellipse/ */ 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< 4000 /span> 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) */ // UI绘制操作分为三步来走,分别是测量、布局和绘制 @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, 10f25 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(); } } }
布局
<?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:textSize="15sp" /> <LinearLayout android:id="@+id/linearLayout1" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > </LinearLayout> </LinearLayout>
主程序代码
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.MATCH_PARENT,LayoutParams.WRAP_CONTENT); //----用TextView来显示换行长文本----// tvNormal=(TextView)this.findViewById(R.id.tvNormal); tvNormal.setLayoutParams(lp); //限制TextView的宽高 tvNormal.setEllipsize(TextUtils.TruncateAt.END); tvNormal.setSingleLine(false); tvNormal.setMaxLines(10); tvNormal.setText(text); //----用TextViewMultilineEllipse来显示换行长文本----// linearLayout1=(LinearLayout) this.findViewById(R.id.linearLayout1); tvMultilineEllipse = new TextViewMultilineEllipse(this); tvMultilineEllipse.setLayoutParams(lp);//限制TextView的宽高 tvMultilineEllipse.setEllipsis("...");//...替换剩余字符串 tvMultilineEllipse.setMaxLines(10); tvMultilineEllipse.setTextSize((int)tvNormal.getTextSize());//设置文字大小 tvMultilineEllipse.setTextColor(Color.WHITE); tvMultilineEllipse.setText(text);//设置文本 //在代码里添加tvMultilineEllipse,暂时不支持Layout里直接添加 linearLayout1.addView(tvMultilineEllipse); } }
Demo下载地址:
http://download.csdn.net/detail/yy471101598/9180779
相关文章推荐
- Android的TextView与Html相结合的具体方法
- Android中实现为TextView添加多个可点击的文本
- android textview 显示html方法解析
- Android开发技巧之在a标签或TextView控件中单击链接弹出Activity(自定义动作)
- Android实现TextView中文字链接的4种方式介绍及代码
- android TextView属性的详细介绍 分享
- android TextView加下划线的方法
- android TextView多行文本(超过3行)使用ellipsize属性无效问题的解决方法
- android显示TextView文字的倒影效果实现代码
- Android控件之TextView的分析探究
- TextView显示系统时间(时钟功能带秒针变化
- android TextView不用ScrollViewe也可以滚动的方法
- android开发教程之textview内容超出屏幕宽度显示省略号
- android Textview文字监控(Textview使用方法)
- 解析在Android中为TextView增加自定义HTML标签的实现方法
- Android用户界面开发之:TextView的使用实例
- 深入理解TextView实现Rich Text--在同一个TextView设置不同字体风格
- android动态布局之动态加入TextView和ListView的方法
- 关于TextView的setText()与Integer之间一个易犯的小错误 3ff8
- TextView的日常使用技巧