之前公司接到的一个图形验证码的需求,需求是那种按顺序点选验证码的效果,网上查了下资料,没发现对应的需求,然后就自己研究做了一个出来,一方面分享给大家,一方面给自己做个笔记.
上效果:
video2gif_20190330_150424.gif
因为这里没有走网络耗时操作,所以你们可能看不到第四个点击的效果,因为点击第四个的时候我直接回调吐司坐标了.
实现原理其实挺简单的:
首先我们得弄一个图片的容器(PictureTagLayout),在这个容器内,可以动态添加view
然后弄一个点击效果的view(PictureTagView)
直接上代码:
<1>首先是布局的:
我这是模拟登陆时的一个验证效果,所以我做个了一个自定义dialog,这个就是dialog的布局代码
假如我们的布局当中图片的宽高用的单位不是px,而是dp,那么就得考虑dp和px的转换了,大家不用担心说坐标不精确的问题,肯定是有个误差范围的
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/v_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent"
android:gravity="center"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="380dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:clickable="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="14dp"
android:layout_marginRight="14dp"
android:layout_marginTop="16dp"
android:background="@mipmap/validation_bk"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ly"
android:layout_width="match_parent"
android:layout_height="26dp"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="图形验证码"
android:textColor="@color/yellow"
android:textSize="13dp" />
<TextView
android:id="@+id/tv_button"
android:layout_width="100dp"
android:layout_height="36dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:background="@mipmap/wallet_btn"
android:gravity="center"
android:text="提交"
android:textColor="@color/white"
android:textSize="13dp"
android:visibility="gone" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/ly"
android:clickable="true"
android:gravity="center"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/text_hint"
android:layout_width="match_parent"
android:layout_height="36dp"
android:layout_gravity="center"
android:gravity="center"
android:layout_marginBottom="12dp"
android:text="一 叶 知 秋"
android:textColor="@color/white"
android:textSize="18dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="请按以上文字顺序依次点击图片上的文字"
android:textColor="#89aad9"
android:textSize="12dp" />
<com.haihong.codeselection.PictureTagLayout
android:id="@+id/img_select"
android:layout_width="600px"
android:layout_height="300px"
android:layout_marginTop="20dp"
android:background="@mipmap/image_code"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/tv_changeone"
android:layout_width="100dp"
android:layout_height="36dp"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:background="@mipmap/wallet_btn"
android:gravity="center"
android:text="换一张"
android:textColor="@color/white"
android:textSize="13dp" />
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/img_close"
android:layout_width="34dp"
android:layout_height="34dp"
android:layout_alignParentRight="true"
android:scaleType="centerCrop"
android:src="@mipmap/close" />
</RelativeLayout>
</RelativeLayout>
/*
*容器
* */
@SuppressLint("NewApi")
public class PictureTagLayout extends RelativeLayout implements OnTouchListener, View.OnClickListener {
int startX;
int startY;
int startTouchViewLeft = 0;
int startTouchViewTop = 0;
private View touchView, clickView;
private Context context;
private int height;
private int width;
float xDown, yDown, xUp;
private int mNum;
private String user_position = "";
public PictureTagLayout(Context context) {
super(context, null);
}
public PictureTagLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
private void init() {
this.setOnTouchListener(this);
}
//用作更新图片时操作
public void setImage(Context context, int num) {
this.context = context;
this.mNum = num;
user_position = "";
}
//开始的位置小于结束的位置 向左滑动
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
xDown = event.getX();
yDown = event.getY();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
touchView = null;
if (clickView != null) {
clickView = null;
}
startX = (int) event.getX();
startY = (int) event.getY();
if (hasView(startX, startY)) {//如果点击位置已经有了
startTouchViewLeft = touchView.getLeft();
startTouchViewTop = touchView.getTop();
} else {
showPopup();
}
Log.i("******点击的位置--x-", startX + "*----y" + startY);
break;
}
return true;
}
public void showPopup() {
mNum++;
addData(mNum);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
}
}
public void addData(int num) {
if (hasView(startX, startY)) {//有view 就不添加到集合里
startTouchViewLeft = touchView.getLeft();
startTouchViewTop = touchView.getTop();
} else {
if (num < 5) {
Log.i("addItem 点击的位置--x-", startX + "*----y" + startY);
addItem(startX, startY, num + "");
//完成的时候 返给主界面回调
if (num == 4) {
//咱们再布局中的PictureTagLayout宽高为
//android:layout_width="600px"
//android:layout_height="300px"
//为了精确的拿到正确的坐标:
//第一个600为图片本身像素宽,第二个600为布局中设置的像素宽
//第一个300为图片本身像素宽,第二个300为布局中设置的像素宽
//有人会问为什么需要600/600呢不是刚好就是1吗? 其实这里只是为了表达,
//假如你布局当中设置的宽不是600的话,那就需要这么设置了,不然无法得到精确的坐标与后台匹配
user_position += startX * 600 / 600 + "," + startY * 300 / 300;
//所以你们可能看不到第四个点击的效果
EventBus.getDefault().postSticky(user_position);
mNum = 0;
user_position = "";
} else {
user_position += startX * 300 / 600 + "," + startY * 200 / 400 + "|";
}
}
}
}
private void addItem(int x, int y, String share) {
View view = null;
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
//第一次点击添加标签是 PictureTagView.Direction.Measure 让TagView自己测量方向
view = new PictureTagView(getContext(), PictureTagView.Direction.Right, share);
view.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
int w = MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0,
View.MeasureSpec.UNSPECIFIED);
view.measure(w, h);
height = view.getMeasuredHeight();
width = view.getMeasuredWidth();
//标签在右面 点击位置 x-标签宽度 右边的标签并不是以圆点开始的 而是以左边的wei
params.leftMargin = x - width + 10;
params.topMargin = y - height / 2;
//上下位置在视图内
if (params.topMargin <= 0) {
params.topMargin = 0;
} else if ((params.topMargin + height) > getHeight()) {
params.topMargin = getHeight() - height;
}
if (params.leftMargin <= 0) {
params.leftMargin = 0;
}
if (params.rightMargin >= getWidth()) {
params.rightMargin = getWidth();
}
this.addView(view, params);
}
private boolean hasView(int x, int y) {
//循环获取子view,判断xy是否在子view上,即判断是否按住了子view
for (int index = 0; index < this.getChildCount(); index++) {
View view = this.getChildAt(index);
int left = (int) view.getX();
int top = (int) view.getY();
int right = view.getRight();
int bottom = view.getBottom();
// Toast.makeText(context, "已经有的---"+((PictureTagView)view).getShare()+"-x-"+left+"--y--"+top, Toast.LENGTH_SHORT).show();
Rect rect = new Rect(left, top, right, bottom);
boolean contains = rect.contains(x, y);
//如果是与子view重叠则返回真,表示已经有了view不需要添加新view了
if (contains) {
touchView = view;
touchView.bringToFront();
return true;
}
}
touchView = null;
return false;
}
}
这个是点击后样式的一个view,这个样式可以自定义
/**
*点击样式
* */
public class PictureTagView extends RelativeLayout implements OnEditorActionListener {
private Context context;
private View view;
private TextView tvNum;
public enum Direction {Left, Right, Measure}
private Direction direction;
private InputMethodManager imm;
private String type;
private String share;
public PictureTagView(Context context) {
super(context);
}
public PictureTagView(Context context, Direction direction, String share) {
super(context);
this.context = context;
this.direction = direction;
this.share = share;
initViews();
init();
}
/**
* 初始化视图
**/
protected void initViews() {
view = LayoutInflater.from(context).inflate(R.layout.picturetagview, this, true);
tvNum = (TextView) view.findViewById(R.id.tvNum);
tvNum.setText(share);
}
/**
* 初始化
**/
protected void init() {
imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
return true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
}
有什么可以改进的,请多指教
具体的话直接看demo吧:
github代码:
(https://github.com/a824676719/CodeSelection)
网友评论