Android自定义View——搞一个战网验证

    最近打算好好看一下自定义View这块的东西,刚好无意(毕竟AFK好久了)看到手机里的战网安全令牌,心想这东西既不难也不简单,刚好适合来练习一下。于是开始尝试写一个类似的东西。好吧,先看一下官方的:


不要妄图登录我的战网

先来分析一波:

    首先一个大饼,(其实整个屏幕显示都可以做成一块儿,主要是背影图片与大饼的颜色关联,我这里单独提取出大饼来),然后是外圈的一个圆环,没有走过的进度显示为黑色,而走过的进度大致可以分成三种颜色(不知道是不是我色盲,感觉它这三种颜色应该是做了过度的)。我这里直接分为绿色、橙色、红色好了。要注意的是最后五秒的时候会有一个小圆里有5秒的倒计时。

    先画饼:

```

//大圆半径

final int centerX=getWidth()/2,centerY=getWidth()/2;

final int radius=Math.min(centerX,centerY)-getPaddingLeft();

//画最大的实心圆

mPaint.setAntiAlias(true);

mPaint.setStyle(Paint.Style.FILL);

mPaint.setColor(Color.parseColor("#434343"));

canvas.drawCircle(centerX, centerY, radius, mPaint);

//画外圈圆环

mPaint.setColor(Color.BLACK);

mPaint.setStrokeWidth(20);

mPaint.setStyle(Paint.Style.STROKE);

canvas.drawCircle(centerX, centerY, radius -30, mPaint);

//画进度圆环

mProgressPaint.setAntiAlias(true);

mProgressPaint.setStrokeWidth(25);

mProgressPaint.setStyle(Paint.Style.STROKE);

mProgressPaint.setColor(mProgressColor);

RectF oval =new RectF(centerX - radius+30, centerY - radius+30, centerX + radius-30, centerY+ radius-30);

canvas.drawArc(oval, -90, mProgress, false, mProgressPaint);

```

        这里的动画刚开始的时候我是用子线程通过sleep一定时间来更新mProgress,然后invalidate()方法进行刷新,后来觉得既然是动画那不是应该用Android自带的各种Animator嘛,使用Thread.sleep()会不会比较奇怪?然后果断换成了ValueAnimator,感觉效果挺好。

        最后5秒显示小圆里的倒计时,这个一看就需要计算最后一段弧上的坐标参数了,简单的正弦余弦定理,不多说。因为我们的刷新周期是30秒,所以最后5秒的时候还有60度的圆环没走完。注意令数字居中显示即可。

```

//画倒计时5秒

if(mProgress>=300){

mProgressPaint.setStyle(Paint.Style.FILL);

    int minRadius=getPaddingLeft();

    double angle=Math.toRadians(360f-mProgress);

    float minCenterX=(float) (centerX-(Math.sin(angle)*(radius-30)));

    float minCenterY=(float) (centerY-(Math.cos(angle)*(radius-30)));

    canvas.drawCircle(minCenterX,minCenterY,minRadius,mProgressPaint);

    mPaint.setTextSize(DisplayUtil.sp2px(mContext, mTextSize)-30);

    mPaint.setStrokeWidth(5);

    int lastSecond=5;

    Rect tempRect=new Rect();

    mPaint.getTextBounds(lastSecond+"",0,1,tempRect);

    lastSecond=(int)(360-mProgress)/12+1;

    canvas.drawText(lastSecond+"",minCenterX,minCenterY+tempRect.height()/2,mPaint);

}

```

        最后动态密码下面还有一个可以点击的字符串提示“复制密码”,它与上面的动态密码中间有一定的间隔,要注意的是这个间隔在点击事件上算在了“复制密码”这个字符串上面。当开始的时候我的Touch事件是严格限定在“复制密码”这个Rect上面的,但后来测试发现手指常常点不到?这不科学呀,我这个字体不比官方的小啊,为啥官方的从来没有这种情况。后来我仔细测试了一下官方的这个功能,发现我还是天真了,官方的并没有将touch事件严格限定在“复制密码”上面,原来是连上面的空隙一起来算的,我晕,这样的话就简单了。

