* Copyright 2015-2016 TakWolf
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.takwolf.android.lock9;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
public class Lock9View extends ViewGroup {
* 节点相关定义
*/
private List<Pair<NodeView, NodeView>> lineList = new ArrayList<Pair<NodeView,NodeView>>();
private NodeView currentNode;
private float x;
private float y;
* 自定义属性列表
*/
private Drawable nodeSrc;
private Drawable nodeOnSrc;
private int lineColor;
private float lineWidth;
private float padding;
private float spacing;
* 画线用的画笔
*/
private Paint paint;
* 密码构建器
*/
private StringBuilder passwordBuilder = new StringBuilder();
* 结果回调监听器接口
*/
private CallBack callBack;
public interface CallBack {
public void onFinish(String password);
}
public void setCallBack(CallBack callBack) {
this.callBack = callBack;
}
* 构造函数
*/
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);
}
public Lock9View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr);
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_lock9_nodeSrc);
nodeOnSrc = a.getDrawable(R.styleable.Lock9View_lock9_nodeOnSrc);
lineColor = a.getColor(R.styleable.Lock9View_lock9_lineColor, Color.argb(0, 0, 0, 0));
lineWidth = a.getDimension(R.styleable.Lock9View_lock9_lineWidth, 0);
padding = a.getDimension(R.styleable.Lock9View_lock9_padding, 0);
spacing = a.getDimension(R.styleable.Lock9View_lock9_spacing, 0);
a.recycle();
paint = new Paint(Paint.DITHER_FLAG);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(lineColor);
paint.setAntiAlias(true);
for (int n = 0; n < 9; n++) {
NodeView node = new NodeView(getContext(), n + 1);
addView(node);
}
setWillNotDraw(false);
}
* TODO 我们让高度等于宽度 - 这么使用不清楚是否正确
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(widthMeasureSpec, widthMeasureSpec);
}
* 在这里进行node的布局
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed) {
int nodeWidth = (int) ((right - left - padding * 2 - spacing * 2) / 3);
for (int n = 0; n < 9; n++) {
NodeView node = (NodeView) getChildAt(n);
int row = n / 3;
int col = n % 3;
int l = (int) (padding + col * (nodeWidth + spacing));
int t = (int) (padding + row * (nodeWidth + spacing));
int r = l + nodeWidth;
int b = t + nodeWidth;
node.layout(l, t, r, b);
}
}
}
* 在这里处理手势
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
x = event.getX();
y = event.getY();
NodeView nodeAt = getNodeAt(x, y);
if (currentNode == null) {
if (nodeAt != null) {
currentNode = nodeAt;
currentNode.setHighLighted(true);
passwordBuilder.append(currentNode.getNum());
invalidate();
}
} else {
if (nodeAt != null && !nodeAt.isHighLighted()) {
nodeAt.setHighLighted(true);
Pair<NodeView, NodeView> pair = new Pair<NodeView, NodeView>(currentNode, nodeAt);
lineList.add(pair);
currentNode = nodeAt;
passwordBuilder.append(currentNode.getNum());
}
invalidate();
}
break;
case MotionEvent.ACTION_UP:
if (passwordBuilder.length() > 0) {
if (callBack != null) {
callBack.onFinish(passwordBuilder.toString());
}
lineList.clear();
currentNode = null;
passwordBuilder.setLength(0);
for (int n = 0; n < getChildCount(); n++) {
NodeView node = (NodeView) getChildAt(n);
node.setHighLighted(false);
}
invalidate();
}
break;
}
return true;
}
* 系统绘制回调-主要绘制连线
*/
@Override
protected void onDraw(Canvas canvas) {
for (Pair<NodeView, NodeView> pair : lineList) {
canvas.drawLine(pair.first.getCenterX(), pair.first.getCenterY(), pair.second.getCenterX(), pair.second.getCenterY(), paint);
}
if (currentNode != null) {
canvas.drawLine(currentNode.getCenterX(), currentNode.getCenterY(), x, y, 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;
}
* 结点描述类
*/
private class NodeView extends View {
private int num;
private boolean highLighted = false;
public NodeView(Context context, int num) {
super(context);
this.num = num;
setBackgroundDrawable(nodeSrc);
}
public boolean isHighLighted() {
return highLighted;
}
public void setHighLighted(boolean highLighted) {
if (this.highLighted != highLighted) {
this.highLighted = highLighted;
setBackgroundDrawable(highLighted ? nodeOnSrc : nodeSrc);
}
}
public int getCenterX() {
return (getLeft() + getRight()) / 2;
}
public int getCenterY() {
return (getTop() + getBottom()) / 2;
}
public int getNum() {
return num;
}
}
}