手势解锁java后端设计_自己定义九宫格手势解锁

本文介绍了如何在项目中自定义一个九宫格手势解锁功能,包括定义不同状态的枚举、绘制小圆圈的逻辑以及手势解锁View的实现。文章详细讲解了每个关键步骤,如定义圆圈的状态、颜色处理和触摸区域设定等。
摘要由CSDN通过智能技术生成

项目中用到手势解锁,然而没有在GitHub上找到想要的样式= =,仅仅好自己来定义了。以下来看代码~~

基本上非常多应用的手势解锁全都是九宫格的,内部内就是九个小圈圈而已。

那么我们就先来自己定义这个小圈圈吧~

圈圈的颜色选择状态有大致有三种状态。所以我定义了一个枚举来区分

package com.juzisang.com.library;

/**

* Created by 橘子桑 on 2016/3/27.

*/

public enum LockState {

SELECT_STATE,//选中

ERRER_STATE, //错误

DEFAULT_COLOR //默认

}

圈圈分为边框。内部填充色。还有内部圆。所以我定义了三个画笔来区分。

package com.juzisang.com.library;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.util.AttributeSet;

import android.view.View;

/**

* Created by 橘子桑 on 2016/3/27.

*/

public class MarkerView extends View {

//是否显示内部的圈圈

private boolean mInsideNodeShow;

//宽度

protected int mContentWidth;

//宽度

protected int mContentRadius;

//选中状态

protected LockState mCurrentState = LockState.DEFAULT_COLOR;

//画边框画圆的的画笔

private Paint mNodeFramePaint;

private Paint mNodeCirclePaint;

private Paint mNodeFullPaint;

//默认的颜色

private int mDefaultColor = Color.parseColor("#757575");

private int mDefailtFullColor = Color.parseColor("#64757575");

private int mNodeDefaultColor = Color.parseColor("#757575");

//选中的颜色

private int mSelectColor = Color.parseColor("#7ECEF4");

private int mFrameSelectFullColor = Color.parseColor("#647ECEF4");

private int mNodeSelectColor = Color.parseColor("#7ECEF4");

//错误时候的颜色

private int mErrerColor = Color.parseColor("#EC6941");

private int mErrerFullColor = Color.parseColor("#64EC6941");

private int mErrerNodeColor = Color.parseColor("#EC6941");

//边框的宽度

private int mFrameLineWidth;

private int mNodeRadius;

//每一个圈圈的内边距

private int mNodePadding;

//触摸有效的范围

private float mTouchRatio;

//当前标记的位置

private int mNum;

public MarkerView(Context context, AttributeSet attrs) {

super(context, attrs);

initView(context, attrs, 0);

}

public MarkerView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

initView(context, attrs, defStyleAttr);

}

//以后外部布局传来的參数

public MarkerView(Context context, int mDefaultColor, int mDefailtFullColor, int mNodeDefaultColor,

int mSelectColor, int mFrameSelectFullColor, int mNodeSelectColor,

int mErrerColor, int mErrerFullColor, int mErrerNodeColor,

int mFrameLineWidth, int mNodeRadius, int mNodePadding, boolean insideNodeShow) {

super(context);

this.mInsideNodeShow = insideNodeShow;

this.mDefaultColor = mDefaultColor;

this.mDefailtFullColor = mDefailtFullColor;

this.mNodeDefaultColor = mNodeDefaultColor;

this.mSelectColor = mSelectColor;

this.mFrameSelectFullColor = mFrameSelectFullColor;

this.mNodeSelectColor = mNodeSelectColor;

this.mErrerColor = mErrerColor;

this.mErrerFullColor = mErrerFullColor;

this.mErrerNodeColor = mErrerNodeColor;

this.mFrameLineWidth = mFrameLineWidth;

this.mNodeRadius = mNodeRadius;

this.mNodePadding = mNodePadding;

//内边距

setPadding(mNodePadding, mNodePadding, mNodePadding, mNodePadding);

//外部圆

mNodeFramePaint = new Paint();

mNodeFramePaint.setColor(mDefaultColor);

mNodeFramePaint.setAntiAlias(true);

mNodeFramePaint.setStrokeWidth(mFrameLineWidth);

mNodeFramePaint.setStyle(Paint.Style.STROKE);//仅仅画出边框

//内部填充色

mNodeFullPaint = new Paint();

mNodeFullPaint.setColor(mDefailtFullColor);

mNodeFullPaint.setStyle(Paint.Style.FILL);

mNodeFullPaint.setAntiAlias(true);

//内部圆

mNodeCirclePaint = new Paint();

mNodeCirclePaint.setColor(mNodeDefaultColor);

mNodeCirclePaint.setStyle(Paint.Style.FILL);//填充

mNodeCirclePaint.setAntiAlias(true);

}

//取当前透明度的百分比

public int getFullAlpha(int color, float ratio) {

return Color.argb((int) (Color.alpha(color) * ratio), Color.red(color), Color.green(color), Color.blue(color));

}

@Override

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

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

mContentWidth = getWidth();

mContentRadius = mContentWidth / 2 - Math.abs(getPaddingLeft()) - mFrameLineWidth / 2;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

switch (mCurrentState) {

case DEFAULT_COLOR: //默认

mNodeFramePaint.setColor(mDefaultColor);

mNodeFullPaint.setColor(mDefailtFullColor);

mNodeCirclePaint.setColor(mNodeDefaultColor);

//外圆

canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius, mNodeFramePaint);

//填充色

canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius - mFrameLineWidth / 2, mNodeFullPaint);

//中心圆

if (mInsideNodeShow)

canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mNodeRadius, mNodeCirclePaint);

break;

case ERRER_STATE://错误

mNodeFramePaint.setColor(mErrerColor);

mNodeFullPaint.setColor(mErrerFullColor);

mNodeCirclePaint.setColor(mErrerNodeColor);

//外圆

canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius, mNodeFramePaint);

//填充色

canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius - mFrameLineWidth / 2, mNodeFullPaint);

//中心圆

canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mNodeRadius, mNodeCirclePaint);

break;

case SELECT_STATE://选中

mNodeFramePaint.setColor(mSelectColor);

mNodeFullPaint.setColor(mFrameSelectFullColor);

mNodeCirclePaint.setColor(mNodeSelectColor);

//外圆

canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius, mNodeFramePaint);

//填充色

canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mContentRadius - mFrameLineWidth / 2, mNodeFullPaint);

//中心圆

canvas.drawCircle(mContentWidth / 2, mContentWidth / 2, mNodeRadius, mNodeCirclePaint);

break;

}

}

//设置状态,而且重绘

public void setState(LockState CurrentState) {

mCurrentState = CurrentState;

invalidate();

}

//是否选中

public boolean isHighLighted() {

if (mCurrentState == LockState.SELECT_STATE || mCurrentState == LockState.ERRER_STATE) {

return true;

}

return false;

}

//中心点X

public int getCenterX() {

return (getLeft() + getRight()) / 2;

}

//中心点Y

public int getCenterY() {

return (getTop() + getBottom()) / 2;

}

//设置圈圈在手势锁其中的位置

protected void setNum(int num) {

mNum = num;

}

protected int getNum() {

return mNum;

}

}

以上就是一个简单的圆了

那么,自己定义View当然会有自己定义属性,所以有这么多T0T,不要问我为什么这么多属性。任性= =(事实上我还想写很多其它),自己定义属性的方法

LockView的代码

package com.juzisang.com.library;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.util.AttributeSet;

import android.view.MotionEvent;

import android.view.ViewGroup;

import java.util.ArrayList;

/**

* Created by 橘子桑 on 2016/3/27.

*/

public class LockView extends ViewGroup {

//画连接线的画笔

private Paint mLinePaint;

//能够触摸的区域百分比

private float mTouchRatio;

//线的颜色

protected int mLineColor;

//先的宽度

protected float mLineWidth;

//已经选中了的View

ArrayList mNodeViews = new ArrayList<>();

//存储password

protected StringBuilder pawBuilder = new StringBuilder();

//当前手指触摸的x坐标

protected float x;

//当前手指触摸的y坐标

protected float y;

//回调

private onLockCallback mOnLockCallback;

protected int mDefaultColor;

protected int mSelectColor;

protected int mErrerColor;

//禁用手势锁

private boolean mLockScreen;

private boolean isTouch;

//是否把连接线绘制在子View的上面

private boolean mLineTop = false;

//手指离开马上重绘

private boolean mFingerLeaveRedraw = true;

public LockView(Context context) {

this(context, null);

}

public LockView(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public LockView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

initView(context, attrs, defStyleAttr);

}

protected void initView(Context context, AttributeSet attrs, int defStyleAttr) {

TypedArray r = context.obtainStyledAttributes(attrs, R.styleable.MarkerView);

boolean insideNodeShow = r.getBoolean(R.styleable.LockView_insideNodeShow, false);

//默认的颜色

mDefaultColor = r.getColor(R.styleable.LockView_defaultColor, context.getResources().getColor(android.R.color.holo_blue_dark));

int mDefailtFullColor = r.getColor(R.styleable.LockView_defaultFullColor, getFullAlpha(mDefaultColor, 0.3F));

int mNodeDefaultColor = (int) r.getColor(R.styleable.LockView_defaultNodeColor, mDefaultColor);

//选中的颜色

mSelectColor = (int) r.getColor(R.styleable.LockView_selectColor, context.getResources().getColor(android.R.color.holo_blue_light));

int mFrameSelectFullColor = r.getColor(R.styleable.LockView_selectFrameFullColor, getFullAlpha(mSelectColor, 0.3F));

int mNodeSelectColor = r.getColor(R.styleable.LockView_selectNodeColor, mSelectColor);

//错误时候的颜色

mErrerColor = r.getColor(R.styleable.LockView_errorColor, context.getResources().getColor(android.R.color.holo_red_light));

int mErrerFullColor = r.getColor(R.styleable.LockView_errorFullColor, getFullAlpha(mErrerColor, 0.3F));

int mErrerNodeColor = r.getColor(R.styleable.LockView_errorNodeColor, mErrerColor);

//圆框变的宽度

int mFrameLineWidth = (int) r.getDimension(R.styleable.LockView_frameLineWidth, DensityUtils.dip2px(context, 5));

//内圆的直径

int mNodeRadius = (int) r.getDimension(R.styleable.LockView_nodeRadius, DensityUtils.dip2px(context, 5));

//内边距

int mNodePadding = (int) r.getDimension(R.styleable.LockView_nodePadding, DensityUtils.dip2px(context, 10));

//触摸有效区域

mTouchRatio = r.getFloat(R.styleable.LockView_touchRatio, mTouchRatio);

mLineColor = r.getColor(R.styleable.LockView_lineColor, mDefaultColor);

mLineWidth = r.getDimension(R.styleable.LockView_lineWidth, DensityUtils.dip2px(context, 5));

r.recycle();

//设置线的颜色

mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

mLinePaint.setColor(mLineColor);

mLinePaint.setStyle(Paint.Style.STROKE);

mLinePaint.setStrokeWidth(mLineWidth);

mLinePaint.setStrokeCap(Paint.Cap.ROUND);

mLinePaint.setStrokeJoin(Paint.Join.ROUND);

for (int i = 0; i < 9; i++) {

MarkerView view = new MarkerView(context, mDefaultColor, mDefailtFullColor, mNodeDefaultColor, mSelectColor, mFrameSelectFullColor, mNodeSelectColor,

mErrerColor, mErrerFullColor, mErrerNodeColor, mFrameLineWidth, mNodeRadius, mNodePadding, insideNodeShow);

view.setNum(i + 1);

ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);

view.setLayoutParams(params);

addView(view);

}

// 清除FLAG,否则 onDraw() 不会调用。原因是 ViewGroup 默认透明背景不须要调用 onDraw()

setWillNotDraw(false);

}

public int getFullAlpha(int color, float ratio) {

return Color.argb((int) (Color.alpha(color) * ratio), Color.red(color), Color.green(color), Color.blue(color));

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int size = Math.min(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)); // 測量宽度

setMeasuredDimension(size, size);

for (int i = 0; i < getChildCount(); i++) {

measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);

}

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

if (changed) {

float areaWidth = (r - l - getPaddingLeft() * 2) / 3;

for (int n = 0; n < 9; n++) {

MarkerView node = (MarkerView) getChildAt(n);

// 获取3*3宫格内坐标

int row = n / 3;

int col = n % 3;

//加上内间距

int left = (int) (getPaddingLeft() + col * areaWidth);

int top = (int) (getPaddingTop() + row * areaWidth);

int right = (int) (left + areaWidth);

int bottom = (int) (top + areaWidth);

node.layout(left, top, right, bottom);

}

}

}