```

mPaint.setTextSize(DisplayUtil.sp2px(mContext,16));

mPaint.getTextBounds(mCopyText,0,mCopyText.length(),mCopyRect);

mPaint.setStrokeWidth(2);

mPaint.setColor(Color.BLUE);

canvas.drawText(mCopyText,centerX,centerY+mTextRect.height(),mPaint);


@Override

public boolean onTouch(View view, MotionEvent motionEvent) {

float x=motionEvent.getX(),y=motionEvent.getY();

    Point p1=new Point(getWidth()/2-mCopyRect.width()/2,getHeight()/2);

    Point p2=new Point(getWidth()/2+mCopyRect.width()/2,getHeight()/2+2*mCopyRect.height());

    if(motionEvent.getAction()== MotionEvent.ACTION_UP) {

if (mState== State.START&&x>p1.x&&xp1.y&&y

ClipboardManagercm= (ClipboardManager)mContext.getSystemService(Context.CLIPBOARD_SERVICE);

            cm.setPrimaryClip(ClipData.newPlainText(null,mText));

            Toast.makeText(mContext,mContext.getString(R.string.BallentVerifierView_copyover_test),Toast.LENGTH_SHORT).show();

        }

}

return true;

}

```

        来看一下效果:


请忽略这么丑的背景色
Android自定义View——搞一个战网验证_第1张图片
开始界面应该是这个样子的

        下面那张图是开始的时候显示的界面,当有验证请求的话会自动切换到显示密码的状态。我们这里只是简单的做一下界面工作。

```

//画显示器

RectF rect=new RectF(centerX/3*2+centerX/12,centerY/3+centerY/12,centerX/3*4-centerX/12,centerY/3*2+centerY/12);

mPaint.setStrokeWidth(10);

mPaint.setColor(Color.BLUE);

canvas.drawRoundRect(rect,20,20,mPaint);

mPaint.setStrokeWidth(80);

canvas.drawLine(centerX,centerY/3*2+centerY/12,centerX,centerY/3*2+centerY/12*2,mPaint);

mPaint.setStrokeWidth(10);

canvas.drawLine(centerX-70,centerY/3*2+centerY/12*2,centerX+70,centerY/3*2+centerY/12*2,mPaint);

//画ZZZ

mPaint.setTextAlign(Paint.Align.CENTER);

mPaint.setTextSize(50);

mPaint.getTextBounds("Z Z Z", 0, "Z Z Z".length(), mTextRect);

if(mProgress<1){

canvas.drawText("Z    ",centerX,centerY/3*2-mTextRect.height()/2,mPaint);

}else if(mProgress<2){

canvas.drawText("Z Z  ",centerX,centerY/3*2-mTextRect.height()/2,mPaint);

}else{

canvas.drawText("Z Z Z",centerX,centerY/3*2-mTextRect.height()/2,mPaint);

}

//开始游戏吧

mPaint.setTextSize(60);

mPaint.setStrokeWidth(3);

mText=mContext.getString(R.string.BallentVerifierView_nologin_test);

mPaint.getTextBounds(mText,0,mText.length(),mTextRect);

canvas.drawText(mText,centerX,centerY+mTextRect.height(),mPaint);

int tempHeight=mTextRect.height();

mText=mContext.getString(R.string.BallentVerifierview_startgame_test);

mPaint.setTextSize(45);

mPaint.getTextBounds(mText,0,mText.length(),mTextRect);

canvas.drawText(mText,centerX,centerY+tempHeight+mTextRect.height()*2,mPaint);

```

        其实在真实的应用场景中,这个view需要考虑的东西还有很多,比如30秒的周期,不可能每次进入密码界面都是从0度开始计时。它一定是需要与产生密码的服务器进行时间上的同步的。当然,这里可以优化的东西其实也有很多,比如界面刷新问题,毕竟它从来只是进行局部刷新,没有必要每次都让所有视图元素进行重绘,尽管这可能也浪费不了多少系统资源。

        这个简单的自定义view做到这里其实也有一些其它的疑问,比如一般我们会不会尽量使用一个画笔,还是根据需要new出多个Paint,同样,在什么情况下我们会需要一块新的画布,还是说一般而言只要ondraw()里的canvas就足够了。在自定义view中,动画的制作一般采用Android自带的Animation还是根据需要简单的new一个Thread然后sleep接着invalidate()也可以,它们这间有没有明显的优劣呢。

你可能感兴趣的:(Android自定义View——搞一个战网验证)