Android self-painted textview solve the problem of early line wrapping, support graphics and text mix

Source: Internet
Author: User
Tags drawtext

First look at the following:

Above is Mtextview, the following is the default TextView.

First, Reason

In the simplest of all English sentences, for example, if there is a long word, the remaining space in the line is not displayed, then the rule is not to break the word, but the whole word is dropped to the next line to start the display. There was nothing wrong with that. One is that we Chinese are characters, how all put down, does not exist in English this question. So I'm not used to that typesetting. Second, if there is a picture inside the TextView, do not know how to judge the code of the word is how to make, in short, it feels that the last word and the back of a string of expression should be a whole, can not be separated, and then dropped to the second line, also caused this ugly typesetting. To verify this statement is also very simple, go to QQ to try, in each expression with a space, you will find the layout of a normal.

Second, the solution

The simplest is to add a space between the expression, if you do not want to do so, only to draw their own.

First to the novice friends to explain the view drawing process, the first is onmeasure (int widthmeasurespec, int heightmeasurespec), onmeasure execution time, is the Father view is asking you, children, How much land do you want to occupy? Of course, when asked you, will give you a restrictive condition, that is, the two parameters, taking Widthmeasurespec as an example, this parameter can not be used directly, must first be opened, with int widthmode = Measurespec.getmode (Widthmeasurespec) and int widthsize = Measurespec.getsize (widthmeasurespec); Widthmode in three cases:

measurespec.exactly: You just widthsize so wide on the line.

Measurespec.at_most: You can only widthsize that wide.

Measurespec.unspecified: Unspecified, how wide you love.

Of course, the parent view gives you the advice, do not follow your own look at it, but the self-nonsense caused the display is not the Father view of the wrong.

Eventually you listen to the advice, think about it, feel that you should have width so wide, height is so high, and finally you have to use setmeasureddimension (width, height) This function to really determine their height width. Then the work of Onmeasure () is finished.

Then there is OnDraw (canvas canvas), which is simple, canvas is the parent view to a canvas, love to draw anything, such as writing a word drawtext (String text, float x, float y, paint paint),

Text is the word to write, paint is a pen to write, it is worth noting that the X, Y coordinates are relative to the top left corner of your own small canvas. At the top left is 0,0 right down is width,height

On the Code

