1, yesterday saw a very good UI effect, is to use the Bezier curve to achieve, and everyone to share, and, in the blog when I often will do some of the effects of some of the problems to write, and not like many articles directly on the solution, here to explain to you, Here to write some of the problems I encountered is not to make up the whole article of the word count, but I hope we can know from the root of how it is solved, and not you directly Baidu search this problem to solve the code, well, said so much, just want to tell everyone, I will be in the process to raise a lot of problems (evil face, hey hey Take a look at today's results:
2, What's the fuck? Is that what you're saying that's a good-looking effect? You crossing don't worry, here little brother also can not find a good UI diagram, can only ask you to do it, OK, when we see this effect, we have some ideas, as follows:
1, use paint to draw the sine function (call Math.sin (x) method) 2, using frame-wise animation to achieve 3, using Bessel Third order to achieve the wave effect
Maybe there are more and better ways, and this is just a few of the things I can think of, I am using Bezier today, it is not clear that the use of Bezier students can be found in the series of my blog classification of this column.
OK, let's not take care of those animations, we take the step-by-step, then we have only two parts of the view, one is the pink with the water area, one is our middle with moving icon picture, then we first to achieve the first pink with water, we finally achieve the effect of the following:
OK, for the sake of the extensibility of our control, we are here to customize some properties, here we classmate can not understand this piece (and then see this piece after all understanding)
<?xml version= "1.0" encoding= "Utf-8"?><resources> <declare-styleable name= "Waveview" > <!--picture of the middle boat --<attr name= "Imagebitmap" format= "reference" ></attr> <!--water level is rising-- > <attr name= "Rise" format= "boolean" ></attr> <!--water Ripple to the right when it's time to execute-- <attr Name= "duration" format= "integer" ></attr> <!--the y-coordinate of the starting point and <attr name= "Originy" format= " Integer ></attr> <!--water ripple height-- <attr name= "waveheight" format= "integer" ></attr > <!--water ripple Length-- <attr name= "Wavelength" format= "integer" ></attr> </ Declare-styleable></resources>
Create a Waveview class, inherit from view, and initialize some custom properties, here are two important properties one is the highest point of the sine, that is, the height of our water ripple; one is the length of our sine, that is, the length of the horizontal axis of a ripple of water, the following is the initialization of some properties, very simple , nothing hard.
A reference to the middle boat picture private int imagebitmap; The actual boat Bitmap private Bitmap Bitmap; Whether the rising water level private Boolean rise; Water level starting point private int originy; The execution time of the ripple Shift is private int duration; The width of the ripple private int wavewidth; The height of the ripple private int waveheight; Brush private paint mpaint; Path private path MPath; The width height of the control is private int width; private int height; Public Waveview (Context context) {This (context, NULL); } public Waveview (context context, @Nullable AttributeSet attrs) {This (context, attrs, 0); } public Waveview (context context, @Nullable attributeset attrs, int defstyleattr) {Super (context, Attrs, defst YLEATTR); Init (context, attrs, defstyleattr); } private void init (context context, AttributeSet attrs, int defstyleattr) {TypedArray a = context.obtainstyled Attributes (Attrs, R.styleable.waveview); Imagebitmap = A.getresourceid (r.styleable.waveview_imagebitmap, 0); Rise = A.getboolean (R.styleable.waveview_rise, false); Duration = A.getint (r.styleable.waveview_duration, 2000); Originy = A.getint (R.styleable.waveview_originy, 500); Wavewidth = A.getint (r.styleable.waveview_wavelength, 500); Waveheight = A.getint (r.styleable.waveview_waveheight, 500); A.recycle (); Compress picture Bitmapfactory.options Options = new Bitmapfactory.options (); Options.insamplesize = 2; Compress picture Multiples if (Imagebitmap > 0) {bitmap = Bitmapfactory.decoderesource (Getresources (), imagebitmap,opt ions); } else {bitmap = Bitmapfactory.decoderesource (Getresources (), r.mipmap.ic_launcher, options); }//Initialization brush mpaint = new Paint (); Mpaint.setcolor (Getresources (). GetColor (r.color.coloraccent)); Mpaint.setantialias (TRUE); Mpaint.setstyle (Paint.Style.FILL_AND_STROKE); Initialization path MPath = new Path (); }
Then rewrite the onmeasure to measure the height of our space, here is basically using the system to measure the width of the height, that is, when the height of wrap_content set 800px, here the code is very simple, not much explanation, directly on the code
@Override protected void onmeasure (int widthmeasurespec, int heightmeasurespec) { super.onmeasure ( Widthmeasurespec, Heightmeasurespec); int widthmode = Measurespec.getmode (Widthmeasurespec); Gets the wide pattern int heightmode = Measurespec.getmode (HEIGHTMEASURESPEC);//Gets the high mode int widthsize = Measurespec.getsize (WIDTHMEASURESPEC); Gets the wide dimension int heightsize = measurespec.getsize (HEIGHTMEASURESPEC);//Gets the high size if (Widthmode = = measurespec.exactly) { width = widthsize; } if (Heightmode = = measurespec.exactly) { height = heightsize; } else { height = n; } Save measurement results setmeasureddimension (width, height); }
Go ahead, rewrite the OnDraw method, note that this is the focus of the whole blog today, first of all we know to use Bessel Third order to achieve, so we can basically write the following code:
@Override protected void OnDraw (canvas canvas) { super.ondraw (canvas); Constant calculation of the path of the Wave Calculatepath (); Draw Water Part Canvas.drawpath (MPath, mpaint);}
The key is the logical processing in our Calculatepath () method, which is the direct use of Bezier, first we translate our drawing start point to the location of our custom Originy property
Mpath.moveto (0, Originy);
Then, judging by our width length and the length of the waveheight, how many sinusoidal curves are plotted on the screen?
for (int i =-wavewidth; I < width + Wavewidth; i + = wavewidth) { //use Sanche Besel curve to draw mpath.rcubicto (???? );}
OK, here we draw the whole idea is no problem, the key to our third-order Bezier curve two control points and an end point of the coordinates of the confirmation (here do not know what is the control point and the end point of the classmate of the recommendation you first go to see my blog's Bessel Basics)
Here please look at the four points that I marked in are our starting point, Control point 1, Control point 2, end point, OK, so we can write the following code:
Mpath.moveto (0, originy); Draw the wave for (int i =-wavewidth; I < width + Wavewidth; i + = wavewidth) { ////Draw Mpath.rcubicto with Sanche Besel curve wavew IDTH/4,-waveheight, WAVEWIDTH/4 * 3, Waveheight, wavewidth, 0); }
OK, here we go. We can look at the effect of our Bessel, as follows:
The curve is a little light, but it's still drawn, but it feels like the third-order curve here is still a little bit out of line with our imaginary sine lines, and we're going to change the third order to two second-degree tests.
Mpath.moveto (0, originy); Draw wave for (int i =-wavewidth; I < width + Wavewidth; i + = wavewidth) { //Sanche Besel curve drawing// Mpath.rcubicto (WAV EWIDTH/4,-waveheight, WAVEWIDTH/4 * 3, Waveheight, wavewidth, 0); Draw mpath.rquadto with quadratic Bezier curve (WAVEWIDTH/4,-waveheight, WAVEWIDTH/2, 0); Mpath.rquadto (WAVEWIDTH/4, Waveheight, WAVEWIDTH/2, 0); }
As follows:
OK, no problem, so the result is almost the same as the effect, we continue to realize that the following water is filled with fill then we also need to draw this three-wire (yellow mark), so as to form a closed area.
Logic is simple, I'm going to go straight to the code.
Draw lines mpath.lineto (width, height); Mpath.lineto (0, height); Mpath.close ();
Look again.
no problem, we have succeeded here today One-third of our task, we continue to realize, now we think about how to make our water ripple, there must be a reunion said, that certainly attribute animation Ah, right, yes, is the use of property animation, but, how to use? Where to use is a problem (the first difficulty comes)!!
The idea here is to change the starting coordinates of the wavelength we're drawing and set (-wavewidth,originy) as the coordinates, why? Because we're going to draw one more wavelength of water on the far left (there's a bug here, so we're going to draw one more wavelength on the far right, Specific explanation of the fancy label), and then translate through the property animation (and not only repeat the length of a perimeter), so that we can achieve the animation effect,
so the code is modified as follows:
Mpath.moveto (-wavewidth + dx, originy); for (int i =-wavewidth; I < width + Wavewidth; i + = wavewidth) { //draw with Sanche Besel curve// Mpath.rcubicto (WAVEWIDTH/4, -waveheight, WAVEWIDTH/4 * 3, Waveheight, wavewidth, 0); Draw mpath.rquadto with quadratic Bezier curve (WAVEWIDTH/4,-waveheight, WAVEWIDTH/2, 0); Mpath.rquadto (WAVEWIDTH/4, Waveheight, WAVEWIDTH/2, 0); } Draw lines mpath.lineto (width, height); Mpath.lineto (0, height); Mpath.close ();
OK, so we are writing a simple animation, dynamically changing the value of DX, thus changing our animation to the right (this involves property animation, but the knowledge is the most basic, we should be able to understand)
Start animation public void Startanimation () { animator = valueanimator.offloat (0, 1); Animator.setduration (duration); Animator.setrepeatcount (valueanimator.infinite); Animator.setinterpolator (New Linearinterpolator ()); Animator.addupdatelistener (New Valueanimator.animatorupdatelistener () { @Override public Void Onanimationupdate (Valueanimator animation) { float fraction = (float) animation.getanimatedvalue (); DX = (int) (wavewidth * fraction); Postinvalidate (); } }); Animator.start (); }
OK, here we can look at our animation effect, don't forget to call in the activity
Mwaveview = (Waveview) Findviewbyid (R.id.waveview); Mwaveview.startanimation ();
OK, so that our water ripple on the bottom, so we are almost finished one-second, we continue, now the poor is to draw our boat, first casually find a point to get out of the boat, and then in the back slowly consider the specific location of its placement, here I first write a fixed height 800
protected void OnDraw (canvas canvas) { super.ondraw (canvas); Constant calculation of the path of the Wave Calculatepath (); Draw Water Part Canvas.drawpath (MPath, mpaint); Draw the boat part Canvas.drawbitmap (bitmap,width/2,800,mpaint); }
Take a look at the effect
The picture is showing out, now is how to let him roll up and down with the waves, some students may say, ah-hang elder brother Oh, very simple ah, is also very obvious x coordinate is fixed, is the general width, y coordinate is next to its height of waves, directly engage a property animation, with the change of wave height and change.
Well, the key is how the coordinates of the wave next to it are calculated, which is the key point of the problem (this is our second difficulty in achieving this effect)
Here is a way of thinking that we draw a perpendicular bisector, that is, the Blue line and every time our water ripples intersect the point where the picture of our boat is placed
Now the idea is clear, now is to find this intersection, then the path class in Android is there any way to get this worth it? Very clear to tell you no, now here we have a broken train of thought, but I tell you here there is a region class can be replaced by the implementation of this effect (because the length is very long, this will not be detailed with the region Class), the interpretation of this class is to obtain the intersection of two regions, For example, the small rectangular area below the graph is the intersection of our large rectangular and water ripples.
We think about the limits of mathematics, when we are outside the large rectangular area around the coordinates of the wireless near the time we can see the rectangle is a straight line, so that we meet the requirements of the previous
The idea is clear, let's look at the code
float x = WIDTH/2; Region = new Region (); Region clip = new region ((int) (x-0.1), 0, (int) x, height); Region.setpath (MPath, clip);
It is important to note that before you draw the Bezier curve, draw the other three lines (this is a pit, everyone should pay attention to)
Take a look at the rectangular area and set the coordinates of the picture (here I get the coordinates and the coordinates of the rectangle directly)
@Override protected void OnDraw (canvas canvas) { super.ondraw (canvas); Constant calculation of the path of the Wave Calculatepath (); Draw Water Part Canvas.drawpath (MPath, mpaint); Gets where the current boat should be in rect rect = Region.getbounds (); Canvas.drawbitmap (Bitmap, Rect.right, Rect.top, mpaint); }
Take a look at the effect
The effect is roughly out, some students may say that this is because bitmap's starting point is not his central point, then we continue to modify the changes
Canvas.drawbitmap (Bitmap, Rect.right-(Bitmap.getwidth ()/2), rect.top-(Bitmap.getheight ()/2), mpaint);
And look at the effect.
This time looks more comfortable, the general deviation is not a problem, but at the trough when there is still a bit of a problem, what is the reason, here, we still have a little deviation, when the y-coordinate is greater than originy, We use rect.bottom here to get the value will be more accurate, when the y-coordinate is less than originy, we use rect.top here to get the value will be more accurate (we seriously think about, here is actually very good to understand)
Gets where the current boat should be in rect rect = Region.getbounds (); LOG.I ("Wangjitao", "right:" + Rect.right + ", Top:" + rect.bottom); if (Rect.top < Originy) { canvas.drawbitmap (bitmap, Rect.right-(Bitmap.getwidth ()/2), rect.top-( Bitmap.getheight ()/2), mpaint); } else { Canvas.drawbitmap (bitmap, Rect.right-(Bitmap.getwidth ()/2), rect.bottom-(Bitmap.getheight ()/2), Mpaint) ; }
The effect is as follows:
OK, now our coordinates are exactly right, no problem, fix it.
In fact, there is a better extension of the small effect, as follows:
1, provide just came in when the swollen effect 2, the ship's water ripple when the direction of the ship with the ripple tangent parallel (here to use the derivation of sin, can I forget it)
These functions here do not and everyone to achieve, we can go down to their own realization, today is late, but the dry goods are still quite a lot of, I hope we understand well, especially when we encounter problems when the solution, this is very important. Not much to say, to sleep. See you Next time .....
Android-Bezier for water-ripple animation (Focus!!) )