效果图

Android应用如何识别当前窗口中的各个控件_控件

这Q弹的动画,妖艳的色彩转换,着实和市面上的普通Switch控件不太一样。下面对它逐一拆解,从0到1实现它。

一、设计思路

Android系统提供的SwitchButon很好地体现了metiarial design设计风格,但不够妖艳。我希望用两个比较冲突的对比色进行控件的颜色设计,这样摆在浅色背景页面的时,控件可以达到一种直刺眼睛的效果。抓人、妖艳、骚气外放。只要整体页面搭配得当,该控件必定可以“外骚内纯”。微微模糊的光晕可以使得本就亮丽的颜色变得更加妖艳。

为增加设计的可扩展性,可以改变颜色从而体现不同的风格。

Android应用如何识别当前窗口中的各个控件_控件_02

二、实现方式

总体而言,虽然控件的外表非常妖艳,但交互逻辑是比较简单的。不考虑继承自系统Switch,因为并不需要再去了解如何扩展Switch。因此直接继承自View即可。整个逻辑控制在onTouchEvent中实现,声明好各个可配置的属性,如颜色、大小等等。

  • UI绘制

可以看到,图形由两大部分构成,一个是前面蓝色的圆角矩形(指示器),一个是后面红色的长条矩形(背景条),分别对这两个图形进行绘制即可。onDraw部分的代码如下:

画背景条:其中bkgRect代表待绘制矩形区域范围,height、width为控件长宽,bkgBarW、bkgBarH为背景条长宽,由于是圆角矩形,调用canvas的drawRoundRect方法进行绘制,bkgBarPaint控制背景条颜色

//画背景条RectF bkgRect = new RectF((width - bkgBarW) / 2f, height / 2 - (bkgBarH / 2), (width - bkgBarW) / 2f + bkgBarW, height / 2 + (bkgBarH / 2));canvas.drawRoundRect(bkgRect, bkgBarH / 4, bkgBarH / 4, bkgBarPaint);


画前景圆角矩形:同样的,indicatorRect为待绘制圆角矩形的区域范围,indicatorW、indicatorH为指示器宽高,indicatorX、indicatorY为指示器的中心坐标点,该坐标之后会结合animator进行动态计算,最后调用canvas的drawRoundRect进行绘制

//画指示器RectF indicatorRect = new RectF(        indicatorX,        indicatorY,        indicatorW + indicatorX,        (height - indicatorH) / 2 + indicatorH);canvas.drawRoundRect(indicatorRect, indicatorH / 6, indicatorH / 6, indicatorPaint);


画图标或文字:这部分计算好文字或图标的坐标进行绘制即可,需要注意的是,如果要绘制文字,需要计算出文字的基线位置,方便与指示器在视觉上居中

//画图标int baseLineY = (int) (indicatorRect.centerY() - textTop / 2 - textBottom / 2);//基线中间点的y轴计算公式if (status == false) {    canvas.drawText("♂", indicatorRect.centerX(), baseLineY, textPaint);} else {    canvas.drawText("♀", indicatorRect.centerX(), baseLineY, textPaint);}
  • 动画实现


为方便控制,定义两个AnimatorSet,分别为animOnSet、animOffSet,animOnSet代表开关选择器打开时的动画合集,animOffSet代表开关选择器关闭时的动画合集。设置为BounceInterpolator即可配置弹性效果。

animatorOn = ValueAnimator.ofFloat(indicatorStartX, indicatorEndX);animatorOn.setDuration(500);animatorOn.setInterpolator(new BounceInterpolator());animatorOn.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {    @Override    public void onAnimationUpdate(ValueAnimator animation) {        indicatorX = (float) animation.getAnimatedValue();        postInvalidate();    }});animOnSet = new AnimatorSet();animOnSet.playTogether(animatorOn, animatorColorOn);animatorOff = ValueAnimator.ofFloat(indicatorEndX, indicatorStartX);animatorOff.setDuration(500);animatorOff.setInterpolator(new BounceInterpolator());animatorOff.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {    @Override    public void onAnimationUpdate(ValueAnimator animation) {        indicatorX = (float) animation.getAnimatedValue();        postInvalidate();    }});animOffSet = new AnimatorSet();animOffSet.playTogether(animatorColorOff, animatorOff);
  • 加亿点细节

仔细观察可以注意到,指示器是带有同颜色的阴影的,淡淡的阴影向四周晕开,如同光雾一般。这里使用设计好的阴影背景图切图即可,但为了增加控件通用性,采取了讨巧的办法。通过对Bitmap的处理从而在指示器颜色变化时,绘制的阴影颜色也随之变化。代码如下,bmShdow为待绘制的阴影图片


bmShadow = BitmapFactory.decodeResource(getResources(), R.drawable.img_shadow_rect_blue);//sex_blue为配置的指示器为“开”状态时的颜色,int值bmShadow = BitmapUtils.replacePixelColor(bmShadow, sex_blue);

再仔细观察,会发现在指示器动画执行的过程中,指示器颜色也完成了一个渐变过渡。这里有个变换颜色动态计算的操作,依然采用animator进行计算,其中indicatorPaint控制指示器颜色


animatorColorOn = new ValueAnimator();animatorColorOn.setIntValues(sex_blue, sex_red);animatorColorOn.setEvaluator(new ArgbEvaluator());animatorColorOn.setDuration(500);animatorColorOn.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {    @Override    public void onAnimationUpdate(ValueAnimator animation) {        int color = (int) animation.getAnimatedValue();        indicatorPaint.setColor(color);    }});animatorColorOff = new ValueAnimator();animatorColorOff.setIntValues(sex_red, sex_blue);animatorColorOff.setEvaluator(new ArgbEvaluator());animatorColorOff.setDuration(500);animatorColorOff.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {    @Override    public void onAnimationUpdate(ValueAnimator animation) {        int color = (int) animation.getAnimatedValue();        indicatorPaint.setColor(color);    }});

三、后记

控件设计或开发,无处不体现着“自顶向下”的思想。弄清需求,理清逻辑,打磨细节,做到这三点,绝大部分控件设计或开发的难题一定可以迎刃而解。