最近一直想写一片视图的绘制过程,一张图,从测量布局到绘制,真的会的人也不用看,不会的人吧看完也不懂..尴尬。
这几天项目用到手势密码,网上下载了改了改。发现晚上很多都是自己从头画到底,很多带的功能不少,但是实际开发中就很尴尬。自己有想到了个实现方法,闲来没事搞了下,感觉可行分享出来。
这个使用的时候必须注意要给LinGesturePassword设置上背景,否则不会调用的的onDraw,主要是父类组件的一个特性,父类容器是用来发子类的所以没背景是不会调用的的onDraw。
这里要使用的时候添加的子类必须实现LinGesturePassword.ZhuangTai接口的三个方法,这样滑动才会改变,这里为了方便英语学渣的我直接没用使用有道查询,有强迫的人可以修改。这里我还主要标记了一下onLayout方法,这是父类的一个布局方法,有的时候是很方便的,自定义视图不自己写也要理解,不然怎么魔改别人的代码。
public class LinGesturePassword extends LinearLayout {
float xy[] = new float[2];
List<float[]> list = new ArrayList<>();
List<Integer> lintp = new ArrayList<>();//保存密码id
WanChengHuiDiao wanChengHuiDiao;
private int lineColor = 0xFF378FC9;
private int lineColorCuo = 0xFF378FC9;
private int linStrokeWidth=10;
private boolean isTouch = true;
private boolean cuowu=false;
public LinGesturePassword(Context context) {
super(context);
}
public LinGesturePassword(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public LinGesturePassword(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public LinGesturePassword(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void onDraw(Canvas canvas) {
Log.e("aaa", "运行拉啦啦啦");
super.onDraw(canvas);
drawAllLine(canvas);
}
/**
* 关闭手势密码
* @param touch
*/
public void setTouch(boolean touch) {
isTouch = touch;
}
public void setWanChengHuiDiao(WanChengHuiDiao wanChengHuiDiao) {
this.wanChengHuiDiao = wanChengHuiDiao;
}
/**
* 线条颜色
* @param lineColor
*/
public void setLineColor(int lineColor) {
this.lineColor = lineColor;
}
/**
* 选择错误后的线条颜色
* @param lineColorCuo
*/
public void setLineColorCuo(int lineColorCuo) {
this.lineColorCuo = lineColorCuo;
}
/**
* 设置线条粗细
* @param linStrokeWidth
*/
public void setLinStrokeWidth(int linStrokeWidth) {
this.linStrokeWidth = linStrokeWidth;
}
private void drawAllLine(Canvas canvas) {
Paint mPaint = new Paint();
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
if(cuowu) {
mPaint.setColor(lineColorCuo);
}else {
mPaint.setColor(lineColor);
}
mPaint.setStrokeWidth(linStrokeWidth);
for (int i = 0; i < list.size(); i++) {
if (i == list.size() - 1) {
canvas.drawLine(list.get(i)[0], list.get(i)[1], xy[0], xy[1], mPaint);
} else {
canvas.drawLine(list.get(i)[0], list.get(i)[1], list.get(i + 1)[0], list.get(i + 1)[1], mPaint);
}
}
}
/**
* 控制子控件的换行
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int cellWidth = 0;//宽度偏差
int columns = 0;//保存每排放的第几个view
int y = 0; //高度偏移量
int maxy = 0; //最大高度
int count = getChildCount();//子view数量
int gw = getMeasuredWidth() / 3;//获取宽度1/3这里主要是我一排放三个,看需求可以改成变量
for (int j = 0; j < count; j++) {
final View childView = getChildAt(j);
// 获取子控件Child的宽高
int w = childView.getMeasuredWidth();
int h = childView.getMeasuredHeight();
// 计算子控件的顶点坐标
int left = (gw - w) / 2 + cellWidth;
cellWidth += gw;//下一一个位置
int top = y + (gw - w) / 2;
// 布局子控件
maxy = Math.max(gw, maxy);
columns++;
childView.layout(left, top, left + w, top + h);
//判断一排是否到三个了,到三个换行
if (columns >= 3) {
cellWidth = 0;
y += maxy;
maxy = 0;
columns = 0;
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float[] fxy;
if (isTouch) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
chongZhi();
cuowu=false;
fxy = jx(event.getX(), event.getY());
if (fxy != null) {
list.add(fxy);
xy[0] = event.getX();
xy[1] = event.getY();
}
break;
case MotionEvent.ACTION_MOVE:
fxy = jx(event.getX(), event.getY());
if (fxy != null) {
list.add(fxy);
}
xy[0] = event.getX();
xy[1] = event.getY();
break;
case MotionEvent.ACTION_UP:
if (wanChengHuiDiao == null) {
list.clear();
lintp.clear();
} else {
wanChengHuiDiao.wanCheng(lintp);
}
break;
}
postInvalidate();
return true;
} else {
return false;
}
}
/**
* 判断滑动的点是否和子类交叉
* @param x
* @param y
* @return
*/
public float[] jx(float x, float y) {
int count = getChildCount();
for (int j = 0; j < count; j++) {
View childView = getChildAt(j);
boolean t = true;
for (int i = 0; i < lintp.size(); i++) {
if (lintp.get(i) == j) {
t = false;
break;
}
}
if (t) {
if (childView.getX() < x && childView.getX() + childView.getWidth() > x) {
if (childView.getY() < y && childView.getY() + childView.getHeight() > y) {
lintp.add(j);
if (childView instanceof ZhuangTai) {
((ZhuangTai) childView).xuanZhong();
}
return new float[]{childView.getX() + childView.getWidth() / 2, childView.getY() + childView.getHeight() / 2};
}
}
}
}
return null;
}
/**
* 重置界面
*/
public void chongZhi() {
int count = getChildCount();
for (int j = 0; j < count; j++) {
View childView = getChildAt(j);
if (childView instanceof ZhuangTai) {
((ZhuangTai) childView).weiXuanZhong();
}
}
list.clear();
lintp.clear();
postInvalidate();
}
/**
* 密码错误
*/
public void cuoWuJieMina(){
cuowu=true;
int count = getChildCount();
for (int j = 0; j < count; j++) {
View childView = getChildAt(j);
if (childView instanceof ZhuangTai) {
((ZhuangTai) childView).cuoWu();
}
}
postInvalidate();
}
public interface ZhuangTai {
void xuanZhong();
void cuoWu();
void weiXuanZhong();
}
public interface WanChengHuiDiao {
void wanCheng(List<Integer> list);
}
}
随便实现的一个子类,正常用的ImageView的也不错看需求了,现在这个实现很难看,你可以找设计设置几张图完事,项目在接口放里面自己画也可,感觉还是跟要UI图方便......
public class Textvvvv extends TextView implements LinGesturePassword.ZhuangTai {
public Textvvvv(Context context) {
super(context);
}
public Textvvvv(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public Textvvvv(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public Textvvvv(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void xuanZhong() {
setText("选中了");
setBackgroundColor(0xFF378FC9);
}
@Override
public void cuoWu() {
setText("选择错误");
setBackgroundColor(0xFFff0000);
}
@Override
public void weiXuanZhong() {
setText("未选中");
setBackgroundColor(0xFF3ab826);
}
}
布局文件随便加就行自动布局,但是要设置高度DP的话基本不会出现什么大的布局问题。这个视图的是现实根据父视图大小来实现平均的,可以改变LinGesturePassword的宽度设置居中然后会还看点,fLinGesturePassword的宽度必须大于三个的TextView的总宽度,不然就没间距了或者重叠。
<?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:orientation="vertical">
<com.ag.text.myapplication.LinGesturePassword
android:id="@+id/lin1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff"
android:layout_margin="10dp"
android:orientation="vertical">
<com.ag.text.myapplication.Textvvvv
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#FF3ab826"
android:text="Hello World!"
android:layout_marginTop="50dp"
/>
<com.ag.text.myapplication.Textvvvv
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#FF3ab826"
android:text="Hello World!"
android:layout_marginTop="50dp"
/>
<com.ag.text.myapplication.Textvvvv
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#FF3ab826"
android:text="Hello World!"
android:layout_marginTop="50dp"
/>
<com.ag.text.myapplication.Textvvvv
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#FF3ab826"
android:text="Hello World!"
android:layout_marginTop="50dp"
/>
<com.ag.text.myapplication.Textvvvv
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#FF3ab826"
android:text="Hello World!"
android:layout_marginTop="50dp"
/>
<com.ag.text.myapplication.Textvvvv
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#FF3ab826"
android:text="Hello World!"
android:layout_marginTop="50dp"
/>
<com.ag.text.myapplication.Textvvvv
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#FF3ab826"
android:text="Hello World!"
android:layout_marginTop="50dp"
/>
<com.ag.text.myapplication.Textvvvv
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#FF3ab826"
android:text="Hello World!"
android:layout_marginTop="50dp"
/>
<com.ag.text.myapplication.Textvvvv
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#FF3ab826"
android:text="Hello World!"
android:layout_marginTop="50dp"
/>
<com.ag.text.myapplication.Textvvvv
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#FF3ab826"
android:text="Hello World!"
android:layout_marginTop="50dp"
/>
</com.ag.text.myapplication.LinGesturePassword>
</LinearLayout>
使用
public class MainActivity extends AppCompatActivity {
LinGesturePassword lin1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lin1=findViewById(R.id.lin1);
// lin1.setLineColor();//线条颜色
// lin1.setLineColorCuo();//错误线条颜色
lin1.setLinStrokeWidth(20);//线条粗度
lin1.setWanChengHuiDiao(new LinGesturePassword.WanChengHuiDiao() {
@Override
public void wanCheng(List<Integer> list) {
String pw="";
for (int i = 0; i < list.size(); i++) {
pw+=list.get(i);
}
Log.e("aaa","密码"+pw);
// lin1.cuoWuJieMina();//密码错误是调用
// lin1.chongZhi();//重置界面
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("main","main"+event.getAction());
return super.onTouchEvent(event);
}
gitee可能会跟新呵呵~~