/**

* 设置连接线是否绘制在子View的上面

* true 绘制在子View的上面

* false 绘制在子View的以下

*

*@param isLineTop 设置连接线是否绘制在子View的上面

*/

public void setLineTop(boolean isLineTop) {

mLineTop = isLineTop;

invalidate();

}

/**

* 设置连接线是否绘制在子View的上面

* true 绘制在子View的上面

* false 绘制在子View的以下

*/

public boolean getLineTop() {

return mLineTop;

}

@Override

public boolean onTouchEvent(MotionEvent event) {

if (getLockScreen()) {

invalidate();

return false;

}

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

//恢复默认

resetDefault();

x = event.getX();

y = event.getY();

isTouch = true;

break;

case MotionEvent.ACTION_MOVE:

x = event.getX(); // 这里要实时记录手指的坐标

y = event.getY();

MarkerView nodeView = getNodeAt(x, y);

//没有选中

if (nodeView != null && !nodeView.isHighLighted()) {

nodeView.setState(LockState.SELECT_STATE);

mNodeViews.add(nodeView);

//进度

if (mOnLockCallback != null) {

pawBuilder.setLength(0);

for (MarkerView markerView : mNodeViews) {

pawBuilder.append(markerView.getNum());

}

mOnLockCallback.onProgress(pawBuilder.toString(), nodeView.getNum());

}

}

if (mNodeViews.size() > 0) {

invalidate();

}

break;

case MotionEvent.ACTION_UP:

LogUtils.i("手指抬起了");

isTouch = false;

pawBuilder.setLength(0);

if (mNodeViews.size() <= 0) return true;

pawBuilder.delete(0, pawBuilder.length());

if (mOnLockCallback != null) {

for (MarkerView markerView : mNodeViews) {

pawBuilder.append(markerView.getNum());

}

mOnLockCallback.onFinish(pawBuilder.toString());

}

if (mFingerLeaveRedraw) {

resetDefault();

} else {

invalidate();

}

break;

}

return true;

}

@Override

protected void onDraw(Canvas canvas) {

//线画在子view的以下

if (!mLineTop) onDrawLock(canvas);

}

//画子View的地方

protected void dispatchDraw(Canvas canvas) {

super.dispatchDraw(canvas);

//放在这里的原因是。线会被子View挡到

if (mLineTop) onDrawLock(canvas);

}

/**

* 绘图的方法

*/

private void onDrawLock(Canvas canvas) {

//屏幕锁住了,仅仅画起点到终点的

if (getLockScreen()) {

onDrawNodeViewLock(canvas);

return;

}

if (isTouch || mFingerLeaveRedraw) {

//从第一个和最后一个的连接线

onDrawNodeViewLock(canvas);

//最后一个点,到手指之间的线

if (mNodeViews.size() > 0) {

MarkerView lastNode = mNodeViews.get(mNodeViews.size() - 1);

canvas.drawLine(lastNode.getCenterX(), lastNode.getCenterY(), x, y, mLinePaint);

}

} else {

//假设手指离开屏幕。而且设置了手指离开马上重绘

onDrawNodeViewLock(canvas);

}

}

private void onDrawNodeViewLock(Canvas canvas) {

//从第一个和最后一个的连接线

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

MarkerView frontNode = mNodeViews.get(i - 1);

MarkerView backNode = mNodeViews.get(i);

canvas.drawLine(frontNode.getCenterX(), frontNode.getCenterY(), backNode.getCenterX(), backNode.getCenterY(), mLinePaint);

}

}

/**

* 获取给定坐标点的Node,返回null表示当前手指在两个Node之间

*/

private MarkerView getNodeAt(float x, float y) {

for (int n = 0; n < getChildCount(); n++) {

MarkerView node = (MarkerView) getChildAt(n);

//计算触摸区域以外的距离

float ratioPadding = (node.getWidth() - (node.getWidth() * mTouchRatio)) / 2;

if (!(x >= node.getLeft() + ratioPadding && x < node.getRight() - ratioPadding)) {

continue;

}

if (!(y >= node.getTop() + ratioPadding && y < node.getBottom() - ratioPadding)) {

continue;

}

return node;

}

return null;

}

/**

* 设置连接线的颜色

*

*@param color 颜色值

*/