/** * @ Graphics mixed TextView, please use {@link #setMText (charsequence)} * @author Huangwei * @ May 27, 2014 * @ PM 5:29:27 */public class MTe  Xtview extends Textview{private Context context;/** * For measuring character width */private paint paint = new Paint ();p rivate int textcolor =  color.black;//line spacing private float linespacing;private int LINESPACINGDP = 2;//private float Linespacingmult = 0.5f;/** * Maximum width */private Int maxwidth;/** * Only one row width */private int onelinewidth = -1;/** * The width of the widest row in the plotted row */private float Linewidthmax =    -1;    /** * Stores the current text content, each item is one character or one imagespan */private arraylist<object> oblist = new arraylist<object> ();    /** * 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 row */arraylist<line> ContentList = new arraylist<line> (); /** * Cache measured data */private static hashmap<string, softreference<measureddata>> measureddata = new HashMa P<string, Softreference<measureddaTa>> ();    private static int hashindex = 0; Private Charsequence Text = "";/** * min. height */private int minheight;/** * For high screen width */private displaymetrics displaymetrics;p Ublic Mtextview (Context context) {super (context); This.context = Context;paint.setantialias (true); linespacing = dip2px (context, LINESPACINGDP); minheight = dip2px (context,;d) Isplaymetrics = new Displaymetrics ();} Public Mtextview (context context, AttributeSet Attrs) {Super (context, attrs); this.context = Context;paint.setantialias (true); linespacing = dip2px (context, LINESPACINGDP); minheight = dip2px (context,;d) Isplaymetrics = new Displaymetrics ();} @Overridepublic void setmaxwidth (int maxpixels) {super.setmaxwidth (maxpixels); maxWidth = Maxpixels;} @Overridepublic void setminheight (int minheight) {super.setminheight (minheight); this.minheight = 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 (). Getdefaultdisplay (). Getmetrics (displaymetrics); width = displaymetrics.widthpixels;break;default:break;} if (MaxWidth > 0) Width = math.min (width, maxWidth);p aint.settextsize (This.gettextsize ());p Aint.setcolor (textcolor int realheight = measurecontentheight ((int) width), or//if the actual line width is less than the predetermined width, reduce the line width so that its contents are centered horizontally 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 toppadding = Getcompoundpaddingtop (); float height = 0 + TopPadding + linespacing;//has only one row when the IF (onelinewidth! =-1) {height = Getmeasuredheight ()/2-contentlist.get (0). Height/2;} for (int i = 0; i < contentlist.size (); i++) {//Draw a line 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); Realdrawedwidth + = 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) (realdrawed Width + width), int bottom = (int) (height + line.height);d. SetBounds (left, top, right, bottom);d. Draw (canvas); Realdrawedw Idth + = width;}} Height + = line.height + linespacing;}} @Overridepublic void SetTextColor (int color) {super.settextcolor (color); textcolor = color;} /** * Used to measure the height of text content with Imagespan * @param width of the predetermined widths * @return Required height */private int measurecontentheight (int width) {int cache Dheight = Getcacheddata (Text.tostring (), width), if (Cachedheight > 0) {return cachedheight;} The painted width of float obwidth = 0;float Obheight = 0;float textSize = this.gettextsize ();//Line high float lineheight = textsize;//calculated required High degree float height = linespacing;int leftpadding = Getcompoundpaddingleft (); int rightpadding = Getcompoundpaddingright (); 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 = TextSize;} 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 line is full, deposit contentlist, new row if (Width-drawedwidth < obwidth) {Contentlist.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.si Ze ()-1) instanceof String) {int size = Line.line.size (); sb = new StringBuilder (); Sb.append (Line.line.get (size-1)); SB.A Ppend (OB); ob = Sb.tostring (); obwidth = Obwidth + line.widthList.get (size-1); Line.line.set (size-1, OB); line.widthlist. Set (size-1, (int) obwidth); 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 + LineH Eight + linespacing;}    CacheData (width, (int) height); return (int) height;} /** * Get cached measurement data and avoid repeated measurements * @param text * @param width * @return Height */@SuppressWarnings ("Unchecked ") private int Getcacheddata (String text, int width) {softreference<measureddata> cache = Measureddata.get (text); if (cache = = null) return-1; Measureddata MD = Cache.get (); if (MD! = null && md.textsize = = This.gettextsize () && width = = md.width) {Li Newidthmax = Md.linewidthmax;contentlist = (arraylist<line>) md.contentList.clone (); onelinewidth = Md.onelinewidth; StringBuilder sb = new StringBuilder (); for (int. i=0;i<contentlist.size (); i++) {Line line =Contentlist.get (i);   Sb.append (Line.tostring ()); }return Md.measuredheight;} elsereturn-1;} /** * Cache measured data * @param width * @param height */@SuppressWarnings ("unchecked") private void CacheData (int width, int heigh T) {Measureddata MD = new Measureddata (); md.contentlist = (arraylist<line>) 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<contentlist.size (), i++) {Line line = Contentlist.get (i);   Sb.append (Line.tostring ()); }softreference<measureddata> cache = new Softreference<measureddata> (MD); Measureddata.put (    Text.tostring (), cache);} /** * Use this function instead of {@link #setText (charsequence)} * @param cs */public void Setmtext (Charsequence cs) {text = Cs;oblis T.clear ();//contentlist.clear (); arraylist<is> islist = new arraylist<mtextview.is> (); 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.getspa Nend (Imagespans[i]); Is are = 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 ()) {was is = Islist.get (j); if (I < Is.start) {Integer CP = Str.codepointat (i);//Support 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);} /** * Change from DP unit to PX (pixel) */public static int dip2px (context context, float dpvalue) According to the resolution of the phone {final float scale = CONTEXT.G    Etresources (). Getdisplaymetrics (). Density;return (int) (Dpvalue * scale + 0.5f);} /** * @ function: storage Imagespan and its start end location * @author Huangwei * @ May 27, 2014 * @ pm 5:21:37 */class is{public Imagesp    an is;public int start;public int end;} /** * @ function: Store measured row of data * @author Huangwei * @ May 27, 2014 * @ pm 5:22:12 */class line{public Arraylist<o Bject> line = new arraylist<object> ();p ublic arraylist<integer> widthlist = new arraylist<integer> ();p ublic int height; @Overridepublic String toString () {StringBuilder sb = new StringBuilder ("Height:" +height+ ""); for (i NT I=0;i<line.size (); i++) {Sb.append (Line.get (i) + ":" +widthlist.get (i));} Return SB.TOSTRing ();}} /** * @ function: Cached data * @author Huangwei * @ May 27, 2014 * @ PM 5:22:25 */class measureddata{public int measure dheight;public float textsize;public int width;public float Linewidthmax; arraylist<line> contentlist;public int onelinewidth;public int hashindex;}}


For ease of use in the ListView (the ListView repeatedly slides up and down again and again onmeasure), the cache is added, the same case can not be repeated in the measurement once.

For spannablestring, only Imagespan is supported, others need to expand themselves

demo:http://download.csdn.net/detail/yellowcath/7421147

or Https://github.com/yellowcath/MTextView.git

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.