首页 » 技术分享 » 完美的实现九宫格锁屏

完美的实现九宫格锁屏

 

        第一次写博客,写的不好,知识点不到位的请大神,大牛指点。不废话了直接进入主题。

先看下效果图:

大致说下我的思路:

一:对题目的思考。

二:继承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    

可以看到是直接返回最小的宽高。

我们在来看两段源码:

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();
				}
			}
		});
    }

好了就到这里了,也不早了,早上还要上班。代码设计的不是很好,下次会改进。如有错误的地方请大家指点。谢谢!下面有源码

 

转载自原文链接, 如需删除请联系管理员。

原文链接:完美的实现九宫格锁屏,转载请注明来源!

0