Android custom control Series 5: Custom colorful water ripple effects, android water ripple

Source: Internet
Author: User
Tags gety

Android custom control Series 5: Custom colorful water ripple effects, android water ripple

Respect originality! Reprinted please indicate the source: http://blog.csdn.net/cyp331203/article/details/41114551


Today, we use Android custom controls to achieve an interesting effect: Sliding water ripple. Let's take a look at the final result:


Figure 1


The effect is still very dazzling; it takes a bit of food to students. Here we will divide the entire process into several steps for implementation.


1. Click "Water ripple" in a single circle:



Figure 2


As usual, it is still a custom control. Here we will directly fill the entire screen with this control. (If you are not familiar with the custom control, refer to my previous article:Android custom control series 2: Custom switch button (1)). Observe this effect and find that we should rewrite the onTouchEvent and onDraw methods to obtain the coordinates of the touch in the onTouchEvent, and then draw the image we need with this coordinate value as the center of the circle, this painting process is called onDraw method.


1. Create a project, define a WaterWave class, inherit the custom View, and use it as a custom control. Write the custom control in the configuration file and fill the parent form directly.


2. In the WaterWave class, implement its two-parameter constructor:
Package com. example. waterwavedemo. ui; import android. content. context; import android. graphics. canvas; import android. graphics. color; import android. graphics. paint; import android. OS. handler; import android. OS. message; import android. util. attributeSet; import android. view. motionEvent; import android. view. view; public class WaterWave extends View {... /** 1. Two-Parameter constructor */public WaterWave (Context context, AttributeSet attrs) {super (context, attrs); alpha = 0; radius = 0; initPaint ();}...}



3. To use a custom control, you generally need to specify its size. Here we only need to fill the form, so use the default onMeasure method:


/*** OnMeasure method: determines the control size. Use the default */@ Overrideprotected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stubsuper. onMeasure (widthMeasureSpec, heightMeasureSpec );}



4. Draw the custom image and rewrite the onDraw method. Here, because we need to draw a circle, write as follows:


@ Override/*** specifies the method for drawing the required image. This method is critical */protected void onDraw (Canvas canvas) {canvas. drawCircle (xDown, yDown, radius, paint );}



The xDown and yDown parameters are member variables, which represent the x and y coordinates of the press. The point corresponding to the coordinates is the center of the ring to be drawn. The radius parameter is also a member variable, represents the radius of the ring to be drawn;


We also need a paint, which is a Paint paint object. Here we define it as a member variable, because the onDraw method will be called when the first custom control is displayed, therefore, we need to initialize the paint in the constructor of the two parameters. Otherwise, a null pointer exception will be reported. Here we will write an initPaint () method to initialize our paint:

/*** Initialize paint */private void initPaint () {/** create a paint brush */Paint = new paint (); paint. setAntiAlias (true); paint. setStrokeWidth (width); // you can use the ring method to draw the paint. setStyle (Paint. style. STROKE); System. out. println ("alpha =" + alpha); paint. setAlpha (alpha); System. out. println ("Get transparency:" + paint. getAlpha (); paint. setColor (Color. RED );}


5. Touch timed refresh

After the onDraw method, we can draw this ring, but the actual problem is that we want to draw a ring at the position when clicking, so we certainly need to get the coordinates xDown and yDown when we click, so we must rewrite the onTouchEvent method. In addition, we need to make the transparency the most opaque (alpha = 255) When we press ), in the process of drawing, the radius of the ring is constantly expanded, while the transparency is constantly reduced until it is completely transparent (alpha = 0 ), this constantly changing process needs to refresh the status and redraw the image at intervals, so we use handler to handle it here:


@ Override/*** method of the touch event */public boolean onTouchEvent (MotionEvent event) {super. onTouchEvent (event); switch (event. getAction () {case MotionEvent. ACTION_DOWN: radius = 0; alpha = MAX_ALPHA; width = radius/4; xDown = (int) event. getX (); yDown = (int) event. getY (); handler. sendEmptyMessage (0); break; case MotionEvent. ACTION_MOVE: break; case MotionEvent. ACTION_UP: break; default: break;} return true ;}



We can see that here we only implement the logic in ACTION_DOWN, set the radius to 0 when each press, set the transparency alpha to completely opaque, and the width is also 0, after obtaining the x and y coordinates of the press, handler sends an empty message, allowing the handler to regularly refresh the status and draw the image, we want to make the transparency of the ring no longer refresh regularly when alpha reaches 0. Otherwise, we will refresh the status value for each handleMessage and then draw the image:


Private Handler handler = new Handler () {@ Overridepublic void handleMessage (Message msg) {super. handleMessage (msg); switch (msg. what) {case 0: flushState (); invalidate (); if (alpha! = 0) {// If the transparency does not reach 0, refresh continues. Otherwise, stop refreshing handler. sendEmptyMessageDelayed (0, 50);} break; default: break;}/*** refresh status */private void flushState () {radius + = 5; alpha-= 10; if (alpha <0) {alpha = 0;} // System. out. println ("alpha =" + alpha); width = radius/4; paint. setAlpha (alpha); paint. setStrokeWidth (width );}};




We can see that in handler, We overwrite the handleMessage method, in msg. when what is 0, we call the flushState () method to refresh the state, and the invalidate () method to draw the image, and then use handler. sendEmptyMessageDelayed (0, 50); repeat the above work every 50 milliseconds; invalidate () is provided by Android, and flushState () needs to be implemented by ourselves;

As per our needs, every time the status refresh job flushState (), we need to do the following:

(1) Increase the radius

(2) reduce the transparency and set it to paint;

(3) increase the ring width and set it to paint.

(4) for transparency, the maximum value is 255, but if the transparency is reduced to less than 0, for example,-1, the alpha value will not be-1, but 255 + (-1) = 254, so we need to add a judgment condition to prevent alpha <0


/*** Refresh status */private void flushState () {radius + = 5; alpha-= 10; if (alpha <0) {alpha = 0;} // System. out. println ("alpha =" + alpha); width = radius/4; paint. setAlpha (alpha); paint. setStrokeWidth (width );}



6. Add some initialization work to the constructor of the two parameters:


public WaterWave(Context context, AttributeSet attrs) {super(context, attrs);alpha = 0;radius = 0;initPaint();}



So far, our first step is basically complete.


2. enable multiple click rings to exist at the same time and refresh the results:


From the area picture, we can easily find that, no matter how you click it, the screen will only have a circle effect at the same time. This is because we reset the circle center every time we click it, in addition, all circular parameters are member variables that are shared. In addition, if the previous circle does not disappear, click again, it will greatly increase the speed of the new circle, because handler is used. sendEmptyMessageDelayed () method. This method is repeatedly triggered when you click it for the second time, so that the handler of the previous two clicks. sendEmptyMessageDelayed () overlaps to take effect, so the actual interval is much less than 50 milliseconds, so the refresh speed is much faster.

Now we need to solve the above two small problems to achieve the following results:


To solve these two problems:

1. For the sharing parameters of all water ripple circles:

The method is to create an internal Wave class to store the parameters of each circle. Each circle corresponds to a Wave object, and then redraws all the circle views in the onDraw method; A List set waveList is also required to store all the wave objects for convenient traversal.


2. the handler. sendEmptyMessageDelayed method is constantly called in subsequent clicks, resulting in faster and faster refresh.

Here, we can set a member variable boolean isStart; to indicate whether it is the first time to press; because when we press the variable for the first time, we certainly want to start timed refresh and call handler. sendEmptyMessageDelayed keeps the Ring State changing. However, for subsequent clicks, we only want them to be refreshed immediately and added to the waveList collection, instead of sending a handler message to call handler. sendEmptyMessageDelayed. So we set it to true at the beginning, and set it to false at the first click. When will it be set to false, the third problem is involved:


3. For the waveList set, if you keep clicking to add a Wave object to the set, this set will undoubtedly become larger and larger, which we do not want to see.

We want to remove the ring from the waveList when its transparency value alpha is 0, that is, completely transparent, so that it can be recycled by garbage collection, in this way, if you click a few points and the point will automatically disappear (the alpha value is reduced to 0), the corresponding Wave object will also be removed from the waveList, And the waveList size will also change to 0, at this time, we can stop handler. the sendEmptyMessageDelayed method can be called and set isStart to true again. When is isStart set to false? We can set it to false when the flushState is refreshed, because it indicates that the first click has been pressed. Then, under the ACTION_DWON condition of the onTouchEvent method, if isStart is true, the handler message will be sent. This means that the first click will not be sent, but the wave object will be added to the waveList, because isStart has been set to false when flushState is called for the first time.

The Code is as follows:

Package com. example. waterwavedemo. ui; import java. util. arrayList; import java. util. collections; import java. util. list; import android. content. context; import android. graphics. canvas; import android. graphics. color; import android. graphics. paint; import android. OS. handler; import android. OS. message; import android. util. attributeSet; import android. view. motionEvent; import android. view. view; public class WaterWave Extends View {/*** List of waveforms */private List <Wave> waveList;/*** maximum opacity, completely opaque */private static final int MAX_ALPHA = 255; protected static final int FLUSH_ALL =-1; private boolean isStart = true; // ** // * x coordinate when pressed // * // private int xDown; /// ** // * y coordinate when pressed // * // private int yDown; /// ** // * indicates the radius of the ring. // * // private float radius; // private int alpha; /** 1. Two-Parameter constructor */public WaterWave (Context con Text, AttributeSet attrs) {super (context, attrs); waveList = Collections. synchronizedList (new ArrayList <Wave> ();}/*** onMeasure method to determine the control size. The default */@ Overrideprotected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) is used here) {super. onMeasure (widthMeasureSpec, heightMeasureSpec);} @ Override/*** specifies the method for drawing the desired image. This method is more critical */protected void onDraw (Canvas canvas Canvas) {// redraw all rings for (int I = 0; I <waveList. size (); I ++) {Wave wave = waveList. get (I); canvas. drawCircle (wave. xDown, wave. yDown, wave. radius, wave. paint) ;}}/*** initialize paint */private Paint initPaint (int alpha, float width) {/** create a Paint brush */paint Paint = new paint (); paint. setAntiAlias (true); paint. setStrokeWidth (width); // you can use the ring method to draw the paint. setStyle (Paint. style. STROKE); // System. out. println ("alpha =" + alpha); paint. setAlpha (alpha); // System. out. println ("Get Transparency: "+ paint. getAlpha (); paint. setColor (Color. RED); return paint;} private Handler handler = new Handler () {@ Overridepublic void handleMessage (Message msg) {super. handleMessage (msg); switch (msg. what) {case 0: flushState (); invalidate (); if (waveList! = Null & waveList. size ()> 0) {handler. sendEmptyMessageDelayed (0, 50) ;}break; default: break ;}};/*** refresh status */private void flushState () {for (int I = 0; I <waveList. size (); I ++) {Wave wave = waveList. get (I); if (isStart = false & wave. alpha = 0) {waveList. remove (I); wave. paint = null; wave = null; continue;} else if (isStart = true) {isStart = false;} wave. radius + = 5; wave. alpha-= 10; if (wave. alpha <0) {wave. alpha = 0;} wave. width = wave. radius/4; wave. paint. setAlpha (wave. alpha); wave. paint. setStrokeWidth (wave. width) ;}// private Paint paint; // private float width; @ Override/*** method of touch event */public boolean onTouchEvent (MotionEvent event) {super. onTouchEvent (event); switch (event. getAction () {case MotionEvent. ACTION_DOWN: Wave wave = new Wave (); wave. radius = 0; wave. alpha = MAX_ALPHA; wave. width = wave. radius/4; wave. xDown = (int) event. getX (); wave. yDown = (int) event. getY (); wave. paint = initPaint (wave. alpha, wave. width); if (waveList. size () = 0) {isStart = true;} System. out. println ("isStart =" + isStart); waveList. add (wave); // click it and then click it to cleanse the image. invalidate (); if (isStart) {handler. sendEmptyMessage (0);} break; case MotionEvent. ACTION_MOVE: break; case MotionEvent. ACTION_UP: break; default: break;} return true;} private class Wave {int waveX; int waveY;/*** indicates the radius of the ring */float radius; Paint paint; /*** x coordinate */int xDown when pressing;/*** y coordinate */int yDown; float width; int alpha ;}}


3. Achieve full effect (Click and move, random color, and circle size change speed)

It is the same as Figure 1, mainly for a few small places:

1. Let ACTION_DOWN in onTouchEvent respond to the same event as ACTION_MOVE. In fact, it is to remove the break of ACTION_DOWN and then write the processing code to the subsequent ACTION_MOVE.


2. Create a new member variable array colors with the desired color in it. Then, use the paint when setting the color of the initPaint method. setColor (colors [(int) (Math. random () * (colors. length-1)]);


3. Control the changing trend of waveforms. This is my personal hobby. I am doing this: In flushState:


wave.radius += waveList.size() - i;wave.width = (wave.radius / 3);wave.paint.setStrokeWidth(wave.width);// wave.alpha -= 10;if (wave.alpha < 0) {wave.alpha = 0;}// wave.width = wave.radius / 4;wave.paint.setAlpha(wave.alpha);

This completes the custom water ripple effect. The problem is that if you slide quickly on the simulator, there will be a lag. On my mobile phone Nexus5, it is still smooth and should be unrelated to the memory, and some optimizations may be made in the future.

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.