android6自定义锁屏,Android

前言

Android 自定义 View 技能是成为高级工程师所必备的,笔者觉得自定义 View 没有什么捷径可走,唯有经常练习才能解决产品需求。笔者也好久没有写自定义 View 了,赶紧写个控件找点感觉回来。

本文实现的是一个 锁屏图案的自定义控件。效果图如下:

09d8e5b2bce16f17a0110fc447333f6e.gif

LockView 介绍

自定义属性:

0402c8ce0ce5c51627dfadd1511fb419.png

引用方式:

(1) 在布局文件中引入

android:id="@+id/lock_view"

app:rowCount="4"

app:normalColor=""

app:moveColor=""

app:errorColor=""

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_margin="40dp" />

(2) 在代码中设置正确的图案,用于校验是否匹配成功,并在回调中获取结果

List intList = new ArrayList<>();

intList.add(3);

intList.add(7);

intList.add(4);

intList.add(2);

lockView.setStandard(intList);

lockView.setOnDrawCompleteListener(new LockView.OnDrawCompleteListener() {

@Override

public void onComplete(boolean isSuccess) {

Toast.makeText(CustomViewActivity.this, isSuccess ? "success" : "fail", Toast.LENGTH_SHORT).show();

}

});

实现思路

以默认状态绘制 rowCount * rowCount 个圆,外圆颜色需要在内圆颜色上加上一定的透明度。

在 onTouchEvent() 方法中,判断当前触摸点与各个圆的圆心距离是否小于圆的半径,决定各个圆此时处于哪个状态(normal,move,error),调用 invalidate() 重新绘制,更新颜色。

​将手指滑动触摸过的圆的坐标添加到一个 ArrayList 中,使用 Path 连接该集合中选中的圆,即可绘制出划过的路径线。

实现步骤

自定义属性

在 res/values 目录下新建 attrs.xml 文件:

获取自定义属性

public LockView(Context context) {

this(context, null);

}

public LockView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

readAttrs(context, attrs);

init();

}

/**

* 获取自定义属性

*/

private void readAttrs(Context context, AttributeSet attrs) {

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LockView);

normalColor = typedArray.getColor(R.styleable.LockView_normalColor, DEFAULT_NORMAL_COLOR);

moveColor = typedArray.getColor(R.styleable.LockView_moveColor, DEFAULT_MOVE_COLOR);

errorColor = typedArray.getColor(R.styleable.LockView_errorColor, DEFAULT_ERROR_COLOR);

rowCount = typedArray.getInteger(R.styleable.LockView_rowCount, DEFAULT_ROW_COUNT);

typedArray.recycle();

}

/**

* 初始化

*/

private void init() {

stateSparseArray = new SparseIntArray(rowCount * rowCount);

points = new PointF[rowCount * rowCount];

innerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

innerCirclePaint.setStyle(Paint.Style.FILL);

outerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

outerCirclePaint.setStyle(Paint.Style.FILL);

linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

linePaint.setStyle(Paint.Style.STROKE);

linePaint.setStrokeCap(Paint.Cap.ROUND);

linePaint.setStrokeJoin(Paint.Join.ROUND);

linePaint.setStrokeWidth(30);

linePaint.setColor(moveColor);

}

计算圆的半径

设定外圆半径和相邻两圆之间间距相同,内圆半径是外圆半径的一半,所以半径计算方式为:

radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f;

设置各圆坐标

各圆坐标使用一维数组保存,计算方式为:

// 各个圆设置坐标点

for (int i = 0; i < rowCount * rowCount; i++) {

points[i] = new PointF(0, 0);

points[i].set((i % rowCount * 3 + 1) * radius, (i / rowCount * 3 + 1) * radius);

}

测量 View 宽高

根据测量模式设置控件的宽高,当布局文件中设置的是 wrap_content ,默认将控件宽高设置为 600dp

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int width = getSize(widthMeasureSpec);

int height = getSize(heightMeasureSpec);

setMeasuredDimension(width, height);

}

private int getSize(int measureSpec) {

int mode = MeasureSpec.getMode(measureSpec);

int size = MeasureSpec.getSize(measureSpec);

if (mode == MeasureSpec.EXACTLY) {

return size;

} else if (mode == MeasureSpec.AT_MOST) {

return Math.min(size, dp2Px(600));

}

return dp2Px(600);

}

onTouchEvent() 触摸事件

在手指滑动过程中,根据当前触摸点坐标是否落在圆的范围内,更新该圆的状态,在重新绘制时,绘制成新的颜色。手指抬起时,将存放状态的 list,选中圆的 list ,linePath 重置,并将结果回调出来。

private PointF touchPoint;

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

reset();

case MotionEvent.ACTION_MOVE:

if (touchPoint == null) {

touchPoint = new PointF(event.getX(), event.getY());

} else {

touchPoint.set(event.getX(), event.getY());

}

for (int i = 0; i < rowCount * rowCount; i++) {

// 是否触摸在圆的范围内

if (getDistance(touchPoint, points[i]) < radius) {

stateSparseArray.put(i, STATE_MOVE);

if (!selectedList.contains(points[i])) {

selectedList.add(points[i]);

}

break;

}

}

break;

case MotionEvent.ACTION_UP:

if (check()) { // 正确图案

if (listener != null) {

listener.onComplete(true);

}

for (int i = 0; i < stateSparseArray.size(); i++) {

int index = stateSparseArray.keyAt(i);

stateSparseArray.put(index, STATE_MOVE);

}

} else { // 错误图案

for (int i = 0; i < stateSparseArray.size(); i++) {

int index = stateSparseArray.keyAt(i);

stateSparseArray.put(index, STATE_ERROR);

}

linePaint.setColor(0xeeff0000);

if (listener != null) {

listener.onComplete(false);

}

}

touchPoint = null;

if (timer == null) {

timer = new Timer();

}

timer.schedule(new TimerTask() {

@Override

public void run() {

linePath.reset();

linePaint.setColor(0xee0000ff);

selectedList.clear();

stateSparseArray.clear();

postInvalidate();

}

}, 1000);

break;

}

invalidate();

return true;

}

绘制各圆和各圆之间连接线段

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

drawCircle(canvas);

drawLinePath(canvas);

}

private void drawCircle(Canvas canvas) {

// 依次从索引 0 到索引 8,根据不同状态绘制圆点

for (int index = 0; index < rowCount * rowCount; index++) {

int state = stateSparseArray.get(index);

switch (state) {

case STATE_NORMAL:

innerCirclePaint.setColor(normalColor);

outerCirclePaint.setColor(normalColor & 0x66ffffff);

break;

case STATE_MOVE:

innerCirclePaint.setColor(moveColor);

outerCirclePaint.setColor(moveColor & 0x66ffffff);

break;

case STATE_ERROR:

innerCirclePaint.setColor(errorColor);

outerCirclePaint.setColor(errorColor & 0x66ffffff);

break;

}

canvas.drawCircle(points[index].x, points[index].y, radius, outerCirclePaint);

canvas.drawCircle(points[index].x, points[index].y, radius / 2f, innerCirclePaint);

}

}

完整 View 代码:

/**

* Created by star.tao on 2018/5/30.

* email: xing-java@foxmail.com

* github: https://github.com/xing16

*/

public class LockView extends View {

private static final int DEFAULT_NORMAL_COLOR = 0xee776666;

private static final int DEFAULT_MOVE_COLOR = 0xee0000ff;

private static final int DEFAULT_ERROR_COLOR = 0xeeff0000;

private static final int DEFAULT_ROW_COUNT = 3;

private static final int STATE_NORMAL = 0;

private static final int STATE_MOVE = 1;

private static final int STATE_ERROR = 2;

private int normalColor; // 无滑动默认颜色

private int moveColor; // 滑动选中颜色

private int errorColor; // 错误颜色

private float radius; // 外圆半径

private int rowCount;

private PointF[] points; // 一维数组记录所有圆点的坐标点

private Paint innerCirclePaint; // 内圆画笔

private Paint outerCirclePaint; // 外圆画笔

private SparseIntArray stateSparseArray;

private List selectedList = new ArrayList<>();

private List standardPointsIndexList = new ArrayList<>();

private Path linePath = new Path(); // 手指移动的路径

private Paint linePaint;

private Timer timer;

public LockView(Context context) {

this(context, null);

}

public LockView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

readAttrs(context, attrs);

init();

}

private void readAttrs(Context context, AttributeSet attrs) {

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LockView);

normalColor = typedArray.getColor(R.styleable.LockView_normalColor, DEFAULT_NORMAL_COLOR);

moveColor = typedArray.getColor(R.styleable.LockView_moveColor, DEFAULT_MOVE_COLOR);

errorColor = typedArray.getColor(R.styleable.LockView_errorColor, DEFAULT_ERROR_COLOR);

rowCount = typedArray.getInteger(R.styleable.LockView_rowCount, DEFAULT_ROW_COUNT);

typedArray.recycle();

}

private void init() {

stateSparseArray = new SparseIntArray(rowCount * rowCount);

points = new PointF[rowCount * rowCount];

innerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

innerCirclePaint.setStyle(Paint.Style.FILL);

outerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

outerCirclePaint.setStyle(Paint.Style.FILL);

linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

linePaint.setStyle(Paint.Style.STROKE);

linePaint.setStrokeCap(Paint.Cap.ROUND);

linePaint.setStrokeJoin(Paint.Join.ROUND);

linePaint.setStrokeWidth(30);

linePaint.setColor(moveColor);

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

// 外圆半径 = 相邻外圆之间间距 = 2倍内圆半径

radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f;

// 各个圆设置坐标点

for (int i = 0; i < rowCount * rowCount; i++) {

points[i] = new PointF(0, 0);

points[i].set((i % rowCount * 3 + 1) * radius, (i / rowCount * 3 + 1) * radius);

}

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int width = getSize(widthMeasureSpec);

int height = getSize(heightMeasureSpec);

setMeasuredDimension(width, height);

}

private int getSize(int measureSpec) {

int mode = MeasureSpec.getMode(measureSpec);

int size = MeasureSpec.getSize(measureSpec);

if (mode == MeasureSpec.EXACTLY) {

return size;

} else if (mode == MeasureSpec.AT_MOST) {

return Math.min(size, dp2Px(600));

}

return dp2Px(600);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

drawCircle(canvas);

drawLinePath(canvas);

}

private void drawCircle(Canvas canvas) {

// 依次从索引 0 到索引 8,根据不同状态绘制圆点

for (int index = 0; index < rowCount * rowCount; index++) {

int state = stateSparseArray.get(index);

switch (state) {

case STATE_NORMAL:

innerCirclePaint.setColor(normalColor);

outerCirclePaint.setColor(normalColor & 0x66ffffff);

break;

case STATE_MOVE:

innerCirclePaint.setColor(moveColor);

outerCirclePaint.setColor(moveColor & 0x66ffffff);

break;

case STATE_ERROR:

innerCirclePaint.setColor(errorColor);

outerCirclePaint.setColor(errorColor & 0x66ffffff);

break;

}

canvas.drawCircle(points[index].x, points[index].y, radius, outerCirclePaint);

canvas.drawCircle(points[index].x, points[index].y, radius / 2f, innerCirclePaint);

}

}

/**

* 绘制选中点之间相连的路径

*

* @param canvas

*/

private void drawLinePath(Canvas canvas) {

// 重置linePath

linePath.reset();

// 选中点个数大于 0 时,才绘制连接线段

if (selectedList.size() > 0) {

// 起点移动到按下点位置

linePath.moveTo(selectedList.get(0).x, selectedList.get(0).y);

for (int i = 1; i < selectedList.size(); i++) {

linePath.lineTo(selectedList.get(i).x, selectedList.get(i).y);

}

// 手指抬起时,touchPoint设置为null,使得已经绘制游离的路径,消失掉,

if (touchPoint != null) {

linePath.lineTo(touchPoint.x, touchPoint.y);

}

canvas.drawPath(linePath, linePaint);

}

}

private PointF touchPoint;

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

reset();

case MotionEvent.ACTION_MOVE:

if (touchPoint == null) {

touchPoint = new PointF(event.getX(), event.getY());

} else {

touchPoint.set(event.getX(), event.getY());

}

for (int i = 0; i < rowCount * rowCount; i++) {

// 是否触摸在圆的范围内

if (getDistance(touchPoint, points[i]) < radius) {

stateSparseArray.put(i, STATE_MOVE);

if (!selectedList.contains(points[i])) {

selectedList.add(points[i]);

}

break;

}

}

break;

case MotionEvent.ACTION_UP:

if (check()) { // 正确图案

if (listener != null) {

listener.onComplete(true);

}

for (int i = 0; i < stateSparseArray.size(); i++) {

int index = stateSparseArray.keyAt(i);

stateSparseArray.put(index, STATE_MOVE);

}

} else { // 错误图案

for (int i = 0; i < stateSparseArray.size(); i++) {

int index = stateSparseArray.keyAt(i);

stateSparseArray.put(index, STATE_ERROR);

}

linePaint.setColor(0xeeff0000);

if (listener != null) {

listener.onComplete(false);

}

}

touchPoint = null;

if (timer == null) {

timer = new Timer();

}

timer.schedule(new TimerTask() {

@Override

public void run() {

linePath.reset();

linePaint.setColor(0xee0000ff);

selectedList.clear();

stateSparseArray.clear();

postInvalidate();

}

}, 1000);

break;

}

invalidate();

return true;

}

/**

* 清除绘制图案的条件,当触发 invalidate() 时将清空图案

*/

private void reset() {

touchPoint = null;

linePath.reset();

linePaint.setColor(0xee0000ff);

selectedList.clear();

stateSparseArray.clear();

}

public void onStop() {

timer.cancel();

}

private boolean check() {

if (selectedList.size() != standardPointsIndexList.size()) {

return false;

}

for (int i = 0; i < standardPointsIndexList.size(); i++) {

Integer index = standardPointsIndexList.get(i);

if (points[index] != selectedList.get(i)) {

return false;

}

}

return true;

}

public void setStandard(List pointsList) {

if (pointsList == null) {

throw new IllegalArgumentException("standard points index can't null");

}

if (pointsList.size() > rowCount * rowCount) {

throw new IllegalArgumentException("standard points index list can't large to rowcount * columncount");

}

standardPointsIndexList = pointsList;

}

private OnDrawCompleteListener listener;

public void setOnDrawCompleteListener(OnDrawCompleteListener listener) {

this.listener = listener;

}

public interface OnDrawCompleteListener {

void onComplete(boolean isSuccess);

}

private float getDistance(PointF centerPoint, PointF downPoint) {

return (float) Math.sqrt(Math.pow(centerPoint.x - downPoint.x, 2) + Math.pow(centerPoint.y - downPoint.y, 2));

}

private int dp2Px(int dpValue) {

return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());

}

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值