您的位置:首页 > 其它

完美的实现九宫格锁屏

2016-04-13 21:04 344 查看
        第一次写博客,写的不好,知识点不到位的请大神,大牛指点。不废话了直接进入主题。

先看下效果图:





大致说下我的思路:

一:对题目的思考。

二:继承View 主要重写 onDraw(Canvas),onMeasure(int,int),oTouchEvent(MotionEvent);三个方法;

三:回调。

我们来说明下;

九宫格是个控件,是个单一的控件,不是一组控件,所以我直接继承view ,而不继承ViewGroup。

要自定义控件必须要重写测量方法 onMeasure(int,int).

是不是这样得到控件width,height很简单,其实onMeasure这样写是有局限性的,你的控件必须要设定大小和fill_parent才可以这样使用。

如果要是wrap_content,就不可以这样用了,

<pre name="code" class="java">/**
* 作用是返回一个默认的值,如果MeasureSpec没有强制限制的话则使用提供的大小.否则在允许范围内可任意指定大小
* 第一个参数size为提供的默认大小,第二个参数为测量的大小
*/
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {
// Mode = UNSPECIFIED,AT_MOST时使用提供的默认大小
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
// Mode = EXACTLY时使用测量的大小
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}



这是在网上找的,可以看到当模式为MeasureSpec.UNSPECIFIED时,即未指明大小或warp_content,测量的结果为size;

而size为多少呢?

/**
* 这个方法需要被重写,应该由子类去决定测量的宽高值,
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
也就是这个size=getSuggestedMinimuWidth()和size=getSuggestedMinimuHeight()。

这两方法是View的两保护方法,下面是源码

*/
7285 protected int getSuggestedMinimumWidth() {
7286 int suggestedMinWidth = mMinWidth;
7287
7288 if (mBGDrawable != null) {
7289 final int bgMinWidth = mBGDrawable.getMinimumWidth();
7290 if (suggestedMinWidth < bgMinWidth) {
7291 suggestedMinWidth = bgMinWidth;
7292 }
7293 }
7294
7295 return suggestedMinWidth;
7296 }
7297
可以看到是直接返回最小的宽高。

我们在来看两段源码:


4000
4162        public final int getMeasuredWidth() {
4163            return mMeasuredWidth;
4164        }


7191        protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
7192            mMeasuredWidth = measuredWidth;
7193            mMeasuredHeight = measuredHeight;
7194
7195            mPrivateFlags |= MEASURED_DIMENSION_SET;
7196        }
7197


是不是发现什么。getMeasuredWidth()和getMeasuredHeight()两方法得到的值就是getDefaultSize()的返回值。

所以这两个方法与模式没关系。所以在自定义测量的时候可以直接用这两个方法。

不懂在上网查查,onMeasure(int ,int)先到这里。

再来说说onDraw(Canvas)

这个ondraw方法在view初始化后调用绘制画布。每当控件增加新的图案都会调用ondraw(canvas)方法,调用这个方法是重新绘制,而不是只绘制新增的部分。要在UI线程调用

invalidate()刷新画布才可以显示。Canvas是画图,创建Canvas有两种方法。一个是当做onDraw参数传进去,

另一中是: Bitmap b=Bitmap.createBitmap(200,200,Bitmap.Config.ARGB_888);

Canvas c=new Canvas(b);创建一个200*200的Bitmap当做Canvas操作画布。

不懂的可以上网查查。

我在细说一下我的代码:


