九宫格解锁大家应该经常用到,比如支付宝里,就用到这个。我借鉴了一些大神的作品,并在基础在进行了一些优化修改封装,做到了高内聚低耦合,使用也极其简单方便。先放上几张截图吧(包括图片也直接借用大神的)
这个控件的宽度和高度相等,都为屏幕的宽度。因此,这个控件可以根据需要调整所在屏幕的位置。也可以在布局文件中设置控件的宽度,结果就是这个控件是一个正方形,并且限制在正方形内做手势动作。我们追求的就是简单实用方便。好了现在我贴下代码
控件SudokoView .java
package com.fay.sudokounlock; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Cap; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; /** * {@link email:1940125001@qq.com} * @author Fay * @since 2014/5/23 */ public class SudokoView extends View { private String TAG = "SudokoView"; //width for this SudokoView private int width ; //height for this SudokoView,width = height private int height ; //spacing between two points private int spacing = 0; private int startX = 0; private int startY = 0; //current x-Location when move private int moveX = 0; //current y-Location when move private int moveY = 0; //check whether the any point is selected private boolean hasSelected = false; //outer paint for the line private Paint outerPaint = null; //inner paint for the line private Paint innerPaint = null; //the String of lock private StringBuilder lockString = new StringBuilder(); //default bitmap for the point private Bitmap defaultBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lock_default); //selected bitmap for the point private Bitmap selectedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lock_selected); //the radius for the default bitmap private int defaultRadius = defaultBitmap.getWidth() / 2; //the radius for the selected bitmap private int selectedRadius = selectedBitmap.getWidth() / 2; //start point for touching; //this will only appears when the ACTION_DOWN in the area of nine points. private PointInfo startPoint = null; //nine point for this SudokoView private PointInfo ninePoints[] = new PointInfo[9]; private OnLockFinishListener mOnLockFinishListener = null; public SudokoView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public SudokoView(Context context, AttributeSet attrs) { super(context, attrs); } public SudokoView(Context context) { super(context); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, widthMeasureSpec); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { width = getWidth(); height = getWidth(); spacing = (width - selectedRadius * 2 * 3) / 4; initPoints(); initPaint(); super.onLayout(changed, left, top, right, bottom); } @Override protected void onDraw(Canvas canvas) { if (startX > 0 && startY > 0 && moveX > 0 && moveY > 0) { canvas.drawLine(startX, startY, moveX, moveY, outerPaint); } drawNinePoint(canvas); super.onDraw(canvas); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (hasSelected) {//if the points has been selected previously, clear all initPoints(); startPoint = null; hasSelected = false; lockString.delete(0, lockString.length()); invalidate(); return false;//must return false to end touch } int x = (int) event.getX(); int y = (int) event.getY(); for (PointInfo pointInfo : ninePoints) { if (pointInfo.isInPoint(x, y)) { pointInfo.setSelected(true); startPoint = pointInfo; startX = pointInfo.getCenterX(); startY = pointInfo.getCenterY(); lockString.append(pointInfo.getNumber()); hasSelected = true; } } break; case MotionEvent.ACTION_MOVE: Log.v(TAG, "ACTION_MOVE"); moveX = (int) event.getX(); moveY = (int) event.getY(); for (PointInfo pointInfo : ninePoints) { if (!pointInfo.isSelected() && pointInfo.isInPoint(moveX, moveY)) { startX = pointInfo.getCenterX(); startY = pointInfo.getCenterY(); int length = lockString.length(); if (length > 0) { int previousNumber = lockString.charAt(length - 1) - 48; ninePoints[pointInfo.getNumber()].setSelected(true); ninePoints[previousNumber].setNextNumber(pointInfo.getNumber()); } hasSelected = true; lockString.append(pointInfo.getNumber()); } } break; case MotionEvent.ACTION_UP: if (lockString.length() > 0) { mOnLockFinishListener.finish(lockString); } startX = 0; startY = 0; moveX = 0; moveY = 0; break; } invalidate(); return true; } /** * draw the nine points for this SudokoView including default bitmap and selected bitmap * @param Canvas canvas */ private void drawNinePoint(Canvas canvas) { if (null != startPoint) { drawLine(canvas, startPoint); } for (PointInfo mPointInfo : ninePoints) { //Firstly, if the point is selected, draw the selected bitmap on this point if (mPointInfo.isSelected()) { canvas.drawBitmap(selectedBitmap, mPointInfo.getSelectedX(), mPointInfo.getSelectedY(), null); } //Then, draw the default bitmap on this point canvas.drawBitmap(defaultBitmap, mPointInfo.getDefaultX(), mPointInfo.getDefaultY(), null); } } /** * draw a line between two points * @param Canvas canvas * @param PointInfo mPointInfo */ private void drawLine(Canvas canvas, PointInfo mPointInfo) { while (mPointInfo.isHasNextPoint()) { int index = mPointInfo.getNextNumber(); canvas.drawLine(mPointInfo.getCenterX(), mPointInfo.getCenterY(), ninePoints[index].getCenterX(), ninePoints[index].getCenterY(), outerPaint); canvas.drawLine(mPointInfo.getCenterX(), mPointInfo.getCenterY(), ninePoints[index].getCenterX(), ninePoints[index].getCenterY(), innerPaint); mPointInfo = ninePoints[index]; } } /** * initialize the paint */ private void initPaint() { outerPaint = new Paint(); outerPaint.setColor(Color.GRAY); outerPaint.setStrokeWidth(defaultBitmap.getWidth()); outerPaint.setAntiAlias(true); outerPaint.setStrokeCap(Cap.ROUND); innerPaint = new Paint(); innerPaint.setColor(Color.WHITE); innerPaint.setStrokeWidth(defaultBitmap.getWidth() - 5); innerPaint.setAntiAlias(true); innerPaint.setStrokeCap(Cap.ROUND); } /** * initialize basic nine points */ private void initPoints() { int selectedX = spacing; int selectedY = spacing; int defaultX = spacing + selectedRadius - defaultRadius; int defaultY = spacing + selectedRadius - defaultRadius; PointInfo mPointInfo = null; for (int index = 0; index < 9; index ++) { if (index == 3 || index == 6) { selectedX = spacing; selectedY += selectedRadius * 2 + spacing; defaultX = spacing + selectedRadius - defaultRadius; defaultY += selectedRadius * 2 + spacing; } else { if (index != 0) { selectedX += selectedRadius * 2 + spacing; //selectedY = selectedY; defaultX += selectedRadius * 2 + spacing; //defaultY = defaultY; } } mPointInfo = new PointInfo(defaultX, defaultY, selectedX, selectedY, defaultRadius, selectedRadius, index, index); mPointInfo.setSelected(false); mPointInfo.setNumber(index); mPointInfo.setNextNumber(index); ninePoints[index] = mPointInfo; } } /** * Listener when the locking is finishing */ public interface OnLockFinishListener { void finish(StringBuilder lockString) ; } /** * set the Listener for the callback */ public void setOnLockFinishListener (OnLockFinishListener mOnLockFinishListener) { this.mOnLockFinishListener = mOnLockFinishListener; } }
每一个点单元的实体类PointInfo.java
package com.fay.sudokounlock; import java.io.Serializable; /** * the basic data for the point * @author Fay * @since 2014/5/23 */ public class PointInfo implements Serializable{ private static final long serialVersionUID = 1L; //default x-Location private int defaultX; //default y-Location private int defaultY; //selected x-Location private int selectedX; //selected y-Location private int selectedY; //the radius for the default bitmap private int defaultRadius; //the radius for the selected bitmap private int selectedRadius; //the number about this point private int number; //the next point connection with this point private int nextNumber; //whether the point is selected private boolean isSelected; public PointInfo(int defaultX, int defaultY, int selectedX, int selectedY, int defaultRadius, int selectedRadius, int number, int nextNumber) { super(); this.defaultX = defaultX; this.defaultY = defaultY; this.selectedX = selectedX; this.selectedY = selectedY; this.defaultRadius = defaultRadius; this.selectedRadius = selectedRadius; this.number = number; this.nextNumber = number;// } public int getDefaultX() { return defaultX; } public void setDefaultX(int defaultX) { this.defaultX = defaultX; } public int getDefaultY() { return defaultY; } public void setDefaultY(int defaultY) { this.defaultY = defaultY; } public int getSelectedX() { return selectedX; } public void setSelectedX(int selectedX) { this.selectedX = selectedX; } public int getSelectedY() { return selectedY; } public void setSelectedY(int selectedY) { this.selectedY = selectedY; } public int getDefaultRadius() { return defaultRadius; } public void setDefaultRadius(int defaultRadius) { this.defaultRadius = defaultRadius; } public int getSelectedRadius() { return selectedRadius; } public void setSelectedRadius(int selectedRadius) { this.selectedRadius = selectedRadius; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } public int getNextNumber() { return nextNumber; } public void setNextNumber(int nextNumber) { this.nextNumber = nextNumber; } public boolean isSelected() { return isSelected; } public void setSelected(boolean isSelected) { this.isSelected = isSelected; } /* * whether current point which touch is in the area of this point * @return boolean */ public boolean isInPoint(int x, int y) { return (selectedX <= x && x <= selectedX + 2 * selectedRadius && selectedY <= y && y <= selectedY + 2 * selectedRadius); } /** * whether current point the next connecting point * @return boolean */ public boolean isHasNextPoint() { //if equals, return false, else return true return (number == nextNumber) ? false : true; } /** * get the center x-Location of this point * @return Integer */ public int getCenterX() { return selectedX + selectedRadius; } /** * get the center y-Location of this point * @return */ public int getCenterY() { return selectedY + selectedRadius; } @Override public String toString() { return "PointInfo [defaultX=" + defaultX + ", defaultY=" + defaultY + ", selectedX=" + selectedX + ", selectedY=" + selectedY + ", defaultRadius=" + defaultRadius + ", selectedRadius=" + selectedRadius + ", number=" + number + ", nextNumber=" + nextNumber + ", isSelected=" + isSelected + "]"; } }
最后我们看下使用的MainActivity.java
package com.fay.sudokounlock; import com.fay.sudokounlock.SudokoView.OnLockFinishListener; import android.app.Activity; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends Activity { private SudokoView mSudokoView = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSudokoView = (SudokoView) findViewById(R.id.sudoko); mSudokoView.setOnLockFinishListener(new OnLockFinishListener() { @Override public void finish(StringBuilder lockString) { Toast.makeText(getApplicationContext(), lockString, 2000).show(); } }); } }
然后看下布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" > <com.fay.sudokounlock.SudokoView android:id="@+id/sudoko" android:layout_width="fill_parent" android:layout_height="0dp" android:layout_centerInParent="true" > </com.fay.sudokounlock.SudokoView> </RelativeLayout>
相信到这里,各位可以分分钟熟悉使用这个控件。我们要的就是最大化简单、实用、规范。
源码下载地址:https://files.cnblogs.com/yinweiliang/SudokoUnlock.rar