public void setLineColor(int color) {

mLinePaint.setColor(color);

}

/**

* 手指离开马上重绘

*/

public void setfingerLeaveRedraw(boolean mFingerLeaveRedraw) {

this.mFingerLeaveRedraw = mFingerLeaveRedraw;

}

public boolean getfingerLeaveRedraw() {

return this.mFingerLeaveRedraw;

}

/**

* 重置状态 为默认状态

*/

public void resetDefault() {

setState(LockState.DEFAULT_COLOR);

mNodeViews.clear();

}

/**

* 重置状态错误状态

*/

public void resetErrer() {

setState(LockState.ERRER_STATE);

}

/**

* 重置为选中状态

*/

public void resetSelect() {

setState(LockState.SELECT_STATE);

}

/**

* 锁屏,不同意触摸

*/

public void LockScreen(boolean isScreen) {

mLockScreen = isScreen;

}

public boolean getLockScreen() {

return mLockScreen;

}

public void setState(LockState state) {

switch (state) {

case DEFAULT_COLOR:

case SELECT_STATE:

setLineColor(mSelectColor);

break;

case ERRER_STATE:

setLineColor(mErrerColor);

break;

}

int size = mNodeViews.size();

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

mNodeViews.get(i).setState(state);

}

invalidate();

}

public void setLockCallback(onLockCallback lockCallback) {

mOnLockCallback = lockCallback;

}

//回调

public interface onLockCallback {

void onProgress(String paw, int current);

void onFinish(String paw);

}

}

以上凝视都写的非常清晰了。以下讲一下遇到的一些问题。

1.画出来的线被上面的圈圈覆盖了。

99c6bcc56d3f8e545ce03a40e4d419d7.png

通过百度。知道ViewGroup的onDraw是画布局中的内容的,画子View的的方法在这种方法的后面运行。所以ViewGroup的内容会被子View覆盖。那么怎么才干把连接线画在子View的上面呢,非常easy

仅仅要在画子View的方法中运行就好了

//画子View的地方

protected void dispatchDraw(Canvas canvas) {

super.dispatchDraw(canvas);

//放在这里的原因是。线会被子View挡到

if (mLineTop) onDrawLock(canvas);

}

以下是View的draw()方法

@CallSuper

public void draw(Canvas canvas) {

final int privateFlags = mPrivateFlags;

final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&

(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

// Step 1, draw the background, if needed

int saveCount;

if (!dirtyOpaque) {

drawBackground(canvas);

}

// skip step 2 & 5 if possible (common case)

final int viewFlags = mViewFlags;

boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;

boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;

if (!verticalEdges && !horizontalEdges) {

// Step 3, draw the content

if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children

//这里就是画子View的方法了

dispatchDraw(canvas);

// Overlay is part of the content and draws beneath Foreground

if (mOverlay != null && !mOverlay.isEmpty()) {

mOverlay.getOverlayView().dispatchDraw(canvas);

}

// Step 6, draw decorations (foreground, scrollbars)

onDrawForeground(canvas);

// we're done...

return;

}

2.怎么设置触摸的区域?

/**

* 获取给定坐标点的Node,返回null表示当前手指在两个Node之间

*/

private MarkerView getNodeAt(float x, float y) {

for (int n = 0; n < getChildCount(); n++) {

MarkerView node = (MarkerView) getChildAt(n);

//计算触摸区域以外的距离

float ratioPadding = (node.getWidth() - (node.getWidth() * mTouchRatio)) / 2;

if (!(x >= node.getLeft() + ratioPadding && x < node.getRight() - ratioPadding)) {

continue;

}

if (!(y >= node.getTop() + ratioPadding && y < node.getBottom() - ratioPadding)) {

continue;

}

return node;

}

return null;

}

看上面代码。

依据圆圈的宽度减去可触摸区域的长度除2。得到可触摸区域距离边框的距的距离。

光看代码看着有点圆。画个图看一下吧

5f338e6955bef690dff9b92962a3d100.png

画个图是不是清晰非常多,仅仅要用getLeft+边距。和getRight-边距,就能得到可触摸区域在x轴上的范围了。Y轴同理。不懂的同学自己用笔画一下吧~

几乎相同就上面两个问题了

630e5021d9c5e0c0d3a61a20e1cf4a8a.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值