最后一个参数为接口,<pre name="code" class="java">public class CircleCanvas extends ListView {
private Context mct;
private Paint mpaint; //画笔
private Paint hollowPaint;
private Paint linePaint;
private int width; //view 宽,高
private int height;
private int startRangeWidth; //起点坐标(x,y)
private int startRangeHeight;
private int endRangeWidth; //终点坐标(X,Y)两个坐标构成滑动区域。
private int endRangeHeight;
private int spotIntervalWidth; //每个点之间的间隔
private int spotIntervalHeight;
private List<SpotXY> spot; //存放9个点的坐标
private List<SpotXY> delSpot; //作用方面显示画的结果
private List<SpotXY> storeSpot; //储存选中的空心圆的坐标 即spot
private List<Segment> segment; //储存画出的线段
private int i=0;
private float r=100.0f; //大圆半径
private SpotXY xy; //一个点
private SpotXY xyTwo;
private int tclWay=0;
private boolean isspot=true;
private static int countId=1; //每个点的id编号
private String pwd; //密码
private OnCircleCanvasOver oncirclecanvasover;

这个参数在储存画的线段的时候会用的,判断不要重复储存一样的线段

private static int countId=1; //每个点的id编号
初始化操作,

public CircleCanvas(Context context, AttributeSet attrs) {
super(context, attrs);
this.mct=context;
spot=new ArrayList<SpotXY>();
delSpot=new ArrayList<SpotXY>();
storeSpot=new ArrayList<SpotXY>();
segment=new ArrayList<Segment>();
WindowManager wm=(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metric = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metric);
intn();
}
public CircleCanvas(Context context) {
super(context);
this.mct=context;
spot=new ArrayList<SpotXY>();
delSpot=new ArrayList<SpotXY>();
storeSpot=new ArrayList<SpotXY>();
segment=new ArrayList<Segment>();
WindowManager wm=(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metric = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metric);
intn();
}
private void intn() {
mpaint=new Paint();
mpaint.setColor(Color.YELLOW );
mpaint.setStrokeWidth(3);
hollowPaint=new Paint();
hollowPaint.setStyle(Paint.Style.STROKE);
hollowPaint.setStrokeWidth(20);
hollowPaint.setColor(Color.BLUE );
linePaint=new Paint();
linePaint.setColor(Color.GREEN );
linePaint.setStrokeWidth(20);

}
绘制,主要是这两个storeSpot为自己滑动过点的坐标集合,xy类型是SpotXY类这个是封装点的工具类。

有三参数x坐标 ,y坐标和点的id。上面表达式表示提出最近储存的点与即将要滑动的点画出线段。

protected void onDraw(Canvas canvas)
{
switch(tclWay)
{
case 0:
for(int i=0;i<spot.size();i++)
{
canvas.drawCircle(spot.get(i).getSpotx(),spot.get(i).getSpoty(), 30, mpaint);
}
break;
case 1:
for(int i=0;i<spot.size();i++)
{
canvas.drawCircle(spot.get(i).getSpotx(),spot.get(i).getSpoty(), 30, mpaint);
}
canvas.drawCircle(xy.getSpotx(), xy.getSpoty(), r, hollowPaint);
delSpot(xy);
break;
case 2:
for(int i=0;i<spot.size();i++)
{
canvas.drawCircle(spot.get(i).getSpotx(),spot.get(i).getSpoty(), 30, mpaint);
}
for(int i=0;i<storeSpot.size();i++)
{
canvas.drawCircle(storeSpot.get(i).getSpotx(), storeSpot.get(i).getSpoty(), r, hollowPaint);
}
for(int i=0;i<segment.size();i++)
{
canvas.drawLine(segment.get(i).getStartX(), segment.get(i).getStartY(), segment.get(i).getEndX(), segment.get(i).getEndY(), linePaint);
}

xy=storeSpot.get(storeSpot.size()-1);
delSpot(xy);

}
super.onDraw(canvas);
}
移除已近划过的点delSpot(xy)

/**
* 移除被选中的点
* @param xy2
*/
private void delSpot(SpotXY xy2) {
for(int i=0;i<delSpot.size();i++)
{
if(xy.getSpotx()==delSpot.get(i).getSpotx()&&xy.getSpoty()==delSpot.get(i).getSpoty())
{
delSpot.remove(i);
break;
}
}
}

  我是把手机分为4个区域,取中间相邻的两区域为手势可滑动区域。进而可以算出可以滑动区域的坐标,大小。

/**
* 计算有效滑动区域
* @param width2 view 宽
* @param height2 view 高
* @param i 区域划分的个数
*/
private void glidingArea(int width2, int height2, int i) {
startRangeWidth=0;
startRangeHeight=height2/i;
endRangeWidth=width2;
endRangeHeight=height2*3/i;
spotIntervalWidth=width2/6;
spotIntervalHeight=Math.abs((endRangeHeight-startRangeHeight)/6);
//countId=0;
XYZ(startRangeWidth,startRangeHeight,endRangeWidth,endRangeHeight,spotIntervalWidth,spotIntervalHeight);
for(SpotXY sxy:spot){
delSpot.add(sxy);

}

}
countId是静态变量,在项目里不推崇大家用静态变量,这个id是在项目快要完成时加上的,想了一会只能用静态变量。

<pre name="code" class="java"><pre name="code" class="java"> /**
* 计算9个点的坐标
* @param startRangeWidth2
* @param startRangeHeight2
* @param endRangeWidth2
* @param endRangeHeight2
* @param spotIntervalWidth2
* @param spotIntervalHeight2
* @return
*/
private void XYZ(int startRangeWidth2, int startRangeHeight2,
int endRangeWidth2, int endRangeHeight2,
int spotIntervalWidth2, int spotIntervalHeight2) {

for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{

spot.add(new SpotXY((int) (spotIntervalWidth2*(2*(j+1)-1)),
(int) (startRangeHeight2+spotIntervalHeight2*(2*(i+1)-1)),countId++));

}

}
//return spot;
}



