Android Tag Cloud: LabelView

Source: Internet
Author: User

Hey, tease better, today we do an Android label cloud effect, the amount, although not very perfect, but already enough to show the effect of the label cloud, first look at the effect bar.



Well, the record screen can only be recorded in this part, live to see it. Today we come to realize this effect, this time I choose to inherit the view directly, what? Is this effect not surfaceview good at? Why to view, actually all can, I choose View, because: the amount, I am not very familiar with Surfaceview.

Nonsense less ro-ro, start with the code below

public class LabelView extends View {private static final int direction_left = 0;//left private static final int direction_ right = 1; Right private static final int direciton_top = 2; Up private static final int direction_bottom = 3; Down Private Boolean isStatic; Whether static, default false, available dry xml:label:is_static= "false" private int[][] mlocations; The position of each label X/yprivate int[][] mdirections; The direction of each label X/yprivate int[][] mspeeds; Each label's X/y speed x/yprivate int[][] mtextwidthandheight; The size of each labeltext width/heightprivate string[] mlabels; Set of Labelsprivate int[] mfontsizes; Font size per label//default color scheme private int[] Mcolorschema = {0xffff0000, 0xff00ff00, 0xff0000ff, 0XFFCCCCCC, 0XFFFFFFFF}; private int mtouchslop; Minimum touchprivate int mdownx = -1;private int mdowny = -1;private int mdownindex =-1; Click on the indexprivate Paint mpaint;private Thread mthread;private onitemclicklistener mlistener; Item Click event Public LabelView (context context, AttributeSet Attrs) {This (context, attrs, 0);} Public LAbelview (context context, AttributeSet attrs, int defstyleattr) {Super (context, attrs, defstyleattr); TypedArray ta = context.obtainstyledattributes (attrs, R.styleable.labelview, defstyleattr, 0); isStatic = Ta.getboolean (r.styleable.labelview_is_static, false); ta.recycle (); mtouchslop = Viewconfiguration.get (context). Getscaledtouchslop (); mpaint = new Paint (); Mpaint.setantialias (true);} @Overrideprotected void OnLayout (Boolean changed, int left, int top, int. Right,int bottom) {super.onlayout (changed, left, Top, right, bottom); init (); @Overrideprotected void OnDraw (canvas canvas) {if (!hascontents ()) {return;} for (int i = 0; i < mlabels.length; i++) {mpaint.settextsize (mfontsizes[i]); if (I < mcolorschema.length) Mpaint.setco Lor (Mcolorschema[i]); else Mpaint.setcolor (Mcolorschema[i-mcolorschema.length]); Canvas.drawtext (MLabels[i), Mlocations[i][0], mlocations[i][1], mpaint);}} @Overridepublic boolean ontouchevent (motionevent ev) {switch (ev.getaction ()) {case MotionEvent.ACTION_DOWN:mDownX = (int) ev.getx (); mdowny = (int) ev.gety (); mdownindex = GetClickIndex (); break;case MotionEvent.ACTION_UP:int nowx = (int ) ev.getx (); int nowy = (int) ev.gety (); if (Nowx-mdownx < Mtouchslop && Nowy-mdowny < Mtouchslop&&amp ; Mdownindex! =-1 && mlistener! = null) {Mlistener.onitemclick (Mdownindex, Mlabels[mdownindex]);} Mdownx = Mdowny = Mdownindex = -1;break;} return true;} /** * Gets the position of the label currently clicked * @return label position, no point returned-1 */private int getclickindex () {rect downrect = new Rect (); Rect locationrect = new rect (); for (int i=0;i<mlocations.length;i++) {Downrect.set (mdownx-mtextwidthandheight[i][0 ], mdowny-mtextwidthandheight[i][1], mdownx+ mtextwidthandheight[i][0], mdowny+ mtextwidthandheight[i][1]); Locationrect.set (Mlocations[i][0], mlocations[i][1],mlocations[i][0] + mtextwidthandheight[i][0],mlocations[i][1] + mtextwidthandheight[i][1]); if (Locationrect.intersect (Downrect)) {return i;}} return-1;} /** * Turn on the sub thread to constantly refresh the position and postinvalidate */private Void Run () {if (MThread! = null && mthread.isalive ()) {return;} Mthread = new Thread (mstartrunning); Mthread.start ();} Private Runnable mstartrunning = new Runnable () {@Overridepublic void Run () {for (;;) {systemclock.sleep); for (int i = 0; i < mlabels.length; i++) {if (mlocations[i][0] <= getpaddingleft ()) {Mdirect Ions[i][0] = direction_right;} if (Mlocations[i][0] >= getmeasuredwidth ()-Getpaddingright ()-mtextwidthandheight[i][0]) {mDirections[i][0] = Direction_left;} if (Mlocations[i][1] <= getpaddingtop () + mtextwidthandheight[i][1]) {mdirections[i][1] = Direction_bottom;} if (Mlocations[i][1] >= getmeasuredheight ()-Getpaddingbottom ()) {mdirections[i][1] = direciton_top;} int xspeed = 1;int Yspeed = 2;if (i < mspeeds.length) {xspeed = Mspeeds[i][0];yspeed = Mspeeds[i][1];} else {xspeed = Mspeeds[i-mspeeds.length][0];yspeed = Mspeeds[i-mspeeds.length][1];} Mlocations[i][0] + = mdirections[i][0] = = Direction_right? XSpeed:-xspeed;mlocations[i][1] + = mdirections[i][1] = = DirecTion_bottom? Yspeed:-yspeed;} Postinvalidate ();}}};/ * * Initialize position, orientation, label height * and turn on thread */private void init () {if (!hascontents ()) {return;} int minX = Getpaddingleft (), int miny = Getpaddingtop (); int maxX = Getmeasuredwidth ()-getpaddingright (); int maxy = Getmea Suredheight ()-Getpaddingbottom ();  Rect textbounds = new rect (), for (int i = 0, i < mlabels.length; i++) {int[] location = new Int[2];location[0] = MinX + (int) (Math.random () * MaxX); location[1] = miny + (int) (Math.random () * Maxy); Mlocations[i] = location;mfontsizes[i] = + (in T) (Math.random () * +); Mdirections[i][0] = Math.random () > 0.5? DIRECTION_RIGHT:DIRECTION_LEFT;MDIRECTIONS[I][1] = Math.random () > 0.5? Direction_bottom:direciton_top;mpaint.settextsize (Mfontsizes[i]); Mpaint.gettextbounds (MLabels[i], 0, MLabels[i]. Length (), textbounds); mtextwidthandheight[i][0] = Textbounds.width (); mtextwidthandheight[i][1] = Textbounds.height ( );} if (!isstatic) run ();} /** * Whether to set label * @return True or False */private Boolean Hascontents () {return mlabels! = null && mlabels.length > 0;} /** * Set Labels * @see setlabels (string[] labels) * @param labels */public void setlabels (list<string> labels) {Setla BELs ((string[]) Labels.toarray ());} /** * Set Labels * @param labels */public void setlabels (string[] labels) {mlabels = Labels;mlocations = new Int[labels.leng th][2];mfontsizes = new Int[labels.length];mdirections = new Int[labels.length][2];mtextwidthandheight = new int[ Labels.length][2];mspeeds = new Int[labels.length][2];for (int speed[]: mspeeds) {speed[0] = speed[1] = 1;} Requestlayout ();} /** * Set color scheme * @param colorschema */public void Setcolorschema (int[] colorschema) {mcolorschema = Colorschema;} /** * Set x/y speed per item * <p> * speeds.length > Labels.length Ignore extra * <p> * Speeds.length < Labels.length will Re-use * * @param speeds */public void setspeeds (int[][] speeds) {mspeeds = speeds;} /** * Set Item click Listener Event * @param l */public void Setonitemclicklistener (Onitemclicklistener l) {getParent (). Requestdisallowintercepttouchevent (true); mlistener = l;} /** * Item's Click Listener Event */public interface Onitemclicklistener {public void Onitemclick (int index, String label);}}

4 constants to get up first, why use? is to determine the direction of each item, because when a certain boundary is reached, item moves in the opposite direction.

In the second construction method, a custom property is obtained, and there is an initialized paint.

Continue to see onlayout, in fact onlayout we do nothing, just call the Init method, look at the Init method.

/** * Initialize position, orientation, label height * and turn on thread */private void init () {if (!hascontents ()) {return;} int minX = Getpaddingleft (), int miny = Getpaddingtop (); int maxX = Getmeasuredwidth ()-getpaddingright (); int maxy = Getmea Suredheight ()-Getpaddingbottom ();  Rect textbounds = new rect (), for (int i = 0, i < mlabels.length; i++) {int[] location = new Int[2];location[0] = MinX + (int) (Math.random () * MaxX); location[1] = miny + (int) (Math.random () * Maxy); Mlocations[i] = location;mfontsizes[i] = + (in T) (Math.random () * +); Mdirections[i][0] = Math.random () > 0.5? DIRECTION_RIGHT:DIRECTION_LEFT;MDIRECTIONS[I][1] = Math.random () > 0.5? Direction_bottom:direciton_top;mpaint.settextsize (Mfontsizes[i]); Mpaint.gettextbounds (MLabels[i], 0, MLabels[i]. Length (), textbounds); mtextwidthandheight[i][0] = Textbounds.width (); mtextwidthandheight[i][1] = Textbounds.height ( );} if (!isstatic) run ();}

Init method, the first to determine whether to set the label, if not set to return directly, save a lot of things.

10~13 Line, the purpose is to get the item in the view move up and down the left and right boundary, after all, the item is still in the whole view to move, not beyond the boundaries of the view.

17 lines, start A For loop, and go through all the tags.

18~20 line, is a random initialization of a position, so, our labels each occurrence of the position is random, and there is no regularity, but the next move is regular, you can not jump around.

Then, 22 lines, save this position, because we have to continue to modify the position below.

23 lines, randomly a font size, 24, 25 lines, randomly the label X/y initial direction.

27 line, go to set the current label font size, 28 lines, is to get the width and height of the label, and in the following stored in a two-dimensional array, why is a two-dimensional array, we have multiple tags, each label to save its width and height.

Finally, if we don't see the declaration Labelview is still, call the Run method.

Continue to follow the code to see the internal organs of the Run method.

/** * Open sub-thread constantly refreshes position and postinvalidate */private Void Run () {if (Mthread! = null && mthread.isalive ()) {return;} Mthread = new Thread (mstartrunning); Mthread.start ();}

5~7 line, if the thread is already turned on, direct return prevents multiple threads from coexisting, and the consequence is that the label is getting faster.

9, 10 lines, go to start a thread, and have a mstartrunning runnable parameter.

So let's continue to look at this runnable.

Private Runnable mstartrunning = new Runnable () {@Overridepublic void Run () {for (;;) {systemclock.sleep); for (int i = 0; i < mlabels.length; i++) {if (mlocations[i][0] <= getpaddingleft ()) {Mdirect Ions[i][0] = direction_right;} if (Mlocations[i][0] >= getmeasuredwidth ()-Getpaddingright ()-mtextwidthandheight[i][0]) {mDirections[i][0] = Direction_left;} if (Mlocations[i][1] <= getpaddingtop () + mtextwidthandheight[i][1]) {mdirections[i][1] = Direction_bottom;} if (Mlocations[i][1] >= getmeasuredheight ()-Getpaddingbottom ()) {mdirections[i][1] = direciton_top;} int xspeed = 1;int Yspeed = 2;if (i < mspeeds.length) {xspeed = Mspeeds[i][0];yspeed = Mspeeds[i][1];} else {xspeed = Mspeeds[i-mspeeds.length][0];yspeed = Mspeeds[i-mspeeds.length][1];} Mlocations[i][0] + = mdirections[i][0] = = Direction_right? XSpeed:-xspeed;mlocations[i][1] + = mdirections[i][1] = = Direction_bottom? Yspeed:-yspeed;} Postinvalidate ();}};

This runnable is really the key to the implementation of the tag cloud, we are in this thread to modify the location of each label, and notify the view to redraw.

And can see, in the Run is a dead loop, so that our label can be endless movement, the next is to let the thread to rest 100ms, can not kept to move it, too fast is not good, but also to consider performance issues.

Next line 7th, go through all the tags, 8~23 line, by judging the current position is not reached a certain boundary, if it is, then the direction of change to the opposite direction, for example, now to the top of the view, then the label will have to move down.

25, 26 lines, the default x/y speed, why is said to be the default, because each tab x/Y speed we can be set through the method.

Next 28~34, make a judgment, the general meaning is: If you set the total speed of the current label in the label s position, then go to the corresponding position speed, otherwise, get the speed back from the front.

36, 37 line is based on the X/Y direction to modify the current label coordinates.

Finally, call the Postinvalidate (), notify the view to refresh the interface, here is the use of postinvalidate () because we are called in the thread, remember.


Postinvalidate (), Must go OnDraw () to draw these labels, then we will take a look at OnDraw bar.


@Overrideprotected void OnDraw (canvas canvas) {if (!hascontents ()) {return;} for (int i = 0; i < mlabels.length; i++) {mpaint.settextsize (mfontsizes[i]); if (I < mcolorschema.length) Mpaint.setco Lor (Mcolorschema[i]); else Mpaint.setcolor (Mcolorschema[i-mcolorschema.length]); Canvas.drawtext (MLabels[i), Mlocations[i][0], mlocations[i][1], mpaint);}}

Come up or judge a bit, if not set the label, return directly. If there is a tag, then go through all the tags and set the corresponding font size, remember? We randomly set the font size of each label at the time of initialization, then go to the color of the label, an if else principle and set the speed that is the same, the key is the following, called Canvas.drawtext () to draw the label on the screen, In the mlocations we have saved the location of each label, and we are constantly modifying the location in the thread.

Here, in fact, our labelview on the move, but the number of settings tag, speed, color method and said. Actually, it's very simple, take a look.

/** * Set Labels * @see setlabels (string[] labels) * @param labels */public void setlabels (list<string> labels) {Setla BELs ((string[]) Labels.toarray ());} /** * Set Labels * @param labels */public void setlabels (string[] labels) {mlabels = Labels;mlocations = new Int[labels.leng th][2];mfontsizes = new Int[labels.length];mdirections = new Int[labels.length][2];mtextwidthandheight = new int[ Labels.length][2];mspeeds = new Int[labels.length][2];for (int speed[]: mspeeds) {speed[0] = speed[1] = 1;} Requestlayout ();} /** * Set color scheme * @param colorschema */public void Setcolorschema (int[] colorschema) {mcolorschema = Colorschema;} /** * Set x/y speed per item * <p> * speeds.length > Labels.length Ignore extra * <p> * Speeds.length < Labels.length will Re-use *  * @param speeds */public void setspeeds (int[][] speeds) {mspeeds = speeds;}

The only thing that can be said about these egg-hurting methods is Setlabels (string[] labels), because there is a bit of work in this method. Careful observation, this method in addition to set the label s, the other is to initialize a few arrays, all of what, believe should be very clear, there is here we initialize the default speed of 1.


Just came up to do the demonstration, Labelview can also item click, how is this done? The common onclick is certainly not possible, because we do not know the X/y coordinates of the click, so we can only start with ontouchevent.

@Overridepublic boolean ontouchevent (motionevent ev) {switch (ev.getaction ()) {Case MotionEvent.ACTION_DOWN:mDownX = ( int) ev.getx (); mdowny = (int) ev.gety (); mdownindex = GetClickIndex (); break;case MotionEvent.ACTION_UP:int nowx = (int) EV . GetX (); int nowy = (int) ev.gety (); if (Nowx-mdownx < Mtouchslop && Nowy-mdowny < mtouchslop&& MD Ownindex! =-1 && mlistener! = null) {Mlistener.onitemclick (Mdownindex, Mlabels[mdownindex]);} Mdownx = Mdowny = Mdownindex = -1;break;} return true;}

In Ontouch we only care about the down and up events, because one click is the combination of down and up.

In the down, we get the X/y coordinates of the current event, and get the item that is currently clicked, which is currently obtained through the GetClickIndex () method, which is said later; look up, in the up, we pass the current X/Y and the X/y comparison in the Down , if the distance of the two points is less than the system thought of the minimum sliding distance, can be described as click Effective, if you down, pull a long line, then up, that is certainly not a valid click, of course, click Effective still can not explain everything, only hit the label to Line, So also to determine whether Mdownindex is a valid value, and then if you set the ItemClick, go back to it.

How did the Mdownindex get it? Let's GetClickIndex () a probe.

/** * Gets the position of the label currently clicked * @return label position, no point returned-1 */private int getclickindex () {rect downrect = new Rect (); Rect locationrect = new rect (); for (int i=0;i<mlocations.length;i++) {Downrect.set (mdownx-mtextwidthandheight[i][0 ], mdowny-mtextwidthandheight[i][1], mdownx+ mtextwidthandheight[i][0], mdowny+ mtextwidthandheight[i][1]); Locationrect.set (Mlocations[i][0], mlocations[i][1],mlocations[i][0] + mtextwidthandheight[i][0],mlocations[i][1] + mtextwidthandheight[i][1]); if (Locationrect.intersect (Downrect)) {return i;}} return-1;}

First defines two rect, one is a click of Rect, the other is a rect of the label, and then goes through the position of the most recent save of each label, in the loop, we set the down rectangle by the Rect.set () method, respectively, up and down, and the current label up or down, Then through the Rect.intersect () method to determine whether the two rectangles have a intersection, there is a intersection to prove that click on the label, directly return the label in the label s position, if not return-1 means you mess!


Well, to here, the whole labelview is ready, hurriedly to download the source experience a bar, of course, is not very perfect, perfect solution and so on when it is used to solve, hey, anyway, we already have a train of thought.

Oh, yes, I haven't given the source yet, look here.



Android Tag Cloud: LabelView

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.