关于九宫格解锁,我看了不少博客,但是都感觉很复杂,可能我的功夫还不到,所以很多东西我不了解,但是我还是打算写一个自己的九宫格。我相信我的九宫格大家都能很快的理解,当然如果需要实现更复杂的功能,需要大家自己接着往深了挖掘。
代码文件
NineGroupView:为九宫格空间组
ToggleView:九宫格中的子View,也就是我们看到的圆形按钮,我自己定义的ToggleView可能不好看,当然大家可以自己定义更加好看的ToggleView。
MarkBean:记录ToggleView的索引(ChildIndex)以及是否选中的状态
PositionUtils:工具类,包含规划九个ToggleView的中心点位置,判断当前触摸点是否属于ToggleView中等方法。
NineActivity:测试页面。
布局规划图
public class PositionUtils {
/**
* 判断触摸的点是否属于View中的一点
*
* @param point 触摸的点
* @param position 目标对象圆形坐标
* @param outR 目标对象的外半径
* @return
*/
public static boolean IsIn(Point point, Point position, int outR) {
int touchX = point.x;
int touchY = point.y;
int cx = position.x;
int cy = position.y;
int distance = (int) Math.sqrt(Math.pow((touchX - cx), 2) + Math.pow((touchY - cy), 2));
if (distance <= outR) {
return true;
} else {
return false;
}
}
/**
* 规划 child 的中心位置
*
* @param width
* @param height
* @return
*/
public static List<Point> getNinePoints(int width, int height) {
List<Point> points = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
points.add(getPoint(width, height, 0.25f * j, 0.2f * i + 0.1f));
}
}
return points;
}
/**
* 获取
*
* @param width 父控件的宽
* @param height 父控件的高
* @param x 横轴方向比例
* @param y 纵轴方向的比例
* @return
*/
private static Point getPoint(int width, int height, float x, float y) {
Point point = new Point();
point.x = (int) (width * x);
point.y = (int) (height * y);
return point;
}
}
public class ToggleView extends View {
private Paint inPaint;
private Paint outPaint;
private int outColor;
private int inColor;
private int outR;
private int inR;
private boolean isChecked;
public int getOutColor() {
return outColor;
}
public void setOutColor(int outColor) {
this.outColor = outColor;
}
public int getInColor() {
return inColor;
}
public void setInColor(int inColor) {
this.inColor = inColor;
}
public int getOutR() {
return outR;
}
public void setOutR(int outR) {
this.outR = outR;
}
public int getInR() {
return inR;
}
public void setInR(int inR) {
this.inR = inR;
}
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean checked) {
isChecked = checked;
}
public ToggleView(Context context) {
this(context, null);
}
public ToggleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ToggleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
/**
* 初始化
*/
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ToggleView, defStyleAttr, 0);
int indexCount = array.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.ToggleView_InCircleR_T:
inR = array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(Dimension.DP, 10, getResources().getDisplayMetrics()));
break;
case R.styleable.ToggleView_OutCircleR_T:
outR = array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(Dimension.DP, 50, getResources().getDisplayMetrics()));
break;
case R.styleable.ToggleView_InCircleColor_T:
inColor = array.getColor(attr, 0xff00ffff);
break;
case R.styleable.ToggleView_OutCircleColor_T:
outColor = array.getColor(attr, 0xff888888);
break;
}
}
inPaint = new Paint();
inPaint.setStyle(Paint.Style.FILL_AND_STROKE);
inPaint.setColor(inColor);
inPaint.setAntiAlias(true);
outPaint = new Paint();
outPaint.setAntiAlias(true);
outPaint.setStrokeWidth(5);
outPaint.setStyle(Paint.Style.STROKE);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int cx = getWidth() / 2;
int cy = getHeight() / 2;
outPaint.setStyle(Paint.Style.FILL);
outPaint.setColor(Color.WHITE);
canvas.drawCircle(cx, cy, outR, outPaint);
outPaint.setStyle(Paint.Style.STROKE);
outPaint.setColor(outColor);
canvas.drawCircle(cx, cy, outR, outPaint);
canvas.drawCircle(cx, cy, inR, inPaint);
}
}
public class NineGroupView extends ViewGroup {
private OnFinishListener mListener;
public interface OnFinishListener {
public void onFinish(List<Integer> positionSet);
}
public void setOnFinishListener(OnFinishListener listener) {
this.mListener = listener;
}
private Paint paint;
private Path path;
private TreeMap<Integer, Boolean> checkedMap;
private List<Integer> checkedINdexSet; //用于记录被选中的序号排列。
private List<Point> positionList;
private List<Point> childSize = new ArrayList<>();
public NineGroupView(Context context) {
this(context, null);
}
public NineGroupView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NineGroupView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void reset() {
for (int i = 0; i < 9; i++) {
checkedMap.put(i, false);
}
checkedINdexSet.clear();
IsDownIn = false;
IsUp = false;
path.reset();
prePoint = new Point(-1, -1);
currentPoint = new Point(-1, -1);
invalidate();
}
private void init() {
checkedMap = new TreeMap<>();
for (int i = 0; i < 9; i++) {
checkedMap.put(i, false);
}
checkedINdexSet = new ArrayList<>();
positionList = new ArrayList<>();
path = new Path();
paint = new Paint();
paint.setStrokeWidth(10);
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
//如果该方法在此不调用的话,那么onDraw()方法将不被调用,那么就无法完成连接线的绘制
setWillNotDraw(false);
}
@Override
protected void onLayout(boolean b, int left, int top, int right, int bottom) {
int height = getMeasuredHeight();
int width = getMeasuredWidth();
positionList = PositionUtils.getNinePoints(width, height);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
Point size = childSize.get(i);
Point position = positionList.get(i);
int cLeft = position.x - size.x;
int cTop = position.y - size.y;
int cRight = position.x + size.x;
int cBottom = position.y + size.y;
child.layout(cLeft, cTop, cRight, cBottom);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
Point point = new Point();
point.x = child.getMeasuredWidth();
point.y = child.getMeasuredHeight();
childSize.add(point);
}
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
private boolean IsDownIn = false;
private boolean IsUp = false;
private Point prePoint = new Point(-1, -1);
private Point currentPoint = new Point(-1, -1);
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
int currentX = (int) event.getX();
int currentY = (int) event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN: {
if (IsUp) {
return true;
}
MarkBean bean = isInToggle(new Point(currentX, currentY));
if (bean != null) {
IsDownIn = true;
prePoint = positionList.get(bean.getIndex());
path.moveTo(prePoint.x, prePoint.y);
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
IsUp = true;
if (IsDownIn) {
currentPoint = prePoint;
IsDownIn = false;
invalidate();
if (mListener != null) {
mListener.onFinish(checkedINdexSet);
reset();
}
}
break;
case MotionEvent.ACTION_MOVE: {
if (IsDownIn) {
if (!IsUp) {
MarkBean bean = isInToggle(new Point(currentX, currentY));
if (bean != null) {
int index = bean.getIndex();
currentPoint = positionList.get(index);
path.lineTo(currentPoint.x, currentPoint.y);
invalidate();
prePoint = currentPoint;
} else {
currentPoint = new Point(currentX, currentY);
invalidate();
}
}
} else {
if (!IsUp) {
MarkBean bean = isInToggle(new Point(currentX, currentY));
if (bean != null) {
Point position = positionList.get(bean.getIndex());
prePoint = position;
path.moveTo(position.x, position.y);
IsDownIn = true;
invalidate();
}
}
}
}
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
private MarkBean isInToggle(Point point) {
MarkBean bean = new MarkBean();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
Point position = positionList.get(i);
ToggleView child = (ToggleView) getChildAt(i);
if (PositionUtils.IsIn(point, position, child.getOutR())) {
if (!checkedMap.get(i)) {
checkedMap.put(i, true);
checkedINdexSet.add(i);
bean.setIndex(i);
bean.setCheck(true);
return bean;
}
}
}
return null;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, paint);
if (prePoint.x != -1 && prePoint.y != -1 && currentPoint.x != -1 && currentPoint.y != -1) {
canvas.drawLine(prePoint.x, prePoint.y, currentPoint.x, currentPoint.y, paint);
}
super.onDraw(canvas);
}
}
代码总是最直接的引导,我看博客最喜欢的是研究代码,当然如果代码中有一些讲解就更好了。