Let's take a look at it first:
The above is MTextView, and the following is the default TextView.
I. Reasons
Take the simplest full English sentence as an example. If there is a long word and the remaining space in this line cannot be displayed, the rule is not to interrupt the word, instead, the entire word is displayed in the next line. So there is no error. First, Chinese people are all Chinese characters, so there is no English problem. So I am not used to that layout. Second, if TextView contains an image, it does not know how to determine the code of the word. In short, it thinks that the last word and the next expression should be a whole, if they cannot be separated, they will be thrown to the second line, which will lead to this ugly layout. It is also easy to verify this statement. You can try it on QQ and add a space between each expression. Then you will find that the layout is normal.
Ii. Solution
The simplest thing is to add spaces between expressions. If you don't want to do this, you just need to draw it yourself.
Let's explain the process of drawing a View to a beginner. The first is onMeasure (int widthMeasureSpec, int heightMeasureSpec). When onMeasure is executed, the parent View is asking you, child, how big are you going to take? Of course, when you ask, you will be given a restriction, that is, the two parameters. Taking widthMeasureSpec as an example, this parameter cannot be used directly. You must split it first and use int widthMode = MeasureSpec. getMode (widthMeasureSpec) and int widthSize = MeasureSpec. getSize (widthMeasureSpec); widthMode is applicable to three situations:
MeasureSpec. EXACTLY: You can use the widthSize.
MeasureSpec. AT_MOST: You can only widthSize at most.
MeasureSpec. UNSPECIFIED: UNSPECIFIED. How wide and how wide do you like.
Of course, in fact, this parent View gives you advice, do not comply with what you want to do, but if you are confused, it is not the parent View error.
Finally, you listened to the suggestion and thought about it. You thought you should have the width so wide and height so high. Finally, you must use the setMeasuredDimension (width, height) function to determine your own height and width. Then onMeasure () is finished.
And then onDraw (Canvas canvas). This is simple. canvas is a Canvas provided by the parent View. You can draw anything on it, for example, write a word drawText (String text, float x, float y, Paint paint ),
Text is the word to be written, paint is the pen to write, it is worth noting that x, y coordinate is relative to your own small canvas in the upper left corner. The top left is 0, and the bottom right is width and height.
Code on
/*** @ Function text-and-text layout TextView, please use {@ link # setMText (CharSequence )} * @ author huangwei * @ May 27, 2014 * @ 5:29:27 */public class MTextView extends TextView {private Context context; /*** used to measure the character width */private Paint paint = new Paint (); private int textColor = Color. BLACK; // line spacing private float lineSpacing; private int lineSpacingDP = 2; // private float lineSpacingMult = 0.5f;/*** maximum width */private int maxWidth; /*** only the width of a row */private int oneLineWidth =-1; /*** width of the row with the widest width in the painted row */private float lineWidthMax =-1;/*** stores the current text content, each item is a character or an ImageSpan */private ArrayListObList = new ArrayList();/*** Whether to use the default {@ link # onMeasure (int, int)} and {@ link # onDraw (Canvas)} */private boolean useDefault = false; /*** stores the current text content. each item is a line */ArrayList
ContentList = new ArrayList
();/*** Cache measured data */private static HashMap
> MeasuredData = new HashMap
> (); Private static int hashIndex = 0; private CharSequence text = "";/*** minimum height */private int minHeight; /*** used to obtain the screen height and width */private DisplayMetrics displayMetrics; public MTextView (Context context) {super (context); this. context = context; paint. setAntiAlias (true); lineSpacing = dip2px (context, lineSpacingDP); minHeight = dip2px (context, 30); displayMetrics = new DisplayMetrics ();} public MTextView (Context con Text, AttributeSet attrs) {super (context, attrs); this. context = context; paint. setAntiAlias (true); lineSpacing = dip2px (context, lineSpacingDP); minHeight = dip2px (context, 30); displayMetrics = new DisplayMetrics () ;}@ Overridepublic void setMaxWidth (int maxpixels) {super. setMaxWidth (maxpixels); maxWidth = maxpixels ;}@ Overridepublic void setMinHeight (int minHeight) {super. setMinHeight (minHeight); this. minHe Ight = minHeight;} @ Overrideprotected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {if (useDefault) {super. onMeasure (widthMeasureSpec, heightMeasureSpec); return;} int width = 0, height = 0; int widthMode = MeasureSpec. getMode (widthMeasureSpec); int heightMode = MeasureSpec. getMode (heightMeasureSpec); int widthSize = MeasureSpec. getSize (widthMeasureSpec); int heightSize = MeasureSpec. getSize (HeightMeasureSpec); switch (widthMode) {case MeasureSpec. EXACTLY: width = widthSize; break; case MeasureSpec. AT_MOST: width = widthSize; break; case MeasureSpec. UNSPECIFIED :( (Activity) context ). getWindowManager (). getdefadisplay display (). getMetrics (displayMetrics); width = displayMetrics. widthPixels; break; default: break;} if (maxWidth> 0) width = Math. min (width, maxWidth); paint. setTextSize (this. getTextSize (); pa Int. setColor (textColor); int realHeight = measureContentHeight (int) width); // if the actual row width is less than the predefined width, reduce the row width so that the content is horizontally centered int leftPadding = getCompoundPaddingLeft (); int rightPadding = getCompoundPaddingRight (); width = Math. min (width, (int) lineWidthMax + leftPadding + rightPadding); if (oneLineWidth>-1) {width = oneLineWidth;} switch (heightMode) {case MeasureSpec. EXACTLY: height = heightSize; break; case MeasureSpec. AT _ MOST: height = realHeight; break; case MeasureSpec. UNSPECIFIED: height = realHeight; break; default: break;} height + = getCompoundPaddingTop () + getCompoundPaddingBottom (); height = Math. max (height, minHeight); setMeasuredDimension (width, height) ;}@ Overrideprotected void onDraw (Canvas canvas) {if (useDefault) {super. onDraw (canvas); return;} int width; Object ob; int leftPadding = getCompoundPaddingLeft (); int topPa Dding = getCompoundPaddingTop (); float height = 0 + topPadding + lineSpacing; // if (oneLineWidth! =-1) {height = getMeasuredHeight ()/2-contentList. get (0 ). height/2;} for (int I = 0; I <contentList. size (); I ++) {// draw a row float realDrawedWidth = 0 + leftPadding; LINE line = contentList. get (I); for (int j = 0; j <line. line. size (); j ++) {ob = line. line. get (j); width = line. widthList. get (j); if (ob instanceof String) {canvas. drawText (String) ob, realDrawedWidth, height + line. height, paint); realDrawedWidt H + = width;} else if (ob instanceof ImageSpan) {ImageSpan is = (ImageSpan) ob; Drawable d = is. getDrawable (); int left = (int) (realDrawedWidth); int top = (int) height; int right = (int) (realDrawedWidth + width); int bottom = (int) (height + line. height); d. setBounds (left, top, right, bottom); d. draw (canvas); realDrawedWidth + = width;} height + = line. height + lineSpacing; }}@ Overridepublic void setTextColo R (int color) {super. setTextColor (color); textColor = color ;} /*** used to measure the height of text content with ImageSpan * @ param width the predefined width * @ return the required height */private int measureContentHeight (int width) {int cachedHeight = getCachedData (text. toString (), width); if (cachedHeight> 0) {return cachedHeight;} // float obWidth = 0; float obHeight = 0; float textSize = this. getTextSize (); // float lineHeight = textSize; // The calculated float h Eight = lineSpacing; int leftPadding = trim (); int rightPadding = trim (); float drawedWidth = 0; width = width-leftPadding-rightPadding; oneLineWidth =-1; contentList. clear (); StringBuilder sb; LINE line = new LINE (); for (int I = 0; I <obList. size (); I ++) {Object ob = obList. get (I); if (ob instanceof String) {obWidth = paint. measureText (String) ob); obHeight = textS Ize;} else if (ob instanceof ImageSpan) {Rect r = (ImageSpan) ob ). getDrawable (). getBounds (); obWidth = r. right-r. left; obHeight = r. bottom-r. top; if (obHeight> lineHeight) lineHeight = obHeight;} // This row is full and saved to contentList. A new row, if (width-drawedWidth <obWidth) {contentList, is created. add (line); if (drawedWidth> lineWidthMax) {lineWidthMax = drawedWidth;} drawedWidth = 0; height + = line. height + lineSpacing; lineHeight = ObHeight; line = new LINE ();} drawedWidth + = obWidth; if (ob instanceof String & line. line. size ()> 0 & (line. line. get (line. line. size ()-1) instanceof String) {int size = line. line. size (); sb = new StringBuilder (); sb. append (line. line. get (size-1); sb. append (ob); ob = sb. toString (); obWidth = obWidth + line. widthList. get (size-1); line. line. set (size-1, ob); line. widthList. set (size-1, (int) obWi Dth); line. height = (int) lineHeight;} else {line. line. add (ob); line. widthList. add (int) obWidth); line. height = (int) lineHeight;} if (line! = Null & line. line. size ()> 0) {contentList. add (line); height + = lineHeight + lineSpacing;} if (contentList. size () <= 1) {oneLineWidth = (int) drawedWidth + leftPadding + rightPadding; height = lineSpacing + lineHeight + lineSpacing;} cacheData (width, (int) height ); return (int) height;}/*** get the cached measurement data, avoid repeated measurements * @ param text * @ param width * @ return height */@ SuppressWarnings ("unchecked") private int getCachedData (String text, int width) {SoftReference
Cache = measuredData. get (text); if (cache = null) return-1; MeasuredData md = cache. get (); if (md! = Null & md. textSize = this. getTextSize () & width = md. width) {lineWidthMax = md. lineWidthMax; contentList = (ArrayList
) Md. contentList. clone (); oneLineWidth = md. oneLineWidth; StringBuilder sb = new StringBuilder (); for (int I = 0; I
) ContentList. clone (); md. textSize = this. getTextSize (); md. lineWidthMax = lineWidthMax; md. oneLineWidth = oneLineWidth; md. measuredHeight = height; md. width = width; md. hashIndex = ++ hashIndex; StringBuilder sb = new StringBuilder (); for (int I = 0; I
Cache = new SoftReference
(Md); measuredData. put (text. toString (), cache);}/*** use this function to replace {@ link # setText (CharSequence)} * @ param cs */public void setMText (CharSequence cs) {text = cs; obList. clear (); // contentList. clear (); ArrayList
IsList = new ArrayList
(); UseDefault = false; if (cs instanceof SpannableString) {SpannableString ss = (SpannableString) cs; ImageSpan [] imageSpans = ss. getSpans (0, ss. length (), ImageSpan. class); for (int I = 0; I <imageSpans. length; I ++) {int s = ss. getSpanStart (imageSpans [I]); int e = ss. getSpanEnd (imageSpans [I]); IS = new iS (); IS. is = imageSpans [I]; iS. start = s; iS. end = e; isList. add (iS) ;}} String str = cs. toString (); for (int I = 0, j = 0; I <cs. length ();) {if (j <isList. size () {IS = isList. get (j); if (I <is. start) {Integer cp = str. codePointAt (I); // supports the supplementary Character if (Character. isSupplementaryCodePoint (cp) {I + = 2;} else {I ++;} obList. add (new String (Character. toChars (cp);} else if (I> = is. start) {obList. add (is. is); j ++; I = is. end ;}} else {Integer cp = str. codePointAt (I); if (Character. isSupplementaryCodePoint (cp) {I + = 2;} else {I ++;} obList. add (new String (Character. toChars (cp) ;}} requestLayout () ;} public void setUseDefault (boolean useDefault) {this. useDefault = useDefault; if (useDefault) {this. setText (text); this. setTextColor (textColor) ;}} public static int px2sp (Context context, float pxValue) {final float fontScale = context. getResources (). getDisplayMetrics (). scaledDensity; return (int) (pxValue/fontScale + 0.5f);}/*** convert the unit from dp to px (pixel) based on the resolution of the mobile phone) */public static int dip2px (Context context, float dpValue) {final float scale = context. getResources (). getDisplayMetrics (). density; return (int) (dpValue * scale + 0.5f);}/*** @ function: storage of ImageSpan and its start and end position * @ author huangwei * @ May 27, 2014 * @ 5:21:37 */class IS {public ImageSpan is; public int start; public int end ;} /*** @ function: store a row of measurement data * @ author huangwei * @ May 27, 2014 * @ 5:22:12 */class LINE {public ArrayList
Line = new ArrayList(); Public ArrayList
WidthList = new ArrayList
(); Public int height; @ Overridepublic String toString () {StringBuilder sb = new StringBuilder ("height:" + height + ""); for (int I = 0; I
ContentList; public int oneLineWidth; public int hashIndex ;}}
In order to facilitate usage in ListView (ListView repeatedly slides up or down multiple times and returns the onMeasure), the cache is added. In the same case, you do not need to repeat the measurement once.
For SpannableString, only ImageSpan is supported, and other users can expand it themselves.
Demo: http://download.csdn.net/detail/yellowcath/7421147
Or https://github.com/yellowcath/MTextView.git