代码在公司项目中使用没有问题
绘制时
绘制后
选中和未选中效果是两张图片,可以自行设置两张图片代替
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import com.gq.android.R;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* Android-Lock9View
* </p>
* <p>
* 九宫格锁屏视图组件,提供一个九宫格手势屏锁视图功能。
* </p>
* <p>
* 手势处理和连线绘制都有组件内部完成,你只需要绑定这个组件并设置手势完成后的回调对象即可。
* </p>
* <p>
* 请使用 setCallBack(CallBack callBack) 函数设置回调对象。
* </p>
* <p>
* 需要注意的是,不论如何设置,组件的高度永远等于宽度。
* </p>
*
* @author TakWolf (<a
* href="mailto:takwolf@foxmail.com">takwolf@foxmail.com</a>)
*/
public class Lock9View extends ViewGroup {
private Paint paint;
private Bitmap bitmap;
private Canvas canvas;
private List<Pair<NodeView, NodeView>> lineList;
private NodeView currentNode;
private StringBuilder pwdSb;
private CallBack callBack;
private Drawable nodeSrc;
private Drawable nodeOnSrc;
public Lock9View(Context context) {
this(context, null);
}
public Lock9View(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Lock9View(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
private Lock9View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr); // TODO api 21
initFromAttributes(attrs, defStyleAttr);
}
private void initFromAttributes(AttributeSet attrs, int defStyleAttr) {
final TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.Lock9View, defStyleAttr, 0);
nodeSrc = a.getDrawable(R.styleable.Lock9View_nodeSrc);
nodeOnSrc = a.getDrawable(R.styleable.Lock9View_nodeOnSrc);
int lineColor = Color.argb(0, 0, 0, 0);
lineColor = a.getColor(R.styleable.Lock9View_lineColor, lineColor);
float lineWidth = 20.0f;
lineWidth = a.getDimension(R.styleable.Lock9View_lineWidth, lineWidth);
a.recycle();
paint = new Paint(Paint.DITHER_FLAG);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(lineColor);
paint.setAntiAlias(true);
DisplayMetrics dm = getResources().getDisplayMetrics(); // bitmap的宽度是屏幕宽度,足够使用
bitmap = Bitmap.createBitmap(dm.widthPixels, dm.widthPixels, Bitmap.Config.ARGB_8888);
canvas = new Canvas();
canvas.setBitmap(bitmap);
for (int n = 0; n < 9; n++) {
NodeView node = new NodeView(getContext(), n + 1);
addView(node);
}
lineList = new ArrayList<>();
pwdSb = new StringBuilder();
// 清除FLAG,否则 onDraw() 不会调用,原因是 ViewGroup 默认透明背景不需要调用 onDraw()
setWillNotDraw(false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(widthMeasureSpec, widthMeasureSpec); // 我们让高度等于宽度
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (!changed) {
return;
}
int width = right - left;
int nodeWidth = width / 3;
int nodePadding = nodeWidth / 6;
for (int n = 0; n < 9; n++) {
NodeView node = (NodeView) getChildAt(n);
int row = n / 3;
int col = n % 3;
int l = col * nodeWidth + nodePadding;
int t = row * nodeWidth + nodePadding;
int r = col * nodeWidth + nodeWidth - nodePadding;
int b = row * nodeWidth + nodeWidth - nodePadding;
node.layout(l, t, r, b);
}
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, 0, 0, null);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
NodeView nodeAt = getNodeAt(event.getX(), event.getY());
if (nodeAt == null && currentNode == null) { // 不需要画线,之前没接触点,当前也没接触点
return true;
} else { // 需要画线
clearScreenAndDrawList(); // 清除所有图像,如果已有线,则重新绘制
if (currentNode == null) { // 第一个点 nodeAt不为null
currentNode = nodeAt;
if (null!=currentNode){
currentNode.setHighLighted(true);
pwdSb.append(currentNode.getNum());
}
} else if (nodeAt == null || nodeAt.isHighLighted()) { // 已经有点了,当前并未碰触新点
// 以currentNode中心和当前触摸点开始画线
canvas.drawLine(currentNode.getCenterX(), currentNode.getCenterY(), event.getX(), event.getY(), paint);
} else { // 移动到新点
canvas.drawLine(currentNode.getCenterX(), currentNode.getCenterY(), nodeAt.getCenterX(), nodeAt.getCenterY(), paint);// 画线
nodeAt.setHighLighted(true);
Pair<NodeView, NodeView> pair = new Pair<NodeView, NodeView>(currentNode, nodeAt);
lineList.add(pair);
// 赋值当前的node
currentNode = nodeAt;
pwdSb.append(currentNode.getNum());
}
// 通知onDraw重绘
invalidate();
}
return true;
case MotionEvent.ACTION_UP:
// 还没有触摸到点
if (pwdSb.length() <= 0) {
return super.onTouchEvent(event);
}
// 回调结果
if (callBack != null) {
callBack.onFinish(pwdSb.toString());
pwdSb.setLength(0); // 清空
}
// 清空保存点的集合
currentNode = null;
lineList.clear();
clearScreenAndDrawList();
// 清除高亮
for (int n = 0; n < getChildCount(); n++) {
NodeView node = (NodeView) getChildAt(n);
node.setHighLighted(false);
}
// 通知onDraw重绘
invalidate();
return true;
}
return super.onTouchEvent(event);
}
/**
* 清掉屏幕上所有的线,然后画出集合里面的线
*/
private void clearScreenAndDrawList() {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (Pair<NodeView, NodeView> pair : lineList) {
canvas.drawLine(pair.first.getCenterX(), pair.first.getCenterY(), pair.second.getCenterX(), pair.second.getCenterY(), paint);
}
}
/**
* 获取Node,返回null表示当前手指在两个Node之间
*/
private NodeView getNodeAt(float x, float y) {
for (int n = 0; n < getChildCount(); n++) {
NodeView node = (NodeView) getChildAt(n);
if (!(x >= node.getLeft() && x < node.getRight())) {
continue;
}
if (!(y >= node.getTop() && y < node.getBottom())) {
continue;
}
return node;
}
return null;
}
/**
* 设置手势结果的回调监听器
*
* @param callBack
*/
public void setCallBack(CallBack callBack) {
this.callBack = callBack;
}
/**
* 节点描述类
*/
private class NodeView extends View {
private int num;
private boolean highLighted;
private NodeView(Context context) {
super(context);
}
@SuppressWarnings("deprecation")
private NodeView(Context context, int num) {
this(context);
this.num = num;
highLighted = false;
if (nodeSrc == null) {
setBackgroundResource(0);
} else {
setBackgroundDrawable(nodeSrc);
}
}
public boolean isHighLighted() {
return highLighted;
}
@SuppressWarnings("deprecation")
public void setHighLighted(boolean highLighted) {
this.highLighted = highLighted;
if (highLighted) {
if (nodeOnSrc == null) {
setBackgroundResource(0);
} else {
setBackgroundDrawable(nodeOnSrc);
}
} else {
if (nodeSrc == null) {
setBackgroundResource(0);
} else {
setBackgroundDrawable(nodeSrc);
}
}
}
public int getCenterX() {
return (getLeft() + getRight()) / 2;
}
public int getCenterY() {
return (getTop() + getBottom()) / 2;
}
public int getNum() {
return num;
}
}
/**
* 结果回调监听器接口
*/
public interface CallBack {
void onFinish(String password);
}
}
LockPatternIndicator 锁屏指示器
就是上面那九个点
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.view.View;
import com.gq.android.R;
import java.util.List;
/**
* indicator(three rows and three columns)
* @author Sym
*/
public class LockPatternIndicator extends View {
private int width, height;
private int cellBoxWidth, cellBoxHeight;
private int radius;
private int offset = 2;
private Paint defaultPaint, selectPaint;
private IndicatorCell[][] mIndicatorCells = new IndicatorCell[3][3];
private static final String TAG = "LockPatternIndicator";
public LockPatternIndicator(Context context) {
this(context, null);
}
public LockPatternIndicator(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LockPatternIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.init();
}
private void init(){
initRadius();
initPaint();
init9IndicatorCells();
}
private void initRadius() {
this.radius = (this.width - offset*2)/4/2;
this.cellBoxHeight = (this.height - offset*2)/3;
this.cellBoxWidth = (this.width - offset*2)/3;
}
private void initPaint() {
defaultPaint = new Paint();
defaultPaint.setColor(getResources().getColor(R.color.c999999));
defaultPaint.setStrokeWidth(3.0f);
defaultPaint.setStyle(Style.STROKE);
defaultPaint.setAntiAlias(true);
selectPaint = new Paint();
selectPaint.setColor(getResources().getColor(R.color.cff3e19));
selectPaint.setStrokeWidth(3.0f);
selectPaint.setStyle(Style.FILL);
selectPaint.setAntiAlias(true);
}
/**
* initialize nine cells
*/
private void init9IndicatorCells(){
int distance = this.cellBoxWidth + this.cellBoxWidth/2 - this.radius;
for(int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
mIndicatorCells[i][j] = new IndicatorCell(distance*j + radius + offset, distance*i + radius + offset, 3*i + j + 1);
}
}
}
/**
* set nine indicator cells size
*/
private void set9IndicatorCellsSize() {
int distance = this.cellBoxWidth + this.cellBoxWidth/2 - this.radius;
for(int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
mIndicatorCells[i][j].setX(distance*j + radius + offset);
mIndicatorCells[i][j].setY(distance*i + radius + offset);
}
}
}
/**
* set indicator
* @param cells
*/
public void setIndicator(List<Integer> cells) {
for( int index=0;index<cells.size();index++) {
for(int i = 0; i < mIndicatorCells.length; i++) {
for(int j = 0; j < mIndicatorCells[i].length; j++) {
if (cells.get(index) == mIndicatorCells[i][j].getIndex()) {
mIndicatorCells[i][j].setStatus(IndicatorCell.STATE_CHECK);
}
}
}
}
this.postInvalidate();
}
/**
* set default indicator
*/
public void setDefaultIndicator() {
for(int i = 0; i < mIndicatorCells.length; i++) {
for(int j = 0; j < mIndicatorCells[i].length; j++) {
mIndicatorCells[i][j].setStatus(IndicatorCell.STATE_NORMAL);
}
}
this.postInvalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawToCanvas(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
this.width = getMeasuredWidth();
this.height = getMeasuredHeight();
this.initRadius();
this.set9IndicatorCellsSize();
this.invalidate();
}
/**
* draw the view to canvas
* @param canvas
*/
private void drawToCanvas(Canvas canvas) {
for(int i = 0; i < mIndicatorCells.length; i++) {
for(int j = 0; j < mIndicatorCells[i].length; j++) {
if(mIndicatorCells[i][j].getStatus() == IndicatorCell.STATE_NORMAL) {
canvas.drawCircle(mIndicatorCells[i][j].getX(), mIndicatorCells[i][j].getY(), radius, defaultPaint);
} else if(mIndicatorCells[i][j].getStatus() == IndicatorCell.STATE_CHECK) {
canvas.drawCircle(mIndicatorCells[i][j].getX(), mIndicatorCells[i][j].getY(), radius, selectPaint);
}
}
}
}
public class IndicatorCell {
private int x;// the center x of circle
private int y;// the center y of circle
private int status = 0;//default
private int index;// the cell value
public static final int STATE_NORMAL = 0;
public static final int STATE_CHECK = 1;
public IndicatorCell(){}
public IndicatorCell(int x, int y, int index){
this.x = x;
this.y = y;
this.index = index;
}
public int getX(){
return this.x;
}
public void setX(int x) {
this.x = x;
}
public int getY(){
return this.y;
}
public void setY(int y) {
this.y = y;
}
public int getStatus(){
return this.status;
}
public void setStatus(int status){
this.status = status;
}
public int getIndex() {
return this.index;
}
public void setIndex(int index) {
this.index = index;
}
}
}
布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<com.gq.android.views.Lock9View
android:id="@+id/lock_9_view"
android:layout_width="@dimen/px_524"
android:layout_height="@dimen/px_524"
android:layout_centerInParent="true"
app:lineColor="@color/cff3e19"
app:lineWidth="@dimen/px_4"
app:nodeOnSrc="@drawable/huizhi"
app:nodeSrc="@drawable/chushi"/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="35dp"
android:text="请输入原手势密码"
android:textColor="@color/c333333"
android:textSize="@dimen/text_size_46_px" />
<com.gq.android.views.LockPatternIndicator
android:id="@+id/lpi_indicator"
android:visibility="gone"
android:layout_below="@+id/title"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/px_20"
android:layout_width="@dimen/px_90"
android:layout_height="@dimen/px_90" />
<ImageView
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="@dimen/px_46"
android:layout_marginTop="35dp"
android:paddingLeft="@dimen/px_15"
android:paddingRight="@dimen/px_15"
android:layout_marginLeft="@dimen/px_15"
android:src="@drawable/fanhui_hui"
/>
<TextView
android:id="@+id/tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/lpi_indicator"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text=""
android:textColor="@color/cff3e19"
android:textSize="16sp" />
<TextView
android:id="@+id/by_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/px_10"
android:text="通过登录密码修改"
android:textColor="@color/c54adff"
android:textSize="16sp"
android:layout_alignBaseline="@+id/forget_pwd"
android:layout_alignBottom="@+id/forget_pwd"
android:layout_alignLeft="@+id/lock_9_view"
android:layout_alignStart="@+id/lock_9_view" />
<TextView
android:id="@+id/forget_pwd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="36dp"
android:padding="@dimen/px_10"
android:text="忘记登录密码"
android:textColor="@color/c54adff"
android:textSize="16sp"
android:layout_alignParentBottom="true"
android:layout_alignRight="@+id/lock_9_view"
android:layout_alignEnd="@+id/lock_9_view" />
</RelativeLayout>
java使用
lock9View.setCallBack(new CallBack() {
@Override
public void onFinish(String password) {
//绘制完成后指示器显示
//重置
lpi_indicator.setDefaultIndicator();
//画轨迹
if(!TextUtils.isEmpty(password)){
List<Integer> ints=new ArrayList<Integer>();
for(int i=0;i<password.length();i++){
ints.add(Integer.valueOf(password.substring(i,i+1)));
}
lpi_indicator.setIndicator(ints);
}
}
});