The following figure. It's not easy to implement on Android, and some effects are not as cool as the web side. )
Our Demo,ac-Niang town Building
(The picture is very slag, also ignores below seekbar, this is not the key)
Some animation, the effect is not recorded, we can go to the fish web side to see, and then download the demo to see, the effect is still possible.
Code Transfer Gate:
Https://github.com/mcxtzhang/SwipeCaptcha
Our demo and Web end are basically the same.
Then this control contains not only the following features:
Random area Start (upper left corner x,y) generates a CAPTCHA shadow. Verification Code Puzzle concave and convex graphics will be randomly transformed. The validation code area is wide and customizable. Pull up the verification code area and draw a verification code slider for linkage sliding. The validation fails, flashes a few seconds, and then returns to the original point. Successful verification, there will be white light sweep of the animation.
To decompose the core of the verification code to achieve the idea:
Control inherits from ImageView. Reason:
1 if used in the project, the verification code picture is expected to be an interface return. ImageView and its subclasses support fancy-loading pictures.
2 inherits from the ImageView, draws the picture itself does not need us to intervene, also does not need us to worry about ScaleType, saves a lot of work. In the onSizeChanged()
method
Build and control-height related property values:
1 randomly generated authentication code region start when initializing
2 Build authentication Code area Path
3 when the slider bitmap onDraw()
is generated, draw it sequentially:
1 Verification Code Shadow
2 Slide Block
The core work is above, but realize still have a lot of pits, the following step by step.
Validation code Area generation
Here I omit several basic steps for customizing view:
Get the Attrs property in the Attrs.xml definition property in the view constructor some Paint,path initialization work
Complete code in
Https://github.com/mcxtzhang/SwipeCaptcha
Can be downloaded after reading, the effect is better.
First think, the Verification code area contains:
To draw a captcha shadow on a picture
Removable Verification Code Slider
1 Generate Authenticode Shadow
We use the path to store the authentication code area,
So the most important step is to generate the path for the authentication code area.
View the auction (fish Web side) as follows,
So, we are going to draw a rectangle + four edges may have a random bump, bump can be replaced by semicircle.
We write as follows:
Code with annotations, gap refers to the starting point of the bump and the distance between the vertices.
Build verification code path private void Createcaptchapath () {//originally intended to randomly generate gap, and later found that the width/3 effect is better, int gap = Mrandom.nextint (mcaptchawidth
/2);
Gap = MCAPTCHAWIDTH/3;
Randomly generated verification code shadow x y point in upper left corner, Mcaptchax = Mrandom.nextint (MWIDTH-MCAPTCHAWIDTH-GAP);
Mcaptchay = Mrandom.nextint (MHEIGHT-MCAPTCHAHEIGHT-GAP);
Mcaptchapath.reset ();
Mcaptchapath.lineto (0, 0);
Start drawing an irregular shadow Mcaptchapath.moveto (Mcaptchax, Mcaptchay) from the upper left corner;//upper left corner Mcaptchapath.lineto (Mcaptchax + Gap, Mcaptchay); Draw a randomly concave circle drawpartcircle (new PointF (Mcaptchax + Gap, Mcaptchay), new PointF (Mcaptchax + gap * 2, Mcaptchay), mcapt
Chapath, Mrandom.nextboolean ()); Mcaptchapath.lineto (Mcaptchax + mcaptchawidth, mcaptchay);//upper right corner Mcaptchapath.lineto (Mcaptchax + mCaptchaWidth,
Mcaptchay + gap); Draw a randomly concave circle drawpartcircle (new PointF (Mcaptchax + mcaptchawidth, Mcaptchay + Gap), new PointF (Mcaptchax + mcaptchawid
Th, Mcaptchay + gap * 2), Mcaptchapath, Mrandom.nextboolean ()); Mcaptchapath.lineto (Mcaptchax + mcaptchawidth, McaptchAY + mcaptchaheight);//Lower right corner Mcaptchapath.lineto (Mcaptchax + mcaptchawidth-gap, Mcaptchay + mcaptchaheight); Draw a randomly concave circle drawpartcircle (new PointF (Mcaptchax + mcaptchawidth-gap, Mcaptchay + mcaptchaheight), new PointF (MCAPTC
Hax + mcaptchawidth-gap * 2, Mcaptchay + mcaptchaheight), Mcaptchapath, Mrandom.nextboolean ()); Mcaptchapath.lineto (Mcaptchax, Mcaptchay + mcaptchaheight);//lower left corner Mcaptchapath.lineto (Mcaptchax, MCaptchaY +
MCAPTCHAHEIGHT-GAP); Draw a randomly concave circle drawpartcircle (new PointF (Mcaptchax, Mcaptchay + mcaptchaheight-gap), new PointF (Mcaptchax, Mcaptchay +
MCAPTCHAHEIGHT-GAP * 2), Mcaptchapath, Mrandom.nextboolean ());
Mcaptchapath.close (); }
About drawPartCircle()
, its function is to pass in the start point, the end coordinates, and need concave or convex, and draw path. It draws a concave, convex semicircle on the path.
The code is as follows:
/** * Incoming start, endpoint coordinates, bump, and path. * will automatically draw a concave and convex half arc * * * @param start coordinates * @param end endpoint coordinates * @param path semicircle will be drawn on this path * @param outer is convex semicircle * * * Public St
atic void Drawpartcircle (PointF start, PointF end, path path, Boolean outer) {float c = 0.551915024494f;
Midpoint PointF middle = new PointF (Start.x + (end.x-start.x)/2, Start.y + (END.Y-START.Y)/2);
Radius float r1 = (float) math.sqrt (Math.pow (Middle.x-start.x), 2) + Math.pow ((MIDDLE.Y-START.Y), 2);
Gap value Float GAP1 = r1 * C;
if (start.x = = end.x) {///whether to draw the vertical direction is from top to bottom boolean toptobottom = end.y-start.y > 0? true:false;
Here's what I did when I wrote all the formulas, and don't ask me about the process, sensed.
int flag;//rotation coefficient if (toptobottom) {flag = 1;
else {flag =-1;
} if (outer) {//Convex two semicircle path.cubicto (start.x + GAP1 * flag, START.Y, middle.x + r1 * Flag, MIDDLE.Y-GAP1 * flag,
middle.x + r1 * flag, MIDDLE.Y);
Path.cubicto (middle.x + r1 * Flag, MIDDLE.Y + GAP1 * flag, End.X + GAP1 * flag, END.Y, end.x, END.Y); else {//concave two semicircle path. Cubicto (START.X-GAP1 * flag, START.Y, MIDDLE.X-R1 * flag, MIDDLE.Y-GAP1 * flag, MIDDLE.X-R1 * flag, MIDDLE.Y)
;
Path.cubicto (MIDDLE.X-R1 * flag, MIDDLE.Y + GAP1 * flag, END.X-GAP1 * flag, END.Y, end.x, END.Y);
} else {//Draw horizontal//Whether it is from left to right Boolean lefttoright = end.x-start.x > 0 true:false;
Here's what I did when I wrote all the formulas, and don't ask me about the process, sensed.
int flag;//rotation coefficient if (lefttoright) {flag = 1;
else {flag =-1;
} if (outer) {//Convex two semicircle path.cubicto (start.x, START.Y-GAP1 * flag, MIDDLE.X-GAP1 * flag, MIDDLE.Y-R1 * flag,
middle.x, MIDDLE.Y-R1 * flag);
Path.cubicto (middle.x + GAP1 * flag, MIDDLE.Y-R1 * flag, end.x, END.Y-GAP1 * flag, end.x, END.Y); else {//concave two semicircle path.cubicto (start.x, Start.y + GAP1 * flag, MIDDLE.X-GAP1 * flag, MIDDLE.Y + r1 * flag, middle.
X, MIDDLE.Y + r1 * flag);
Path.cubicto (middle.x + GAP1 * flag, MIDDLE.Y + r1 * flag, End.X, END.Y + GAP1 * flag, end.x, END.Y); }/* The formula before derivation is here if (Start.x < end.x) {if (outer)
{//upper left semicircle clockwise Path.cubicto (Start.x, Start.y-gap1, Middle.x-gap1, Middle.y-r1, middle.x, MIDDLE.Y-R1);
Upper right semicircle: Clockwise Path.cubicto (middle.x + gap1, MIDDLE.Y-R1, End.X, End.y-gap1, End.X, END.Y); else {//lower left semicircle counterclockwise path.cubicto (start.x, Start.y + gap1, MIDDLE.X-GAP1, Middle.y + r1, middle.x, middle.y + R1)
;
Lower right semicircle counterclockwise path.cubicto (middle.x + gap1, middle.y + r1, end.x, End.y + gap1, end.x, END.Y); } else {if (outer) {//Next right semicircle clockwise Path.cubicto (start.x, Start.y + gap1, middle.x + gap1, middle.y + r1, middle.)
X, Middle.y + R1);
Lower left semicircle clockwise Path.cubicto (MIDDLE.X-GAP1, middle.y + r1, end.x, End.y + gap1, end.x, END.Y); }
}*/
}
}
Here is the derivation of the formula, not before the deduction is also in the note.
To put it simply, the midpoint and radius are calculated first, and a circle is drawn using three Bezier curves (c and GAP1 are all related to the three Bezier curves). About three times the Bezier curve is not expanded, a lot of information on the Internet, I am also now learning.
Here about the draw verification code shadow path, there is a tortuous course of thinking, the effect of drawing out the following:
On the left is the slider, the right is the shadow
Mind Course (can not see):
Verification code path, a fierce look, it seems very simple, is not a rectangle + four sides may appear on the bump.
Concave and convex words, we are drawing a semicircle well.
Path
the use of the lineTo()
+ seems to addCircle()
be easy to achieve?
At first I did this, and it turns out that the path is a multiple path, closed, unable to form a full shaded area. More cannot be used for the next validation code slider bitmap generation.
Well, it seems to be addCircle()
the pot that caused the path to be split into multiple segments. Then I used it arcTo()
, the result arcTo
is not like addCircle()
that can set the direction of the drawing, (clockwise, counterclockwise), which at that time I was puzzled, because can not counter-clockwise, the upper and right concave can not draw out. So I gave up, and I switched to 贝塞尔曲线
drawing this bump.
The article wrote here, I suddenly found that I was retarded, sweepangle incoming negative can not be counterclockwise. Such as:arcTo(oval, 180, -180);
So blogging is a great benefit, blog when the brain is also a high-speed rotation, because for fear of writing mistakes, one is misleading others, two is a disgrace. The high speed of the brain might have figured out a problem that had not been thought through before.
So I use sin+ Hilleber curve to draw this semicircle, why use them? I used the sin function + Hill Bessel to simulate the wave when I was drawing the wave, so I solved the inertia thinking. What's the result? Draw out the bump is not round ah, sin function is not even than the circle is not.
So I went on to simulate the circular path with the Bezier curves.
It seems that when I first wrote this piece of code, the brain is really not very clear, but also a harvest. I reviewed several functions and Bezier curves of path.
2 Drawing: Verification Code Slider generation
Verify code path is generated, I want to generate the verification code slider according to path. So the first step is to dig up the picture.
The code is as follows:
//generate slider private void Craetemask () {Mmaskbitmap = Getmaskbitmap (((bitmapdrawable) getd
Rawable ()). Getbitmap (), Mcaptchapath);
Slider Shadow Mmaskshadowbitmap = Mmaskbitmap.extractalpha ();
The drag displacement resets mdrageroffset = 0;
Isdrawmask draw failed flashing animation with isdrawmask = true; //Cutout Private Bitmap Getmaskbitmap (Bitmap mbitmap, Path mask) {//to the control wide high create piece Bitmap Bitmap tempbitmap = Bitmap.cre
Atebitmap (Mwidth, Mheight, Bitmap.Config.ARGB_8888);
The created bitmap as the artboard Canvas Mcanvas = new Canvas (TEMPBITMAP);
Serrated and can not be resolved, so replace the Xfermode method to do//mcanvas.clippath (mask); Anti-aliasing Mcanvas.setdrawfilter (new Paintflagsdrawfilter (0, Paint.anti_alias_flag |
Paint.filter_bitmap_flag));
Draws a circular mcanvas.drawpath (mask, mmaskpaint) for the mask;
Set mask mode (image blending mode) Mmaskpaint.setxfermode (Mporterduffxfermode);
★ Take into account the scaletype and other factors, to use the matrix to bitmap scaling Mcanvas.drawbitmap (Mbitmap, Getimagematrix (), mmaskpaint);
Mmaskpaint.setxfermode (NULL);
return tempbitmap; }
In fact, I also walked a number of twists and turns, I first used Canvas.clippath (path) to pull the map, the results found that there are jagged, a lot of information has not been done. So I went back to the xfermode of the road, set it to Mporterduffxfermode = new Porterduffxfermode (PorterDuff.Mode.SRC_IN);
The first DST, which is the Mask verification code path, and then draws the Src:bitmap, takes the intersection to complete the cutout.
Here are some areas to be noted:
The bitmap of SRC is to take the bitmap of ImageView itself.
The height of the width of the newly created bitmap control
The wide height of the two may be different, which is the function of the ImageView parameter scaletype. So we remove the ImageView matrix to draw the bitmap of Src. This bitmap area is the same as the 1th step covering the area.
Mmaskshadowbitmap = Mmaskbitmap.extractalpha (); This is to draw a circle of shadows around the drawn slider to enhance the stereo effect.
Take a closer look at the effect of the image, surrounding another round of stereo shadow effect:
The OnDraw () method is actually simpler, except that some boolean-type flag are added to the animation-related:
The code is as follows:
@Override
protected void OnDraw (Canvas Canvas) {
super.ondraw (Canvas);
Inherited from ImageView, so Bitmap,imageview has helped us draw well.
//I only draw the part related to the verification code above,//
is in validation mode, false after the validation succeeds, and the rest is true
if (ismatchmode) {
///First draw the verification code shadow
if ( Mcaptchapath!= null) {
Canvas.drawpath (Mcaptchapath, mpaint);
}
Draw slider
//Isdrawmask draw failure flicker animation with
if (null!= mmaskbitmap && null!= mmaskshadowbitmap && isdrawma SK) {
///First Draw Shadow
Canvas.drawbitmap (Mmaskshadowbitmap,-mcaptchax + mdrageroffset, 0, mmaskshadowpaint);
Canvas.drawbitmap (Mmaskbitmap,-mcaptchax + mdrageroffset, 0, NULL);
}
Verified successfully, the white light swept through the animation, this piece of animation feels imperfect, there is increased space
if (Isshowsuccessanim) {
canvas.translate (msuccessanimoffset, 0);
Canvas.drawpath (Msuccesspath, msuccesspaint);
}
}
Mpaint is defined as follows: So there are shadow effects in drawing shadows.
Mpaint = new Paint (Paint.anti_alias_flag | Paint.dither_flag);
Mpaint.setcolor (0x77000000);
Mpaint.setstyle (Paint.Style.STROKE);
Sets the brush mask filter
mpaint.setmaskfilter (new Blurmaskfilter (BlurMaskFilter.Blur.SOLID));
It is worth saying that, with the slider sliding, is the use of Mdrageroffset, the default is 0, sliding when the mdrageroffset increase, the slider to the right, and vice versa.
Verify the success of the white Light swept through the animation, is made using Canvas.translate (), Msuccesspath and Msuccesspaint are as follows:
Msuccesspaint = new Paint ();
Msuccesspaint.setshader (New lineargradient (0, 0, width, 0, new int[]{
0x11ffffff, 0x88ffffff}, NULL,
Shader.TileMode.MIRROR));
Imitation bucket fish is a parallelogram rolling past
Msuccesspath = new Path ();
Msuccesspath.moveto (0, 0);
Msuccesspath.rlineto (width, 0);
Msuccesspath.rlineto (WIDTH/2, mheight);
Msuccesspath.rlineto (-width, 0);
Msuccesspath.close ();
Slide, verify, animate
After the last section is finished, our slide verification code view is ready to be drawn, and now we are adding a few ways for it to be linked to sliding, verifying functionality, and animating.
Linkage sliding:
As mentioned in the previous section, the slide is mainly to change the value of the mdrageroffset and then redraw itself->ondraw (), according to the Mdrageroffset offset slider bitmap drawing.
/**
* Reset the verification code slip distance (generally used for validation failure)/public
void Resetcaptcha () {
mdrageroffset = 0;
Invalidate ();
}
/**
* Maximum sliding value
* @return/
public
int Getmaxswipevalue () {
//return (bitmapdrawable) Getdrawable ()). Getbitmap (). GetWidth ()-mcaptchawidth;
Returns the control width return
mwidth-mcaptchawidth;
}
/**
* Set Current sliding value
* @param value
/public void setcurrentswipevalue (int value) {
Mdrageroffset = value;
Invalidate ();
}
Check:
Checksum, you need to introduce a callback interface:
Public interface Oncaptchamatchcallback {
void matchsuccess (Swipecaptchaview swipecaptchaview);
void matchfailed (Swipecaptchaview swipecaptchaview);
}
/**
* Verification Code Verification Callback
/private oncaptchamatchcallback oncaptchamatchcallback;
Public Oncaptchamatchcallback Getoncaptchamatchcallback () {return
oncaptchamatchcallback;
}
/**
* Set Verification code Verification callback
* *
@param oncaptchamatchcallback
* @return
/Public Swipecaptchaview Setoncaptchamatchcallback (Oncaptchamatchcallback oncaptchamatchcallback) {
This.oncaptchamatchcallback = Oncaptchamatchcallback;
return this;
}
/**
* * * checksum public
void Matchcaptcha () {
if (null!= oncaptchamatchcallback && ismatchmode) {
//Here The validation logic is by comparing, dragging the distance and verifying the code start x coordinates. Validation succeeds within the default 3DP.
if (Math.Abs (Mdrageroffset-mcaptchax) < mmatchdeviation) {
//Successful animation
Msuccessanim.start ();
} else {
mfailanim.start ();}}}
A successful, failed callback is notified at the end of the animation.
Animation:
The width of the animation is used, so it is called in the Onsizechanged () method.
Verify the animation initialization area private void Creatematchanim () {Mfailanim = valueanimator.offloat (0, 1);
Mfailanim.setduration. Setrepeatcount (4);
Mfailanim.setrepeatmode (Valueanimator.reverse); Flash a flash when you fail. Animation bucket fish is hidden-show-hide-Displays Mfailanim.addlistener (new Animatorlisteneradapter () {@Override public void onanimation
End (animator animation) {oncaptchamatchcallback.matchfailed (swipecaptchaview.this);
}
}); Mfailanim.addupdatelistener (New Valueanimator.animatorupdatelistener () {@Override public void onanimationupdate (
Valueanimator animation) {Float Animatedvalue = (float) animation.getanimatedvalue ();
if (Animatedvalue < 0.5f) {isdrawmask = false;
else {isdrawmask = true;
} invalidate ();
}
});
int width = (int) typedvalue.applydimension (Typedvalue.complex_unit_dip, Getresources (). Getdisplaymetrics ());
Msuccessanim = Valueanimator.ofint (mwidth + width, 0);
Msuccessanim.setduration (500);
Msuccessanim.setinterpolator (New Fastoutlinearininterpolator ()); MsuccessanIm.addupdatelistener (New Valueanimator.animatorupdatelistener () {@Override public void onanimationupdate (
Valueanimator animation) {msuccessanimoffset = (int) animation.getanimatedvalue ();
Invalidate ();
}
}); Msuccessanim.addlistener (New Animatorlisteneradapter () {@Override public void Onanimationstart (animator animation) {I
Sshowsuccessanim = true; @Override public void Onanimationend (animator animation) {oncaptchamatchcallback.matchsuccess (
Swipecaptchaview.this);
Isshowsuccessanim = false;
Ismatchmode = false;
}
});
Msuccesspaint = new Paint (); Msuccesspaint.setshader (New lineargradient (0, 0, width, 0, new int[]{0x11ffffff, 0x88ffffff}, NULL, Shader.TileMode.MIR
ROR));
Imitation bucket fish is a parallelogram rolling past msuccesspath = new Path ();
Msuccesspath.moveto (0, 0);
Msuccesspath.rlineto (width, 0);
Msuccesspath.rlineto (WIDTH/2, mheight);
Msuccesspath.rlineto (-width, 0);
Msuccesspath.close (); }
The code is very simple, some of the modified Boolean value flag, in the OnDraw () method will be used, combined with OnDraw () a look will understand.
Demo
This section, we linkage seekbar sliding up.
The XML is as follows:
<?xml version= "1.0" encoding= "Utf-8"?>
<relativelayout
...
>
<com.mcxtzhang.captchalib.swipecaptchaview
android:id= "@+id/swipecaptchaview"
android: Layout_width= "300DP"
android:layout_height= "150DP"
android:layout_centerhorizontal= "true"
Android: Scaletype= "Centercrop"
android:src= "@drawable/pic11"
app:captchaheight= "30DP"
app:captchawidth= "30DP"/>
<seekbar
android:id= "@+id/dragbar"
android:layout_width= "320DP"
android: layout_height= "60DP"
android:layout_below= "@id/swipecaptchaview"
android:layout_centerhorizontal= " True "
android:layout_margintop= 30dp"
android:progressdrawable= "@drawable/dragbg"
android:thumb = "@drawable/thumb_bg"/>
<button
android:id= "@+id/btnchange" android:layout_width= "WRAP_"
Content "
android:layout_height=" wrap_content "
android:layout_alignparentright=" true "
Android: text= "Boss Change Code"/>
</RelativeLayout>
The UI is the image of the first picture of the text,
Full activity code:
public class Mainactivity extends appcompatactivity {Swipecaptchaview mswipecaptchaview;
SeekBar Mseekbar;
@Override protected void OnCreate (Bundle savedinstancestate) {super.oncreate (savedinstancestate);
Setcontentview (R.layout.activity_main);
Mswipecaptchaview = (Swipecaptchaview) Findviewbyid (R.id.swipecaptchaview);
Mseekbar = (SeekBar) Findviewbyid (R.id.dragbar); Findviewbyid (R.id.btnchange). Setonclicklistener (New View.onclicklistener () {@Override public void OnClick (View v) {MS
Wipecaptchaview.createcaptcha ();
Mseekbar.setenabled (TRUE);
Mseekbar.setprogress (0);
}
}); Mswipecaptchaview.setoncaptchamatchcallback (New Swipecaptchaview.oncaptchamatchcallback () {@Override public void Matchsuccess (Swipecaptchaview swipecaptchaview) {toast.maketext (Mainactivity.this, "Congratulations on the success of the verification can do things", Toast.LENGTH_
Short). Show ();
Mseekbar.setenabled (FALSE); @Override public void matchfailed (Swipecaptchaview swipecaptchaview) {toast.maketext (Mainactivity.this, "You have 80% possible machinePerson, now go still in time ", Toast.length_short). Show ();
Swipecaptchaview.resetcaptcha ();
Mseekbar.setprogress (0);
}
}); Mseekbar.setonseekbarchangelistener (New Seekbar.onseekbarchangelistener () {@Override public void onprogresschanged (
SeekBar SeekBar, int progress, Boolean fromuser) {Mswipecaptchaview.setcurrentswipevalue (progress); @Override public void Onstarttrackingtouch (SeekBar SeekBar) {//Here is because the control Mseekbar.setmax (mswipecaptchaview.getmaxs
Wipevalue ()); @Override public void Onstoptrackingtouch (SeekBar SeekBar) {log.d ("ZXT", "Onstoptrackingtouch () called
= ["+ SeekBar +"]);
Mswipecaptchaview.matchcaptcha ();
}
}); Loading pictures from the network also OK Glide.with (this). Load ("Http://www.investide.cn/data/edata/image/20151201/20151201180507_281.jpg"). Asbitmap (). into (new simpletarget<bitmap> () {@Override public void Onresourceready (Bitmap resource, Glideanima tion<?
Super bitmap> Glideanimation) {mswipecaptchaview.setimagebitmap (Resource); MswipecaptcHaview.createcaptcha ();
}
}); }
}
Summarize
Code Transfer Door If you like, click on a star. Thanks a lot
Https://github.com/mcxtzhang/SwipeCaptcha
Contains full demo and Swipecaptchaview.
Using some tools to find web-side bucket fish, the captcha picture and the slider picture are all returned by the interface.
Speculate that the front end returns only the background: the percentage of the distance or distance the user is moving.
This example is fully implemented by the front-end verification code generation, verification function, because:
1 Practice customizing view, all of your own to pull the verification of the drawing, feel cool.
2 I will not do backstage, manual smile.
Core point:
1 generation of irregular graphics path.
2 Specifies path to bitmap, anti-aliasing.
3 Suitable for ImageView ScaleType.
4 successful, failed animations
The above is a small set to introduce the Android high imitation bucket fish slide Verification code, I hope to help you, if you have any questions please give me a message, small series will promptly reply to everyone. Here also thank you very much for the cloud Habitat Community website support!