本文通过仿支付宝手势密码,进一步熟悉自定义view。
效果:
分析:我们将该效果分为两部分,文字上面的view为一部分,下面的手势为一部分。可以看到上面的view随着下面绘制完成,就开始绘制。
首先分析下面部分。
步骤:
1.绘制9个空心圆
2.随着手势的滑动绘制实心圆
3.绘制手势滑动的线
可以看到进入以后首先显示的是9个空心的圆圈,那么本文先实现绘制9个空心圆
一 整体代码和效果(后面有优化部分)
public class GesturePwdView extends View {
private Paint mPaint;
private int pieceWidth;//每个大view圆点宽度
private int pieceMargin = 100;//每个圆间隔的距离
private Bitmap unSelectedBitmap;
public GesturePwdView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public GesturePwdView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
unSelectedBitmap = BitmapUtils.drawableToBitmap(getResources().getDrawable(R.drawable.circle_blue_unselect));
pieceWidth = unSelectedBitmap.getWidth();
mPaint = new Paint();
mPaint.setStrokeWidth(5);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth : pieceMargin * 2 + pieceWidth * 3, (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight : pieceMargin * 2 + pieceWidth * 3);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
float x;
float y;
x = (float) (getWidth() / 2 - 1.5 * pieceWidth - pieceMargin + j * pieceWidth + pieceMargin * j);
y = (float) (i * pieceWidth + i * pieceMargin);
canvas.drawBitmap(unSelectedBitmap, x, y, mPaint);
}
}
}
}
二 重写onMeasure 计算宽高
定义了两个变量pieceWidth每个圆的大小直径和pieceMargin圆之间的间隔距离。
重写onMeasure 计算控件的大小,比较容易得出控件宽高是pieceMargin * 2 + pieceWidth * 3
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth : pieceMargin * 2 + pieceWidth * 3, (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight : pieceMargin * 2 + pieceWidth * 3);
}
三 重写onDraw画圆
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
float x;
float y;
x = (float) (getWidth() / 2 - 1.5 * pieceWidth - pieceMargin + j * pieceWidth + pieceMargin * j);
y = (float) (i * pieceWidth + i * pieceMargin);
canvas.drawBitmap(unSelectedBitmap, x, y, mPaint);
}
}
}
代码很简单 求出圆(本例用一张图片)的左上点坐标 ,比如第一个圆的x坐标:getWidth() / 2 - 1.5 * pieceWidth - pieceMargin
四 优化
为了方便调用,将绘制的圆和右边距设置为属性,并且居中显示
最终效果:
优化部分,自定义属性,从属性加载
private void init(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.GesturePwd);
Drawable dw_unSeclect = ta.getDrawable(R.styleable.GesturePwd_unselectedDrawable);
if (dw_unSeclect != null) {
unSelectedBitmap = BitmapUtils.drawableToBitmap(dw_unSeclect);
} else {
unSelectedBitmap = BitmapUtils.drawableToBitmap(getResources().getDrawable(R.drawable.circle_blue_unselect));
}
pieceMargin = ta.getDimensionPixelOffset(R.styleable.GesturePwd_pieceMargin, 100);
pieceWidth = unSelectedBitmap.getWidth();
mPaint = new Paint();
mPaint.setStrokeWidth(5);
}
自定义属性
<declare-styleable name="GesturePwd">
<!-- 选中状态的手势点-->
<attr name="selectedDrawable" format="reference"/>
<!-- 未选中状态的手势点-->
<attr name="unselectedDrawable" format="reference"/>
<attr name="selectedErrorDrawable" format="reference"/>
<attr name="pieceMargin" format="dimension"/>
<!-- 验证失败后再次验证的拦截时间-->
<attr name="waitTime" format="integer"/>
<!-- 验证的最大失败次数-->
<attr name="maxFailCounts" format="integer"/>
<!-- 绘制时最少连接的点数-->
<attr name="minPoint" format="integer"/>
<!-- 绘制时最大连接的点数-->
<attr name="maxPoint" format="integer"/>
<!-- 连接线的颜色-->
<attr name="pathColor" format="color"/>
<attr name="errorPathColor" format="color"/>
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.gesturepwd.GesturePwdView
android:id="@+id/gesturePwdView"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:pieceMargin="50dp"
app:unselectedDrawable="@drawable/circle_blue_unselect" />
</LinearLayout>
未选中状态的drawable代码
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<size
android:width="50dp"
android:height="50dp" />
<solid android:color="@color/Blue56" />
</shape>
</item>
<item
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp">
<shape android:shape="oval">
<solid android:color="@color/white" />
</shape>
</item>
</layer-list>
BitmapUtils
public class BitmapUtils {
public static Bitmap drawableToBitmap (Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable)drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
以上代码比较简单,就不逐步讲解了,本文就到这里,下一篇将描述随着手势的绘制而实心圆和路径 Android仿支付宝手势密码二