├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── libraries │ ├── animated_vector_drawable_24_2_1.xml │ ├── appcompat_v7_24_2_1.xml │ ├── glide_3_7_0.xml │ ├── support_annotations_24_2_1.xml │ ├── support_compat_24_2_1.xml │ ├── support_core_ui_24_2_1.xml │ ├── support_core_utils_24_2_1.xml │ ├── support_fragment_24_2_1.xml │ ├── support_media_compat_24_2_1.xml │ ├── support_v4_24_2_1.xml │ └── support_vector_drawable_24_2_1.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── mcxtzhang │ │ └── swipecaptcha │ │ ├── CstView.java │ │ ├── MainActivity.java │ │ ├── TestSinView.java │ │ └── TestThridBerserView.java │ └── res │ ├── drawable-xxhdpi │ ├── pic11.jpg │ ├── pic8.jpg │ └── yjx.jpg │ ├── drawable-xxxhdpi │ ├── thumb_normal.png │ └── thumb_press.png │ ├── drawable │ ├── dragbg.xml │ └── thumb_bg.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── captchalib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── mcxtzhang │ │ └── captchalib │ │ ├── DrawHelperUtils.java │ │ └── SwipeCaptchaView.java │ └── res │ └── values │ ├── attrs.xml │ └── strings.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | 39 | # Keystore files 40 | *.jks 41 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/libraries/animated_vector_drawable_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/libraries/appcompat_v7_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/libraries/glide_3_7_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/support_annotations_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/support_compat_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/libraries/support_core_ui_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/libraries/support_core_utils_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/libraries/support_fragment_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/libraries/support_media_compat_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/libraries/support_v4_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/libraries/support_vector_drawable_24_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # -SwipeCaptcha 2 | Swipe captcha of Android platform。 3 | Android 平台的滑动验证码。 4 | 5 | **在 Android端app上,自定义View,仿一个斗鱼web端滑动验证码**。 6 | 博文:http://blog.csdn.net/zxt0601/article/details/53315975 7 | 8 | ![我们的Demo,Ac娘镇楼](http://ac-mhke0kuv.clouddn.com/7fcb58653e358b9ec003.gif) 9 | 10 | # Usage 用法: 11 | Step 1. Add the JitPack repository to your build file 12 | 在项目根build.gradle文件中增加JitPack仓库依赖。 13 | ``` 14 | allprojects { 15 | repositories { 16 | ... 17 | maven { url "https://jitpack.io" } 18 | } 19 | } 20 | ``` 21 | Step 2. Add the dependency 22 | ``` 23 | dependencies { 24 | compile 'com.github.mcxtzhang:SwipeCaptcha:V1.0.0' 25 | } 26 | ``` 27 | 28 | 29 | Step 3. 30 | ``` 31 | 40 | 41 | 50 | ``` 51 | 52 | 53 | 54 | 55 | 那么本控件包含不仅包含以下功能: 56 | * 随机区域**起点(左上角x,y)**生成一个验证码阴影。 57 | * 验证码拼图 **凹凸图形**会随机变换。 58 | * 验证码区域**宽高**可自定义。 59 | * **抠图**验证码区域,绘制一个用于联动滑动的验证码滑块。 60 | * 验证失败,会闪烁几下然后回到原点。 61 | * 验证成功,会有白光扫过的动画。 62 | 63 | 分解一下验证码核心实现思路: 64 | * 控件继承自ImageView。理由: 65 | 1 如果放在项目中用,验证码图片希望可以是接口返回。ImageView以及其子类支持花式加载图片。 66 | 2 继承自ImageView,绘制图片本身不用我们干预,也不用我们操心scaleType,节省很多工作。 67 | * 在`onSizeChanged()`方法中生成 和 控件宽高相关的属性值: 68 | 1 初始化时随机生成验证码区域起点 69 | 2 生成验证码区域Path 70 | 3 生成滑块Bitmap 71 | * `onDraw()`时,依次绘制: 72 | 1 验证码阴影 73 | 2 滑块 74 | 75 | # to do list,待完善 76 | * abstract dragbar(seekbar) interface 77 | * SwipeCaptcha inside to hold the interface to do something 78 | 79 | * 抽象拖动条接口 80 | * SwipeCaptcha内部要持有这个接口 做一些事情 81 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.3" 6 | defaultConfig { 7 | applicationId "com.mcxtzhang.swipecaptcha" 8 | minSdkVersion 14 9 | targetSdkVersion 24 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(include: ['*.jar'], dir: 'libs') 23 | compile 'com.android.support:appcompat-v7:24.2.1' 24 | compile project(':captchalib') 25 | compile 'com.github.bumptech.glide:glide:3.7.0' 26 | } 27 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\admin\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/mcxtzhang/swipecaptcha/CstView.java: -------------------------------------------------------------------------------- 1 | package com.mcxtzhang.swipecaptcha; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.util.AttributeSet; 10 | import android.util.Log; 11 | import android.view.View; 12 | 13 | 14 | /** 15 | * 介绍: 16 | * 作者:zhangxutong 17 | * 邮箱:mcxtzhang@163.com 18 | * 主页:http://blog.csdn.net/zxt0601 19 | * 时间: 2016/11/15. 20 | */ 21 | 22 | public class CstView extends View { 23 | private static final String TAG = "zxt"; 24 | ; 25 | 26 | public CstView(Context context) { 27 | super(context); 28 | } 29 | 30 | public CstView(Context context, AttributeSet attrs) { 31 | super(context, attrs); 32 | } 33 | 34 | public CstView(Context context, AttributeSet attrs, int defStyleAttr) { 35 | super(context, attrs, defStyleAttr); 36 | } 37 | 38 | @Override 39 | protected void onDraw(Canvas canvas) { 40 | canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), (R.drawable.pic11)), 0, 0, new Paint()); 41 | Path path = new Path(); 42 | path.moveTo(100, 100); 43 | path.lineTo(200, 100); 44 | path.lineTo(200, 200); 45 | path.lineTo(200, 300); 46 | path.close(); 47 | Bitmap rightBitmap = getMaskBitmap(BitmapFactory.decodeResource(getResources(), (R.drawable.pic11)), path); 48 | canvas.drawBitmap(rightBitmap, -100, -100, new Paint()); 49 | 50 | } 51 | 52 | //生成右边的背景 53 | private Bitmap getMaskBitmap(Bitmap mBitmap, Path mask) { 54 | Bitmap bgBitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888); 55 | Log.e(TAG, " getRightBitmap: " + bgBitmap.getWidth()); 56 | //把创建的位图作为画板 57 | Canvas mCanvas = new Canvas(bgBitmap); 58 | Paint mPaint = new Paint(); 59 | mPaint.setAntiAlias(true); 60 | //mCanvas.save(); 61 | //先将canvas保存 62 | //把canvas修剪成指定的路径区域 63 | //mCanvas.translate(100, 100); 64 | 65 | mCanvas.clipPath(mask); 66 | mCanvas.drawBitmap(mBitmap, 0, 0, mPaint); 67 | 68 | Log.e(TAG, "getRightBitmap: " + bgBitmap.getWidth()); 69 | return bgBitmap; 70 | } 71 | 72 | private Bitmap leftBitmap; 73 | 74 | /* //生成左边的滑块的背景 75 | private void getLeftBitmap(Bitmap mBitmap) { 76 | Bitmap bgBitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888); 77 | //把创建的位图作为画板 78 | Canvas mCanvas = new Canvas(bgBitmap); 79 | Paint mPaint = new Paint(); 80 | mPaint.setAntiAlias(true); 81 | mCanvas.save(); 82 | mCanvas.translate(point.x, point.y); 83 | mCanvas.clipPath(path); 84 | mCanvas.drawColor(Color.WHITE); 85 | mCanvas.restore(); 86 | mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 87 | 88 | //先将canvas保存 89 | //把canvas修剪成指定的路径区域 90 | mCanvas.drawBitmap(mBitmap, 0, 0, mPaint); 91 | leftBitmap = Bitmap.createBitmap(bgBitmap, point.x, point.y, w, w); 92 | }*/ 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/mcxtzhang/swipecaptcha/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.mcxtzhang.swipecaptcha; 2 | 3 | import android.graphics.Bitmap; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.widget.SeekBar; 9 | import android.widget.Toast; 10 | 11 | import com.bumptech.glide.Glide; 12 | import com.bumptech.glide.request.animation.GlideAnimation; 13 | import com.bumptech.glide.request.target.SimpleTarget; 14 | import com.mcxtzhang.captchalib.SwipeCaptchaView; 15 | 16 | public class MainActivity extends AppCompatActivity { 17 | SwipeCaptchaView mSwipeCaptchaView; 18 | SeekBar mSeekBar; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | mSwipeCaptchaView = (SwipeCaptchaView) findViewById(R.id.swipeCaptchaView); 25 | mSeekBar = (SeekBar) findViewById(R.id.dragBar); 26 | findViewById(R.id.btnChange).setOnClickListener(new View.OnClickListener() { 27 | @Override 28 | public void onClick(View v) { 29 | mSwipeCaptchaView.createCaptcha(); 30 | mSeekBar.setEnabled(true); 31 | mSeekBar.setProgress(0); 32 | } 33 | }); 34 | mSwipeCaptchaView.setOnCaptchaMatchCallback(new SwipeCaptchaView.OnCaptchaMatchCallback() { 35 | @Override 36 | public void matchSuccess(SwipeCaptchaView swipeCaptchaView) { 37 | Toast.makeText(MainActivity.this, "恭喜你啊 验证成功 可以搞事情了", Toast.LENGTH_SHORT).show(); 38 | //swipeCaptcha.createCaptcha(); 39 | mSeekBar.setEnabled(false); 40 | } 41 | 42 | @Override 43 | public void matchFailed(SwipeCaptchaView swipeCaptchaView) { 44 | Log.d("zxt", "matchFailed() called with: swipeCaptchaView = [" + swipeCaptchaView + "]"); 45 | Toast.makeText(MainActivity.this, "你有80%的可能是机器人,现在走还来得及", Toast.LENGTH_SHORT).show(); 46 | swipeCaptchaView.resetCaptcha(); 47 | mSeekBar.setProgress(0); 48 | } 49 | }); 50 | mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 51 | 52 | @Override 53 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 54 | mSwipeCaptchaView.setCurrentSwipeValue(progress); 55 | } 56 | 57 | @Override 58 | public void onStartTrackingTouch(SeekBar seekBar) { 59 | //随便放这里是因为控件 60 | mSeekBar.setMax(mSwipeCaptchaView.getMaxSwipeValue()); 61 | } 62 | 63 | @Override 64 | public void onStopTrackingTouch(SeekBar seekBar) { 65 | Log.d("zxt", "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]"); 66 | mSwipeCaptchaView.matchCaptcha(); 67 | } 68 | }); 69 | 70 | //测试从网络加载图片是否ok 71 | Glide.with(this) 72 | .load("http://www.investide.cn/data/edata/image/20151201/20151201180507_281.jpg") 73 | .asBitmap() 74 | .into(new SimpleTarget() { 75 | @Override 76 | public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { 77 | mSwipeCaptchaView.setImageBitmap(resource); 78 | mSwipeCaptchaView.createCaptcha(); 79 | } 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/mcxtzhang/swipecaptcha/TestSinView.java: -------------------------------------------------------------------------------- 1 | package com.mcxtzhang.swipecaptcha; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | 11 | /** 12 | * 介绍: 13 | * 作者:zhangxutong 14 | * 邮箱:mcxtzhang@163.com 15 | * 主页:http://blog.csdn.net/zxt0601 16 | * 时间: 2016/11/16. 17 | */ 18 | 19 | public class TestSinView extends View { 20 | public TestSinView(Context context) { 21 | super(context); 22 | } 23 | 24 | public TestSinView(Context context, AttributeSet attrs) { 25 | super(context, attrs); 26 | } 27 | 28 | //验证码 阴影、抠图的Path 29 | private Path mCaptchaPath = new Path(); 30 | //验证码的左上角(起点)的x y 31 | private int mCaptchaX = 200; 32 | private int mCaptchaY = 200; 33 | //验证码的宽高 34 | private int mCaptchaWidth = 300; 35 | private int mCaptchaHeight = 300; 36 | 37 | @Override 38 | protected void onDraw(Canvas canvas) { 39 | 40 | 41 | mCaptchaPath.reset(); 42 | //从左上角开始 绘制一个不规则的阴影 43 | mCaptchaPath.moveTo(mCaptchaX, mCaptchaY); 44 | 45 | 46 | /* mCaptchaPath.lineTo(mCaptchaX + gap, mCaptchaY); 47 | //画出凹凸 由于是多段Path 无法闭合,简直阿西吧 48 | int r = mCaptchaWidth / 2 - gap; 49 | RectF oval = new RectF(mCaptchaX + gap, mCaptchaY - (r), mCaptchaX + gap + r * 2, mCaptchaY + (r)); 50 | mCaptchaPath.arcTo(oval, 180, 180);*/ 51 | 52 | int gap = mCaptchaWidth / 3; 53 | mCaptchaPath.lineTo(mCaptchaX + gap, mCaptchaY); 54 | //利用正弦曲线方程 Y = A sin(wx+FAI)+k,将计算后的坐标传入Path.quadTo()方法(绘制贝塞尔曲线)中,构建波浪曲线。 55 | 56 | W = (float) (Math.PI / (mCaptchaWidth - 2 * gap)); 57 | FAI = (float) (-W * (mCaptchaX + gap) + Math.PI); 58 | 59 | for (int x = mCaptchaX + gap; x < mCaptchaX + mCaptchaWidth - gap; x++) { 60 | float y = (float) (A * Math.sin(W * x + FAI) + K + mCaptchaY); 61 | /* if (x == mCaptchaX + gap) { 62 | mCaptchaPath.moveTo(x, y); 63 | }*/ 64 | mCaptchaPath.quadTo(x, y, x + 1, y); 65 | } 66 | 67 | 68 | mCaptchaPath.lineTo(mCaptchaX + mCaptchaWidth, mCaptchaY);//节点 69 | 70 | mCaptchaPath.lineTo(mCaptchaX + mCaptchaWidth, mCaptchaY + gap); 71 | 72 | W = (float) (Math.PI / (mCaptchaHeight - 2 * gap)); 73 | FAI = (float) (-W * (mCaptchaY + gap) + Math.PI); 74 | 75 | for (int x = mCaptchaY + gap; x < mCaptchaY + mCaptchaHeight - gap; x++) { 76 | float y = (float) (A * Math.sin(W * x + FAI) + K + mCaptchaX + mCaptchaWidth); 77 | /* if (x == mCaptchaX + gap) { 78 | mCaptchaPath.moveTo(x, y); 79 | }*/ 80 | mCaptchaPath.quadTo(y, x, y + 1, x); 81 | } 82 | 83 | 84 | 85 | 86 | Paint paint = new Paint(); 87 | paint.setColor(Color.BLACK); 88 | paint.setStyle(Paint.Style.STROKE); 89 | canvas.drawPath(mCaptchaPath, paint); 90 | } 91 | 92 | 93 | /** 94 | * 波浪圆X轴偏移 95 | */ 96 | private float FAI = 0; 97 | /** 98 | * 波浪圆振幅 99 | */ 100 | private float A = 50; 101 | /** 102 | * 波浪圆的周期 103 | */ 104 | private float W; 105 | /** 106 | * 波浪圆Y轴偏移 107 | */ 108 | private float K = 0; 109 | 110 | 111 | /** 112 | * 角度转换成弧度 113 | * 114 | * @param degree 115 | * @return 116 | */ 117 | private double degreeToRad(double degree) { 118 | return degree * Math.PI / 180; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/mcxtzhang/swipecaptcha/TestThridBerserView.java: -------------------------------------------------------------------------------- 1 | package com.mcxtzhang.swipecaptcha; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.PointF; 9 | import android.util.AttributeSet; 10 | import android.view.View; 11 | 12 | /** 13 | * 介绍: 14 | * 作者:zhangxutong 15 | * 邮箱:mcxtzhang@163.com 16 | * 主页:http://blog.csdn.net/zxt0601 17 | * 时间: 2016/11/16. 18 | */ 19 | 20 | public class TestThridBerserView extends View { 21 | public TestThridBerserView(Context context) { 22 | super(context); 23 | } 24 | 25 | public TestThridBerserView(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | } 28 | 29 | //验证码 阴影、抠图的Path 30 | private Path mCaptchaPath = new Path(); 31 | //验证码的左上角(起点)的x y 32 | private int mCaptchaX = 200; 33 | private int mCaptchaY = 200; 34 | //验证码的宽高 35 | private int mCaptchaWidth = 300; 36 | private int mCaptchaHeight = 300; 37 | 38 | @Override 39 | protected void onDraw(Canvas canvas) { 40 | mCaptchaPath.reset(); 41 | mCaptchaPath.moveTo(mCaptchaX, mCaptchaY); 42 | 43 | int gap = mCaptchaWidth / 3; 44 | mCaptchaPath.lineTo(mCaptchaX + gap, mCaptchaY); 45 | int left = mCaptchaX + gap; 46 | int right = mCaptchaX + gap * 2; 47 | PointF start = new PointF(left, mCaptchaY); 48 | PointF end = new PointF(right, mCaptchaY); 49 | drawPartCircle(start, end, mCaptchaPath, false); 50 | 51 | 52 | mCaptchaPath.lineTo(mCaptchaX + mCaptchaWidth, mCaptchaY);//节点 53 | mCaptchaPath.lineTo(mCaptchaX + mCaptchaWidth, mCaptchaY + gap); 54 | 55 | drawPartCircle(new PointF(mCaptchaX + mCaptchaWidth, mCaptchaY + gap), 56 | new PointF(mCaptchaX + mCaptchaWidth, mCaptchaY + gap * 2), 57 | mCaptchaPath, false); 58 | 59 | mCaptchaPath.lineTo(mCaptchaX + mCaptchaWidth, mCaptchaY + mCaptchaHeight);//节点 60 | mCaptchaPath.lineTo(mCaptchaX + mCaptchaWidth - gap, mCaptchaY + mCaptchaHeight); 61 | 62 | drawPartCircle(new PointF(mCaptchaX + mCaptchaWidth - gap, mCaptchaY + mCaptchaHeight), 63 | new PointF(mCaptchaX + mCaptchaWidth - gap - gap, mCaptchaY + mCaptchaHeight), 64 | mCaptchaPath, false); 65 | 66 | mCaptchaPath.lineTo(mCaptchaX, mCaptchaY + mCaptchaHeight);//节点 67 | mCaptchaPath.lineTo(mCaptchaX, mCaptchaY + mCaptchaHeight - gap); 68 | 69 | drawPartCircle(new PointF(mCaptchaX, mCaptchaY + mCaptchaHeight - gap), 70 | new PointF(mCaptchaX, mCaptchaY + mCaptchaHeight - gap * 2), 71 | mCaptchaPath, false); 72 | 73 | 74 | Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); 75 | paint.setColor(Color.BLACK); 76 | paint.setStyle(Paint.Style.FILL); 77 | canvas.drawPath(mCaptchaPath, paint); 78 | } 79 | 80 | 81 | /** 82 | * 传入起点、终点 坐标、凹凸和Path。 83 | * 会自动绘制凹凸的半圆弧 84 | * 85 | * @param start 起点坐标 86 | * @param end 终点坐标 87 | * @param path 半圆会绘制在这个path上 88 | * @param outer 是否凸半圆 89 | */ 90 | private void drawPartCircle(PointF start, PointF end, Path path, boolean outer) { 91 | float c = 0.551915024494f; 92 | //中点 93 | PointF middle = new PointF(start.x + (end.x - start.x) / 2, start.y + (end.y - start.y) / 2); 94 | //半径 95 | float r1 = (float) Math.sqrt(Math.pow((middle.x - start.x), 2) + Math.pow((middle.y - start.y), 2)); 96 | //gap值 97 | float gap1 = r1 * c; 98 | 99 | if (start.x == end.x) { 100 | //绘制竖直方向的 101 | 102 | //是否是从上到下 103 | boolean topToBottom = end.y - start.y > 0 ? true : false; 104 | //以下是我写出了所有的计算公式后推的,不要问我过程,只可意会。 105 | int flag;//旋转系数 106 | if (topToBottom) { 107 | flag = 1; 108 | } else { 109 | flag = -1; 110 | } 111 | if (outer) { 112 | //凸的 两个半圆 113 | path.cubicTo(start.x + gap1 * flag, start.y, 114 | middle.x + r1 * flag, middle.y - gap1 * flag, 115 | middle.x + r1 * flag, middle.y); 116 | path.cubicTo(middle.x + r1 * flag, middle.y + gap1 * flag, 117 | end.x + gap1 * flag, end.y, 118 | end.x, end.y); 119 | } else { 120 | //凹的 两个半圆 121 | path.cubicTo(start.x - gap1 * flag, start.y, 122 | middle.x - r1 * flag, middle.y - gap1 * flag, 123 | middle.x - r1 * flag, middle.y); 124 | path.cubicTo(middle.x - r1 * flag, middle.y + gap1 * flag, 125 | end.x - gap1 * flag, end.y, 126 | end.x, end.y); 127 | } 128 | } else { 129 | //绘制水平方向的 130 | 131 | //是否是从左到右 132 | boolean leftToRight = end.x - start.x > 0 ? true : false; 133 | //以下是我写出了所有的计算公式后推的,不要问我过程,只可意会。 134 | int flag;//旋转系数 135 | if (leftToRight) { 136 | flag = 1; 137 | } else { 138 | flag = -1; 139 | } 140 | if (outer) { 141 | //凸 两个半圆 142 | path.cubicTo(start.x, start.y - gap1 * flag, 143 | middle.x - gap1 * flag, middle.y - r1 * flag, 144 | middle.x, middle.y - r1 * flag); 145 | path.cubicTo(middle.x + gap1 * flag, middle.y - r1 * flag, 146 | end.x, end.y - gap1 * flag, 147 | end.x, end.y); 148 | } else { 149 | //凹 两个半圆 150 | path.cubicTo(start.x, start.y + gap1 * flag, 151 | middle.x - gap1 * flag, middle.y + r1 * flag, 152 | middle.x, middle.y + r1 * flag); 153 | path.cubicTo(middle.x + gap1 * flag, middle.y + r1 * flag, 154 | end.x, end.y + gap1 * flag, 155 | end.x, end.y); 156 | } 157 | 158 | 159 | /* 160 | 没推导之前的公式在这里 161 | if (start.x < end.x) { 162 | if (outer) { 163 | //上左半圆 顺时针 164 | path.cubicTo(start.x, start.y - gap1, 165 | middle.x - gap1, middle.y - r1, 166 | middle.x, middle.y - r1); 167 | 168 | //上右半圆:顺时针 169 | path.cubicTo(middle.x + gap1, middle.y - r1, 170 | end.x, end.y - gap1, 171 | end.x, end.y); 172 | } else { 173 | //下左半圆 逆时针 174 | path.cubicTo(start.x, start.y + gap1, 175 | middle.x - gap1, middle.y + r1, 176 | middle.x, middle.y + r1); 177 | 178 | //下右半圆 逆时针 179 | path.cubicTo(middle.x + gap1, middle.y + r1, 180 | end.x, end.y + gap1, 181 | end.x, end.y); 182 | } 183 | } else { 184 | if (outer) { 185 | //下右半圆 顺时针 186 | path.cubicTo(start.x, start.y + gap1, 187 | middle.x + gap1, middle.y + r1, 188 | middle.x, middle.y + r1); 189 | //下左半圆 顺时针 190 | path.cubicTo(middle.x - gap1, middle.y + r1, 191 | end.x, end.y + gap1, 192 | end.x, end.y); 193 | } 194 | }*/ 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/pic11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcxtzhang/SwipeCaptcha/fc77cbe097c25fe1dd66f071233840d7c22e07a5/app/src/main/res/drawable-xxhdpi/pic11.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/pic8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcxtzhang/SwipeCaptcha/fc77cbe097c25fe1dd66f071233840d7c22e07a5/app/src/main/res/drawable-xxhdpi/pic8.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/yjx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcxtzhang/SwipeCaptcha/fc77cbe097c25fe1dd66f071233840d7c22e07a5/app/src/main/res/drawable-xxhdpi/yjx.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/thumb_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcxtzhang/SwipeCaptcha/fc77cbe097c25fe1dd66f071233840d7c22e07a5/app/src/main/res/drawable-xxxhdpi/thumb_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/thumb_press.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcxtzhang/SwipeCaptcha/fc77cbe097c25fe1dd66f071233840d7c22e07a5/app/src/main/res/drawable-xxxhdpi/thumb_press.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/dragbg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/thumb_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 24 | 25 | 34 | 35 |