Android九宫格解锁自定义控件(附源码)

九宫格解锁大家应该经常用到,比如支付宝里,就用到这个。我借鉴了一些大神的作品,并在基础在进行了一些优化修改封装,做到了高内聚低耦合,使用也极其简单方便。先放上几张截图吧(包括图片也直接借用大神的)

这个控件的宽度和高度相等,都为屏幕的宽度。因此,这个控件可以根据需要调整所在屏幕的位置。也可以在布局文件中设置控件的宽度,结果就是这个控件是一个正方形,并且限制在正方形内做手势动作。我们追求的就是简单实用方便。好了现在我贴下代码

控件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

原文地址:https://www.cnblogs.com/yinweiliang/p/3771044.html