这是判断点击点 和滑动的时候在不在可滑动区域,要是在返回最近点的坐标。

/**
* 返回离点击点最近的点
* @param widthX
* @param widthY
* @param spot2
* @return
*/
private SpotXY latelyClick(float widthX, float widthY, List<SpotXY> spot2) {
if(widthY<endRangeHeight&&widthY>startRangeHeight)
{
for(SpotXY spotxy:spot2)
{
if(Math.abs(spotxy.getSpotx()-widthX)<r&&Math.abs(spotxy.getSpoty()-widthY)<r)
{
return new SpotXY(spotxy.getSpotx(),spotxy.getSpoty(),spotxy.getId());
}
}
}
return null;
}
下面事件处理份三处贴

记录点击点 并判断在不在可滑动区域内,在的画在最近的点加入storeSpot集合里(已经划过的点)。tclWay=1执行onDraw里的case 1; 刷新显示空心圆。

public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
System.out.println("X变化为:"+event.getX());
//invalidate();
float widthX=event.getX();
float widthY=event.getY();
xy=latelyClick(widthX,widthY,spot);
if(xy!=null)
{
storeSpot.add(xy);
tclWay=1;
invalidate();
}
滑动时的事件处理,滑动的时候每时每刻都在判断是不是处在可滑动区域内,是否有新的点可以返回。如果有新点,还要遍历storeSpot集合看看里面有没有这个带没有就加入里面。然后在在这个点和上个点做为线段的两点加入到segment集合里。segment这个集合类型是Segment有四个参数即两个坐标点。tclWay变成2执行 onDraw画线段。

case MotionEvent.ACTION_MOVE:
System.out.println("X变化为:"+event.getX());
float movewidthX=event.getX();
float movewidthY=event.getY();
xyTwo=latelyClick(movewidthX,movewidthY,spot);
if(storeSpot.size()==0) //当点击不在规定范围时, 滑动到规定范围里执行次判断
{
xy=xyTwo;
}
if(xyTwo!=null&&(xy.getSpotx()!=xyTwo.getSpotx()||xy.getSpoty()!=xyTwo.getSpoty()))
{

for(SpotXY sxy:storeSpot)
{

if(sxy.getSpotx()==xyTwo.getSpotx()&&sxy.getSpoty()==xyTwo.getSpoty())
{
//storeSpot.add(xyTwo);
isspot=false;
break;
}else{
isspot=true;
}
}
if(isspot)
{

if(storeSpot.size()>0)
{
segment.add(new Segment(xy.getSpotx(), xy.getSpoty(), xyTwo.getSpotx(), xyTwo.getSpoty()));
}else{
xy=xyTwo;
}
storeSpot.add(xyTwo);
}
tclWay=2;
invalidate();
}
break;
回到以前,回到只有九个点的状态

case MotionEvent.ACTION_UP:
System.out.println("X变化为:"+event.getX());
tclWay=0;
setPwd();
storeSpot.removeAll(storeSpot);
segment.removeAll(segment);
isspot=true;
invalidate();
break;

}
return true;
接口设计。

public void setOnCircleCanvasOver(OnCircleCanvasOver oncirclecanvasover)
{
this.oncirclecanvasover=oncirclecanvasover;
}
public OnCircleCanvasOver getOnCircleCanvasOver()
{
return oncirclecanvasover;

}
public List<SpotXY> getStoreSpot() {
return storeSpot;
}
public void setStoreSpot(List<SpotXY> storeSpot) {
this.storeSpot = storeSpot;
}
public void setPwd()
{
pwd="123654789";
oncirclecanvasover.Over(pwd);
}
接口实现在MainActivity里面
cecs=(CircleCanvas)findViewById(R.id.a);
cecs.setOnCircleCanvasOver(new OnCircleCanvasOver() {
public void Over(String pwd) {
StringBuilder sb=new StringBuilder();
List<SpotXY> list=cecs.getStoreSpot();
for(SpotXY xy:list)
{
sb.append(xy.getId());
}
if(pwd.equals(sb.toString()))
{
Toast.makeText(getApplication(), "登陆成功"+pwd, Toast.LENGTH_LONG).show();
}else
{

Toast.makeText(getApplication(), "两次输入不一样"+sb.toString(), Toast.LENGTH_LONG).show();
}
}
});
}
好了就到这里了,也不早了,早上还要上班。代码设计的不是很好,下次会改进。如有错误的地方请大家指点。谢谢!下面有源码

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: