Commit 09b81edf by luofan

集成图形验证码lib

parent d251abff
Showing with 2060 additions and 0 deletions
apply plugin: 'com.android.library'
android {
compileSdkVersion compile_sdk_version
buildToolsVersion build_tools_version
defaultConfig {
minSdkVersion min_sdk_version
targetSdkVersion target_sdk_version
versionCode version_code
versionName verson_name
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled isReleaseMinify
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets{
main{
jniLibs.srcDir(['libs'])
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'
api project(':provider')
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.verificationcodejavademo">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application android:allowBackup="true" android:label="@string/app_name"
android:supportsRtl="true">
</application>
</manifest>
\ No newline at end of file
package com.example.verificationcodejavademo.model;
/**
* Date:2020/5/18
* author:wuyan
*/
public class CaptchaCheckIt {
private String captchaType;
private String token;
private boolean result;
private boolean opAdmin;
public String getCaptchaType() {
return captchaType;
}
public void setCaptchaType(String captchaType) {
this.captchaType = captchaType;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public boolean isResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
public boolean isOpAdmin() {
return opAdmin;
}
public void setOpAdmin(boolean opAdmin) {
this.opAdmin = opAdmin;
}
}
package com.example.verificationcodejavademo.model;
/**
* Date:2020/5/18
* author:wuyan
*/
public class CaptchaGetIt {
private String originalImageBase64;//图表url 目前用base64 data
private String jigsawImageBase64;
private Point point;
private String token;//获取的token 用于校验
private boolean result;
private boolean opAdmin;
private String secretKey;
public String getOriginalImageBase64() {
return originalImageBase64;
}
public void setOriginalImageBase64(String originalImageBase64) {
this.originalImageBase64 = originalImageBase64;
}
public String getJigsawImageBase64() {
return jigsawImageBase64;
}
public void setJigsawImageBase64(String jigsawImageBase64) {
this.jigsawImageBase64 = jigsawImageBase64;
}
public Point getPoint() {
return point;
}
public void setPoint(Point point) {
this.point = point;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public boolean isResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
public boolean isOpAdmin() {
return opAdmin;
}
public void setOpAdmin(boolean opAdmin) {
this.opAdmin = opAdmin;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
}
package com.example.verificationcodejavademo.model;
import java.io.Serializable;
/**
* Date:2020/5/18
* author:wuyan
*/
public class Point implements Serializable {
private double x;
private double y;
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
}
package com.example.verificationcodejavademo.model;
import java.util.ArrayList;
/**
* Date:2020/5/18
* author:wuyan
*/
public class WordCaptchaGetIt {
private String originalImageBase64;
private boolean result;
private String token;
private String secretKey;
private ArrayList<String> wordList;
public String getOriginalImageBase64() {
return originalImageBase64;
}
public void setOriginalImageBase64(String originalImageBase64) {
this.originalImageBase64 = originalImageBase64;
}
public boolean isResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public ArrayList<String> getWordList() {
return wordList;
}
public void setWordList(ArrayList<String> wordList) {
this.wordList = wordList;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
}
package com.example.verificationcodejavademo.network;
import android.app.ProgressDialog;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import android.widget.Toast;
import com.example.verificationcodejavademo.R;
import com.google.gson.Gson;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
/**
* Date:2020/5/18
* author:wuyan
* 对基础数据进行统一出路
*/
public abstract class BaseObserver<T> implements Observer<BaseResponse<T>> {
private static final String TAG = "BaseObserver";
private boolean mShowDialog;
private ProgressDialog dialog;
private Context mContext;
private Disposable d;
public abstract void onSuccess(T data);
public abstract void onFailure(Throwable e, String errorMsg);
public BaseObserver(Context mContext) {
this.mContext = mContext;
}
public BaseObserver(Context mContext, boolean mShowDialog) {
this.mContext = mContext;
this.mShowDialog = mShowDialog;
}
@Override
public void onSubscribe(Disposable d) {
this.d = d;
if (!isConnected(mContext)) {
Toast.makeText(mContext, "网络未连接", Toast.LENGTH_SHORT).show();
if (d.isDisposed()) {
d.dispose();
}
} else {
if (dialog == null && mShowDialog == true) {
dialog = new ProgressDialog(mContext, R.style.dialog);
dialog.show();
}
}
}
//后给给返回结果
@Override
public void onNext(BaseResponse<T> response) {
Log.i(TAG, "网络请求数据结果: " + new Gson().toJson(response));
if (response.getRepCode().equals("0000")) {
onSuccess(response.getRepData());
} else {
//返回其他错误码
onFailure(null, "网络请求失败");
}
}
//网络请求失败
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
if (d.isDisposed()) {
d.dispose();
}
hidDialog();
onFailure(e, RxExceptionUtil.exceptionHandler(e));
}
@Override
public void onComplete() {
if (d.isDisposed()) {
d.dispose();
}
hidDialog();
}
private void hidDialog() {
if (dialog != null && mShowDialog == true) {
dialog.dismiss();
dialog = null;
}
}
/**
* 是否又网络连接
*/
private static boolean isConnected(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if (info == null) {
return false;
}
boolean available = info.isAvailable();
return available;
}
}
package com.example.verificationcodejavademo.network;
/**
* Date:2020/5/18
* author:wuyan
*/
public class BaseResponse<T> {
private T repData;
private String repCode;
private boolean success;
private boolean error;
public T getRepData() {
return repData;
}
public void setRepData(T repData) {
this.repData = repData;
}
public String getRepCode() {
return repCode;
}
public void setRepCode(String repCode) {
this.repCode = repCode;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public boolean isError() {
return error;
}
public void setError(boolean error) {
this.error = error;
}
}
package com.example.verificationcodejavademo.network;
import com.dayu.base.api.HttpLoggingInterceptor;
import com.dayu.common.Constants;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* Date:2020/5/19
* author:wuyan
* Retrofit封装
*/
public class RetrofitUtils {
private static final String TAG = "RetrofitUtils";
//设置默认超时时间
public static final int DEFAULT_TIME = 10;
private static ServerApi mServerApi;
public static ServerApi getServerApi() {
if (mServerApi == null) {
synchronized (RetrofitUtils.class) {
if (mServerApi == null) {
mServerApi = new RetrofitUtils().getRetrofit();
}
}
}
return mServerApi;
}
public ServerApi getRetrofit() {
ServerApi serverApi = initRetrofit(initOkHttp()).create(ServerApi.class);
return serverApi;
}
/**
* 初始化Retrofit
*/
private Retrofit initRetrofit(OkHttpClient client) {
return new Retrofit.Builder()
.client(client)
.baseUrl(Constants.BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//添加Rxjava支持
.addConverterFactory(GsonConverterFactory.create())//添加GSON解析:返回数据转换成GSON类型
.build();
}
private OkHttpClient initOkHttp() {
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
// httpLoggingInterceptor.level(HttpLoggingInterceptor.Level.BODY);
return new OkHttpClient().newBuilder()
.readTimeout(DEFAULT_TIME, TimeUnit.SECONDS)//设置读取超时时间
.connectTimeout(DEFAULT_TIME, TimeUnit.SECONDS)//设置请求超时时间
.writeTimeout(DEFAULT_TIME, TimeUnit.SECONDS)//设置写入超时时间
.retryOnConnectionFailure(true)//设置出现错误进行重新连接
.addInterceptor(httpLoggingInterceptor)
.build();
}
}
package com.example.verificationcodejavademo.network;
import android.net.ParseException;
import org.json.JSONException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import retrofit2.HttpException;
/**
* Date:2020/5/19
* author:wuyan
* 异常处理
*/
public class RxExceptionUtil {
public static String exceptionHandler(Throwable e) {
String errorMsg = "未知错误";
if (e instanceof UnknownHostException) {
errorMsg = "网络不可用";
} else if (e instanceof SocketTimeoutException) {
errorMsg = "请求网络超时";
} else if (e instanceof HttpException) {
HttpException httpException = (HttpException) e;
errorMsg = convertStatusCode(httpException);
} else if (e instanceof ParseException || e instanceof JSONException) {
errorMsg = "数据解析错误";
}
return errorMsg;
}
private static String convertStatusCode(HttpException httpException) {
String msg;
if (httpException.code() >= 500 && httpException.code() < 600) {
msg = "服务器处理请求出错";
} else if (httpException.code() >= 400 && httpException.code() < 500) {
msg = "服务器无法处理请求";
} else if (httpException.code() >= 300 && httpException.code() < 400) {
msg = "请求被重定向到其他页面";
} else {
msg = httpException.message();
}
return msg;
}
}
package com.example.verificationcodejavademo.network;
import android.content.Context;
import com.trello.rxlifecycle2.android.ActivityEvent;
import com.trello.rxlifecycle2.components.RxActivity;
import com.trello.rxlifecycle2.components.RxFragment;
import com.trello.rxlifecycle2.components.support.RxAppCompatActivity;
import com.trello.rxlifecycle2.components.support.RxFragmentActivity;
import io.reactivex.FlowableTransformer;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
/**
* Date:2020/5/19
* author:wuyan
* 对执行线程和绑定生命周期几个方法进行封装
* 调度类
*/
public class RxHelper {
public static <T> ObservableTransformer<T, T> observableIO2Main(final Context context) {
return upstream -> {
Observable<T> observable = upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
return composeContext(context, observable);
};
}
public static <T> ObservableTransformer<T, T> observableIO2Main(final RxFragment fragmnet) {
return upstream -> upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).compose(fragmnet.<T>bindToLifecycle());
}
public static <T> FlowableTransformer<T, T> flowableIO2Main() {
return upstream -> upstream.subscribeOn(AndroidSchedulers.mainThread());
}
private static <T> ObservableSource<T> composeContext(Context context, Observable<T> observable) {
if (context instanceof RxActivity) {
return observable.compose(((RxActivity) context).bindUntilEvent(ActivityEvent.DESTROY));
} else if (context instanceof RxFragmentActivity) {
return observable.compose(((RxFragmentActivity) context).bindUntilEvent(ActivityEvent.DESTROY));
} else if (context instanceof RxAppCompatActivity) {
return observable.compose(((RxAppCompatActivity) context).bindUntilEvent(ActivityEvent.DESTROY));
} else {
return observable;
}
}
}
package com.example.verificationcodejavademo.network;
import com.example.verificationcodejavademo.model.CaptchaCheckIt;
import com.example.verificationcodejavademo.model.CaptchaGetIt;
import com.example.verificationcodejavademo.model.WordCaptchaGetIt;
import io.reactivex.Observable;
import okhttp3.RequestBody;
import retrofit2.http.Body;
import retrofit2.http.POST;
/**
* Date:2020/5/18
* author:wuyan
*/
public interface ServerApi {
//获取验证码
@POST("/api-third/captcha/get")
Observable<BaseResponse<CaptchaGetIt>> getAsync(@Body RequestBody body);
//获取文字的验证码
@POST("/api-third/captcha/get")
Observable<BaseResponse<WordCaptchaGetIt>> getWordCaptchaAsync(@Body RequestBody body);
//核对验证码
@POST("/api-third/captcha/check")
Observable<BaseResponse<CaptchaCheckIt>> checkAsync(@Body RequestBody body);
}
package com.example.verificationcodejavademo.utils;
import android.text.TextUtils;
import android.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
/**
* Date:2020/5/20
* author:wuyan
*/
public class AESUtil {
private final static String ALGORITHMSTR = "AES/ECB/PKCS7Padding";
private final static String ALGORITHM = "AES";
public static String encode(String content, String key) {
if (TextUtils.isEmpty(content)) {
return content;
}
if (key == null || key.isEmpty()) {
return content;
}
try {
byte[] result = encrypt(key, content);
String s = Base64.encodeToString(result, Base64.DEFAULT);
if (s.contains("\n")) {
return s.replace("\n", "");
}
return s;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
private static byte[] encrypt(String encryptKey, String content) throws Exception {
// 创建AES秘钥
SecretKeySpec secretKeySpec = new SecretKeySpec(encryptKey.getBytes(), ALGORITHM);
// 创建密码器
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
// 初始化加密器
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
// 加密
return cipher.doFinal(content.getBytes("UTF-8"));
}
}
package com.example.verificationcodejavademo.utils;
import android.content.Context;
/**
* Date:2020/5/19
* author:wuyan
*/
public class DisplayUtil {
public static int dip2px(Context context, Float dpValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5);
}
public static int px2dip(Context context, Float pxValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5);
}
}
package com.example.verificationcodejavademo.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
import android.util.LruCache;
import android.util.TypedValue;
/**
* Date:2020/5/20
* author:wuyan
*/
public class ImageUtil {
private LruCache<String, Bitmap> mMemoryCache;
private int cacheSize;
/**
* base64转为bitmap
*
* @param base64Data
* @return
*/
public static Bitmap base64ToBitmap(String base64Data) {
byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
public static Bitmap getBitmap(Context context, int resId) {
BitmapFactory.Options options = new BitmapFactory.Options();
TypedValue value = new TypedValue();
context.getResources().openRawResource(resId, value);
options.inTargetDensity = value.density;
options.inScaled = false;//不缩放
return BitmapFactory.decodeResource(context.getResources(), resId, options);
}
}
package com.example.verificationcodejavademo.widget;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.view.Display;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.example.verificationcodejavademo.R;
import com.example.verificationcodejavademo.model.CaptchaCheckIt;
import com.example.verificationcodejavademo.model.CaptchaGetIt;
import com.example.verificationcodejavademo.model.Point;
import com.example.verificationcodejavademo.network.BaseObserver;
import com.example.verificationcodejavademo.network.RetrofitUtils;
import com.example.verificationcodejavademo.network.RxHelper;
import com.example.verificationcodejavademo.utils.AESUtil;
import com.example.verificationcodejavademo.utils.ImageUtil;
import com.google.gson.Gson;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
import okhttp3.MediaType;
import okhttp3.RequestBody;
/**
* Date:2020/5/19
* author:wuyan
*/
public class BlockPuzzleDialog extends Dialog {
private String baseImageBase64;//背景图片
private String slideImageBase64;//滑动图片
private String token;
private Context mContext;
private TextView tvDelete;
private ImageView tvRefresh;
private DragImageView dragView;
private Handler handler = new Handler();
private String key;
public BlockPuzzleDialog(@NonNull Context context) {
super(context, R.style.dialog);
this.mContext = context;
setContentView(R.layout.dialog_block_puzzle);
getWindow().setGravity(Gravity.CENTER);
getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
WindowManager windowManager = ((Activity) mContext).getWindowManager();
Display display = windowManager.getDefaultDisplay();
ViewGroup.LayoutParams lp = getWindow().getAttributes();
lp.width = display.getWidth() * 9 / 10;
getWindow().setAttributes((WindowManager.LayoutParams) lp);
setCanceledOnTouchOutside(false);//点击外部Dialog不消失
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initView();
loadCaptcha();
tvDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
tvRefresh.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadCaptcha();
}
});
}
private void initView() {
tvDelete = findViewById(R.id.tv_delete);
tvRefresh = findViewById(R.id.tv_refresh);
dragView = findViewById(R.id.dragView);
Bitmap bitmap = ImageUtil.getBitmap(getContext(), R.drawable.bg_default);
dragView.setUp(bitmap, bitmap);
dragView.setSBUnMove(false);
}
private void loadCaptcha() {
Map<String, Object> params = new HashMap<>();
params.put("captchaType", "blockPuzzle");
JSONObject jsonObject = new JSONObject(params);
RequestBody body = RequestBody.create(MediaType.parse("application/json"), jsonObject.toString());
RetrofitUtils.getServerApi().getAsync(body).compose(RxHelper.observableIO2Main(mContext)).subscribe(new BaseObserver<CaptchaGetIt>(mContext, true) {
@Override
public void onSuccess(CaptchaGetIt data) {
baseImageBase64 = data.getOriginalImageBase64();
slideImageBase64 = data.getJigsawImageBase64();
token = data.getToken();
key = data.getSecretKey();
dragView.setUp(ImageUtil.base64ToBitmap(baseImageBase64), ImageUtil.base64ToBitmap(slideImageBase64));
dragView.setSBUnMove(true);
initEvent();
}
@Override
public void onFailure(Throwable e, String errorMsg) {
dragView.setSBUnMove(false);
Toast.makeText(mContext, errorMsg, Toast.LENGTH_SHORT).show();
}
});
}
private void checkCaptcha(double sliderXMoved) {
Point point = new Point();
point.setY(5.0);
point.setX(sliderXMoved);
String pointStr = new Gson().toJson(point);
Map<String, Object> params = new HashMap<>();
params.put("captchaType", "blockPuzzle");
params.put("token", token);
params.put("pointJson", AESUtil.encode(pointStr, key));
JSONObject jsonObject = new JSONObject(params);
RequestBody body = RequestBody.create(MediaType.parse("application/json"), jsonObject.toString());
RetrofitUtils.getServerApi().checkAsync(body).compose(RxHelper.observableIO2Main(mContext)).subscribe(new BaseObserver<CaptchaCheckIt>(mContext, false) {
@Override
public void onSuccess(CaptchaCheckIt data) {
dragView.ok();
loadCaptcha();
handler.postDelayed(new Runnable() {
@Override
public void run() {
dismiss();
}
}, 1500);
if (mOnResultsListener!=null){
String result=token+"---"+pointStr;
mOnResultsListener.onResultsClick(AESUtil.encode(result,key));
}
}
@Override
public void onFailure(Throwable e, String errorMsg) {
dragView.fail();
//刷新验证码
loadCaptcha();
}
});
}
private void initEvent() {
dragView.setDragListenner(new DragImageView.DragListenner() {
@Override
public void onDrag(double position) {
checkCaptcha(position);
}
});
}
private OnResultsListener mOnResultsListener;
public interface OnResultsListener {
void onResultsClick(String result);
}
public void setOnResultsListener(OnResultsListener mOnResultsListener) {
this.mOnResultsListener = mOnResultsListener;
}
}
package com.example.verificationcodejavademo.widget;
import android.content.Context;
import android.support.v7.widget.AppCompatTextView;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.ClickableSpan;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Date:2020/5/19
* author:wuyan
*/
public class DiyStyleTextView extends AppCompatTextView {
private String colorRegex = "";//需要改变颜色的内容
private int color = 0;//需要改变的颜色
private ArrayList<Integer> indexArr = new ArrayList<>();
private ArrayList<String> strArr = new ArrayList<>();
public DiyStyleTextView(Context context) {
super(context);
}
public DiyStyleTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 设置需要改变颜色的文本,和改变的颜色
*/
private DiyStyleTextView setColorRegex(String colorRegex, int color) {
this.colorRegex = colorRegex;
this.color = color;
return this;
}
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(setText(text, false), type);
}
private CharSequence setText(CharSequence text, Boolean flag) {
if (TextUtils.isEmpty(text)) {
if (flag) super.setText(text);
return text;
}
SpannableStringBuilder styledText = new SpannableStringBuilder(text);
if (!TextUtils.isEmpty(colorRegex)) {
indexArr.clear();
strArr.clear();
Pattern p = Pattern.compile(colorRegex);
Matcher m = p.matcher(text);
while (m.find()) {
strArr.add(m.group());
indexArr.add(m.start());
}
for (int i = 0; i < indexArr.size(); i++) {
int index = indexArr.get(i);
String clickText = strArr.get(i);
styledText.setSpan(
new TextViewClickSpan(clickText),
index,
index + clickText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
}
}
if (flag) super.setText(styledText);
return styledText;
}
private class TextViewClickSpan extends ClickableSpan {
public TextViewClickSpan(String clickText) {
}
@Override
public void onClick(@NonNull View widget) {
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(color);
}
}
}
package com.example.verificationcodejavademo.widget;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.example.verificationcodejavademo.R;
import com.example.verificationcodejavademo.model.CaptchaCheckIt;
import com.example.verificationcodejavademo.model.WordCaptchaGetIt;
import com.example.verificationcodejavademo.network.BaseObserver;
import com.example.verificationcodejavademo.network.RetrofitUtils;
import com.example.verificationcodejavademo.network.RxHelper;
import com.example.verificationcodejavademo.utils.AESUtil;
import com.example.verificationcodejavademo.utils.ImageUtil;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
import okhttp3.MediaType;
import okhttp3.RequestBody;
/**
* Date:2020/5/19
* author:wuyan
*/
public class WordCaptchaDialog extends Dialog {
private String baseImageBase64;//背景图片
private String token;
private Context mContext;
private TextView tvDelete;
private ImageView tvRefresh;
private WordImageView wordView;
private TextView bottomTitle;
private Handler handler = new Handler();
private String key;
public WordCaptchaDialog(@NonNull Context context) {
super(context, R.style.dialog);
this.mContext = context;
setContentView(R.layout.dialog_word_captcha);
getWindow().setGravity(Gravity.CENTER);
getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
WindowManager windowManager = ((Activity) mContext).getWindowManager();
Display display = windowManager.getDefaultDisplay();
ViewGroup.LayoutParams lp = getWindow().getAttributes();
lp.width = display.getWidth() * 9 / 10;
getWindow().setAttributes((WindowManager.LayoutParams) lp);
setCanceledOnTouchOutside(false);//点击外部Dialog不消失
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initView();
loadCaptcha();
tvDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
tvRefresh.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadCaptcha();
}
});
}
private void initView() {
tvDelete = findViewById(R.id.tv_delete);
tvRefresh = findViewById(R.id.tv_refresh);
wordView = findViewById(R.id.wordView);
bottomTitle = findViewById(R.id.bottomTitle);
Bitmap bitmap = ImageUtil.getBitmap(getContext(), R.drawable.bg_default);
wordView.setUp(bitmap);
}
private void loadCaptcha() {
bottomTitle.setText("数据加载中......");
bottomTitle.setTextColor(Color.BLACK);
Map<String, Object> params = new HashMap<>();
params.put("captchaType", "clickWord");
JSONObject jsonObject = new JSONObject(params);
RequestBody body = RequestBody.create(MediaType.parse("application/json"), jsonObject.toString());
Log.i("wuyan", "body:" + jsonObject);
RetrofitUtils.getServerApi().getWordCaptchaAsync(body).compose(RxHelper.observableIO2Main(mContext)).subscribe(new BaseObserver<WordCaptchaGetIt>(mContext, true) {
@Override
public void onSuccess(WordCaptchaGetIt data) {
baseImageBase64 = data.getOriginalImageBase64();
token = data.getToken();
key = data.getSecretKey();
String wordStr = "";
int j = 0;
for (int i = 0; i < data.getWordList().size(); i++) {
j++;
wordStr += data.getWordList().get(i);
if (j < data.getWordList().size()) {
wordStr += ",";
}
}
wordView.setSize(data.getWordList().size());
bottomTitle.setText("请依此点击【" + wordStr + "】");
bottomTitle.setTextColor(Color.BLACK);
wordView.setUp(
ImageUtil.base64ToBitmap(baseImageBase64)
);
initEvent();
}
@Override
public void onFailure(Throwable e, String errorMsg) {
bottomTitle.setText("加载失败,请刷新");
bottomTitle.setTextColor(Color.RED);
wordView.setSize(-1);
Toast.makeText(mContext, errorMsg, Toast.LENGTH_SHORT).show();
}
});
}
private void checkCaptcha(String cryptedStr) {
Map<String, Object> params = new HashMap<>();
params.put("captchaType", "clickWord");
params.put("token", token);
params.put("pointJson", AESUtil.encode(cryptedStr, key));
JSONObject jsonObject = new JSONObject(params);
RequestBody body = RequestBody.create(MediaType.parse("application/json"), jsonObject.toString());
Log.i("wuyan", "body:" + jsonObject);
RetrofitUtils.getServerApi().checkAsync(body).compose(RxHelper.observableIO2Main(mContext)).subscribe(new BaseObserver<CaptchaCheckIt>(mContext, false) {
@Override
public void onSuccess(CaptchaCheckIt data) {
bottomTitle.setText("验证成功");
bottomTitle.setTextColor(Color.GREEN);
wordView.ok();
handler.postDelayed(new Runnable() {
@Override
public void run() {
loadCaptcha();
dismiss();
}
}, 1000);
if (mOnResultsListener!=null){
String result=token+"---"+cryptedStr;
mOnResultsListener.onResultsClick(AESUtil.encode(result,key));
}
}
@Override
public void onFailure(Throwable e, String errorMsg) {
bottomTitle.setText("验证失败");
bottomTitle.setTextColor(Color.RED);
wordView.fail();
handler.postDelayed(new Runnable() {
@Override
public void run() {
//刷新验证码
loadCaptcha();
}
}, 1000);
}
});
}
private void initEvent() {
wordView.setWordListenner(new WordImageView.WordListenner() {
@Override
public void onWord(String cryptedStr) {
checkCaptcha(cryptedStr);
}
});
}
private OnResultsListener mOnResultsListener;
public interface OnResultsListener {
void onResultsClick(String result);
}
public void setOnResultsListener(OnResultsListener mOnResultsListener) {
this.mOnResultsListener = mOnResultsListener;
}
}
package com.example.verificationcodejavademo.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.example.verificationcodejavademo.R;
import com.example.verificationcodejavademo.model.Point;
import com.example.verificationcodejavademo.utils.DisplayUtil;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.List;
/**
* Date:2020/5/21
* author:wuyan
*/
public class WordImageView extends FrameLayout {
private FrameLayout word_fl_content;
private ImageView word_iv_cover;
private View word_v_flash;
private Bitmap cover;
private int size = 0;//需要点击文字的数量
private List<Point> mList = new ArrayList<>();
private Handler mHandler = new Handler();
public WordImageView(@NonNull Context context) {
super(context);
init();
}
public WordImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public WordImageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
View.inflate(getContext(), R.layout.word_view, this);
word_fl_content = findViewById(R.id.word_fl_content);
word_iv_cover = findViewById(R.id.word_iv_cover);
word_v_flash = findViewById(R.id.word_v_flash);
reset();
}
public void setSize(int size) {
this.size = size;
}
public void setUp(Bitmap cover) {
this.cover = cover;
int w = cover.getWidth();
int h = cover.getHeight();
FrameLayout.LayoutParams l = (FrameLayout.LayoutParams) word_iv_cover.getLayoutParams();
l.width = DisplayUtil.dip2px(getContext(), (float) w);
l.height = DisplayUtil.dip2px(getContext(), (float) h);
word_iv_cover.setLayoutParams(l);
word_iv_cover.setImageBitmap(cover);
setLocation(cover.getWidth(), cover.getHeight());
}
private void setLocation(int w, int h) {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) word_fl_content.getLayoutParams();
layoutParams.width = DisplayUtil.dip2px(getContext(), (float) w);
layoutParams.height = DisplayUtil.dip2px(getContext(), (float) h);
word_fl_content.setLayoutParams(layoutParams);
}
public void ok() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
reset();
}
}, 1000);
}
public void fail() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
reset();
}
}, 1000);
}
private void reset() {
mList.clear();
word_fl_content.removeAllViews();
word_fl_content.addView(word_iv_cover);
word_fl_content.addView(word_v_flash);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
size--;
Point point = new Point();
point.setX(DisplayUtil.px2dip(getContext(), event.getX()));
point.setY(DisplayUtil.px2dip(getContext(), event.getY()));
mList.add(point);
if (size > 0) {
//添加小圆点
addTextView(event);
} else if (size == 0) {
addTextView(event);
if (wordListenner != null) {
wordListenner.onWord(new Gson().toJson(mList));
}
}
}
return true;
}
//点击后添加小圆点
private void addTextView(MotionEvent event) {
TextView textView = new TextView(getContext());
LayoutParams l = new LayoutParams(DisplayUtil.dip2px(getContext(), 20 * 1.0f), DisplayUtil.dip2px(getContext(), 20 * 1.0f));
textView.setLayoutParams(l);
textView.setGravity(Gravity.CENTER);
textView.setText(mList.size() + "");
textView.setTextColor(Color.WHITE);
textView.setBackground(getResources().getDrawable(R.drawable.shape_dot_bg));
MarginLayoutParams postion = (MarginLayoutParams) textView.getLayoutParams();
postion.leftMargin = (int) (event.getX() - 10);
postion.topMargin = (int) (event.getY() - 10);
word_fl_content.addView(textView);
}
//设置滑动监听
private WordListenner wordListenner;
interface WordListenner {
void onWord(String cryptedStr);
}
public void setWordListenner(WordListenner wordListenner) {
this.wordListenner = wordListenner;
}
}
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="#f8f9fb" />
<size android:height="58.5dp" android:width="310dp" />
<stroke
android:width="0.5dp"
android:color="#e5e5e5" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<solid android:color="#ffe0e0e0" android:width="310dp"/>
<size android:height="58.5dp" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="#f3fef1" android:width="310dp"/>
<size android:height="58.5dp" />
<stroke
android:width="0.5dp"
android:color="#447ab2" />
</shape>
</clip>
</item>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="#f8f9fb" />
<size android:height="58.5dp" />
<stroke
android:width="0.5dp"
android:color="#e5e5e5" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<solid android:color="#ffe0e0e0" />
<size android:height="58.5dp" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="#f3fef1" />
<size android:height="58.5dp" />
<stroke
android:width="0.5dp"
android:color="#f32121" />
</shape>
</clip>
</item>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="#f8f9fb" />
<size android:height="58.5dp" />
<stroke
android:width="0.5dp"
android:color="#e5e5e5" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<solid android:color="#ffe0e0e0" />
<size android:height="58.5dp" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="#f3fef1" />
<size android:height="58.5dp" />
<stroke
android:width="0.5dp"
android:color="#28f30e" />
</shape>
</clip>
</item>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/drag_btn" />
<item android:drawable="@drawable/drag_btn_n" />
</selector>
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/shape_btn_bg_press"/>
<item android:drawable="@drawable/shape_btn_bg_unpress"/>
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/transparent" />
<solid
android:width="1dip"
android:color="#ffffff" />
<stroke android:width="0.5dp" android:color="#e5e5e5"/>
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/transparent" />
<corners android:radius="20dip"/>
<solid
android:width="1dip"
android:color="#55F37022" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/transparent" />
<corners android:radius="20dip"/>
<solid
android:width="1dip"
android:color="#F37022" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/holo_green_light"/>
<corners android:radius="5dp"/>
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/transparent" />
<corners android:radius="20dip"/>
<stroke
android:width="1dip"
android:color="#88F37022" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:gravity="center_horizontal"
android:orientation="vertical">
<RelativeLayout
android:layout_width="330dp"
android:layout_height="50dp"
android:orientation="horizontal"
android:paddingLeft="10dp"
android:paddingRight="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="请完成安全验证"
android:textColor="@android:color/black"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_delete"
android:layout_width="27dp"
android:layout_height="27dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:background="@drawable/et_delete" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@android:color/darker_gray" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp">
<com.example.verificationcodejavademo.widget.DragImageView
android:id="@+id/dragView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<ImageView
android:id="@+id/tv_refresh"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_gravity="right|top"
android:paddingLeft="5dp"
android:paddingTop="5dp"
android:paddingRight="15dp"
android:paddingBottom="5dp"
android:src="@drawable/icon_refresh" />
</FrameLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:gravity="center_horizontal"
android:orientation="vertical">
<RelativeLayout
android:layout_width="330dp"
android:layout_height="50dp"
android:orientation="horizontal"
android:paddingLeft="10dp"
android:paddingRight="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="请完成安全验证"
android:textColor="@android:color/black"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_delete"
android:layout_width="27dp"
android:layout_height="27dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:background="@drawable/et_delete" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@android:color/darker_gray" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<com.example.verificationcodejavademo.widget.WordImageView
android:id="@+id/wordView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<ImageView
android:id="@+id/tv_refresh"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_gravity="right|top"
android:paddingLeft="5dp"
android:paddingTop="5dp"
android:paddingRight="15dp"
android:paddingBottom="5dp"
android:src="@drawable/icon_refresh" />
</FrameLayout>
<TextView
android:id="@+id/bottomTitle"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_margin="10dp"
android:background="@drawable/shape_bottom_title_bg"
android:gravity="center"
android:textColor="@android:color/black"
android:textSize="18dp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<FrameLayout
android:id="@+id/drag_fl_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<ImageView
android:id="@+id/drag_iv_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY" />
<ImageView
android:id="@+id/drag_iv_block"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY" />
<View
android:id="@+id/drag_v_flash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/drag_flash" />
<com.example.verificationcodejavademo.widget.DiyStyleTextView
android:id="@+id/drag_tv_tips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#bbf2ece1"
android:gravity="center_vertical"
android:paddingLeft="12dp"
android:paddingTop="2dp"
android:paddingBottom="2dp"
android:text="你本次用了1.0秒"
android:textColor="#323232"
android:textSize="14dp" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="6dp">
<SeekBar
android:id="@+id/drag_sb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:progressDrawable="@drawable/drag_seek_progress"
android:splitTrack="false"
android:thumb="@drawable/drag_btn_error"
android:thumbOffset="0dp" />
<TextView
android:id="@+id/drag_tv_tips2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="向右拖动滑块填充拼图"
android:textSize="16dp" />
</FrameLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"
android:layout_gravity="center_horizontal"
android:background="@android:color/transparent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<ProgressBar
android:id="@+id/pb_load"
android:layout_width="65dp"
android:layout_height="65dp"
android:layout_centerInParent="true" />
</RelativeLayout>
<TextView
android:id="@+id/tv_load_dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="加载中..."
android:textColor="#9a9b98"
android:textSize="12sp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:gravity="center"
android:orientation="vertical">
<FrameLayout
android:id="@+id/word_fl_content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/word_iv_cover"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY" />
<View
android:id="@+id/word_v_flash"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/drag_flash" />
</FrameLayout>
</LinearLayout>
\ No newline at end of file
<resources>
<string name="app_name">VerificationCode</string>
</resources>
<resources>
<style name="dialog" parent="@android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:backgroundDimAmount">0.6</item>
</style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
package com.example.verificationcodejavademo;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment