写作原因:Android进阶过程中有一个绕不开的话题——自定义View。这一块是安卓程序员更好地实现功能自主化必须迈出的一步。下面这个系列博主将通过实现几个例子来认识安卓自定义View的方法。从自定义View到自定义ViewGroup,View事件处理再到View深入分析(这一章如果水平未到位可能今后再补充),其中会涉及一些小的知识,包括Canvas的使用、动画等等。这是本系列的第二章,博主将通过定制一个摇杆的实例巩固上章的知识,并引入自定义View中实现用户交互和数据回调两个方面的功能。此外在本章中我们将看到数学知识(尤其是三角函数)在自定义View中的重要作用(这也是本例的一个难点),下面开始解放大脑和双手吧。
最终效果
本例的最终效果如下:
这就是本例的最终效果,我们将实现一个虚拟游戏方向摇杆,模拟摇杆操作。此外我们将为它写一个监听器实现摇动方向和速度等数据返回(本例中只实现了监听部分代码,数据读者可以自行加上)。
基本思路
首先依然是上一篇所讲述的那几个步骤,包括自定义XML属性,引入属性,测量和绘制几个部分(没看过上一篇文章的点击博客阅读或者查看博主的简书),除了这几个部分外我们还需要重写onTouchEvent()方法进行View事件处理和利用回调写好监听器。整体思路就是这样,看起来不难,实际操作起来陷阱多多。
具体实现
前期准备
新建value/attrs.xml,在XML中声明并引入以下属性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="InnerColor" format="color"/>
<attr name="OuterColor" format="color"/>
<declare-styleable name="NavController">
<attr name="InnerColor" />
<attr name="OuterColor"/>
</declare-styleable>
</resources>
这次我们只需要两个属性,小圆颜色和大圆颜色。然后新建一个java文件,继承View命名为NavController,在java中重写构造方法并且将XML属性导入,新建画笔对象,为之设置好属性。关键代码如下:
public NavController(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = getResources().obtainAttributes(attrs,R.styleable.NavController);
innerColor = ta.getColor(R.styleable.NavController_InnerColor,INNER_COLOR_DEFAULT);
outerColor = ta.getColor(R.styleable.NavController_OuterColor,OUTER_COLOR_DEFAULT);
ta.recycle();
OUTER_WIDTH_SIZE = dip2px(context,125.0f);
OUTER_HEIGHT_SIZE = dip2px(context,125.0f);
outerPaint = new Paint();
innerPaint = new Paint();
outerPaint.setColor(outerColor);
outerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
innerPaint.setColor(innerColor);
innerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
上面的OUTER_WIDTH_SIZE和OUTER_HEIGHT_SIZE分别是大圆在没有设置具体值下的默认大小,我们使用dip2px()方法将我们熟练掌握的dip转化为java逻辑唯一承认的px单位,具体实现:
public static int dip2px(Context context, float dpValue){
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue*scale +0.5f);
}
这样就做好了前期准备工作,由于上篇讲述过关于onMeasure和onDraw的相关理解和用法,这里就简单阐述,将这两块写在同一个部分。
测量绘制
测量时我们分别对三种模式下的尺寸进行不同的处理,分别是返回父View给的值加上padding值(EXACTLY),返回大圆的宽高(UNSPECIFIED)和返回大圆宽高与父View允许最大值之间的最小值(AT_MOST)。然后回调onSizeChanged()中取出实际宽高值,利用该值进行View绘制。onDraw中主要是确定了两个圆的半径(大圆半径为去除padding的宽高一半下四种情况的最小值,参照代码看这句话。小圆半径为大圆的一半)和绘制了两个圆。此外小圆的中心点我们现在onSizeChanged中进行了赋值,注意小圆中心点坐标值的改变是本例的关键,通过改变它来实现效果。这样我们就把View的显示区域和View的基本形状定义完毕。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
setMeasuredDimension(width,height);
}
private int measureWidth(int widthMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthVal = MeasureSpec.getSize(widthMeasureSpec);
//处理三种模式
if(widthMode==MeasureSpec.EXACTLY){
return widthVal+getPaddingLeft()+getPaddingRight();