Android自定义View-手势密码

代码在公司项目中使用没有问题

绘制时

这里写图片描述

绘制后

这里写图片描述

选中和未选中效果是两张图片,可以自行设置两张图片代替


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);
                }
            }
        });
        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值