一起来学习Android自定义控件
概述
Android已经为我们提供了大量的View供我们使用,但是可能有时候这些组件不能满足我们的需求,这时候就需要自定义控件了。自定义控件对于初学者总是感觉是一种复杂的技术。因为里面涉及到的知识点会比较多。但是任何复杂的技术后面都是一点点简单知识的积累。通过对自定义控件的学习去可以更深入的掌握android的相关知识点,所以学习android自定义控件是很有必要的。记得以前学习总是想着去先理解很多知识点,然后再来学着自定义控件,但是每次写自定义控件的时候总是不知道从哪里下手啊。后来在学习的过程中发现自己跟着去写一些简单的自定义控件,然后在这个过程中遇到了没有掌握的知识点再去学习。不仅自定义控件的能力有所提高。其它的知识也有了很好的巩固和认识。所以,今天写的是怎么去自定义一个控件。而不是里面涉及到的细化知识点。一个东西我们先知道怎么用,再去问为什么。
自定义控件需要考虑的点
根据Android Developers官网的介绍,自定义控件你需要以下的步骤。(根据你的需要,某些步骤可以省略)
1、创建View
2、处理View的布局
3、绘制View
4、与用户进行交互
5、优化已定义的View
上面列出的五项就是android官方给出的自定义控件的步骤。每个步骤里面又包括了很多细小的知识点。我们可以记住这五个点,并且了解每个点里包含的小知识点。再加上一些自定义控件的练习。不断的将这些知识熟练于心,相信我们每个人都能够定义出优秀的自定义控件。接下来我们开始对上面列出的5个要点进行细化解说
自定义控件5个要点详细说明
1、创建View
继承View
对Android有一些了解的朋友都知道,android为我们提供的很多View都是继承与View的。所以我们自定义的View当然也是继承于View,当然如果你要自定义的View拥有某些android已经提供的控件的功能,你可以直接继承于已经提供的控件。
我们在使用android提供的控件的时候,我们在.xml文件中编辑了一个控件,在运行的时候就能够看到和获得这个控件。我们自定义的控件当然也要支持配置和一些自定义属性,所以下面的构造方法就必须有了。这个构造方法允许我们在.xml文件中创建和编辑我们自定义控件的实例。
上面说了那么多其实就是下面一段代码。
class PieChart extends View {//继承View
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
}//为了支持.xml中进行创建和编辑
}
定义自定义属性
大部分情况我们的自定义View需要有更多的灵活性,比如我们在xml中指定了颜色大小等属性,在程序运行时候控件就能展示出相应的颜色和大小。所以我们需要自定义属性
自定义属性通常写在在res/values/attrs.xml文件中 下面是自定义属性的标准写法
这段代码声明了两个自定义属性,showText和labelPosition,它们都是属于styleable PieChart的,为了方便,一般styleable的name和我们自定义控件的类名一样。自定义控件定义好了之后就是使用了。
使用代码示例
获取自定义属性
在xml中设置了控件自定义属性,我们就需要拿到属性做一些事情。否则定义自定义属性就没有意义了。
固定的获取自定义属性代码如下
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.PieChart,
0, 0);
try {
mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
} finally {
a.recycle();
}
}
当我们在 xml中创建了一个view时,所有在xml中声明的属性都会被传入到view的构造方法中的AttributeSet类型的参数当中。
通过调用Context的obtainStyledAttributes()方法返回一个TypedArray对象。然后直接用TypedArray对象获取自定义属性的值。
由于TypedArray对象是共享的资源,所以在获取完值之后必须要调用recycle()方法来回收。
添加设置属性事件
在xml中指定的自定义属性只有在view被初始化的时候能够获取到,有时候我们可能在运行时做一些操作,这种情况就需要我们为自定义属性设置getter和setter方法,以下代码展示了自定义控件暴露的set 和get方法
public boolean isShowText() {
return mShowText;
}
public void setShowText(boolean showText) {
mShowText = showText;
invalidate();
requestLayout();
}
重点看setShowText方法,在为mShowText赋值之后,调用了invalidate()和requestLayout()方法,我们自定义控件的属性发生改变之后,控件的样子也可能发生改变,在这种情况下就需要调用invalidate()方法让系统去调用view的onDraw()重新绘制。同样的,控件属性的改变可能导致控件所占的大小和形状发生改变,所以我们需要调用requestLayout()来请求测量获取一个新的布局位置。
2、处理View的布局.
测量
一个View是在展示时总是有它的宽和高,测量View就是为了能够让自定义的控件能够根据各种不同的情况以合适的宽高去展示。提到测量就必须要提到onMeasure方法了。onMeasure方法是一个view确定它的宽高的地方。
<CODE class=" hljs java">@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { }</CODE>
onMeasure方法里有两个重要的参数, widthMeasureSpec, heightMeasureSpec。在这里你只需要记住它们包含了两个信息:mode和size
我们可以通过以下代码拿到mode和size
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
那么获取到的mode和size又代表了什么呢?
mode代表了我们当前控件的父控件告诉我们控件,你应该按怎样的方式来布局。
mode有三个可选值:EXACTLY, AT_MOST, UNSPECIFIED。它们的含义是:
EXACTLY:父控件告诉我们子控件了一个确定的大小,你就按这个大小来布局。比如我们指定了确定的dp值和macth_parent的情况。
AT_MOST:当前控件不能超过一个固定的最大值,一般是wrap_content的情况。
UNSPECIFIED:当前控件没有限制,要多大就有多大,这种情况很少出现。
size其实就是父布局传递过来的一个大小,父布局希望当前布局的大小。
下面是一个重写onMeasure的固定伪代码写法:
if mode is EXACTLY{
父布局已经告诉了我们当前布局应该是多大的宽高, 所以我们直接返回从measureSpec中获取到的size
}else{
计算出希望的desiredSize
if mode is AT_MOST
返回desireSize和specSize当中的最小值
else:
返回计算出的desireSize
}
上面的代码虽然基本都是固定的,但是需要写的步骤还是有点多,如果你不想自己写,你也可以用android为我们提供的工具方法:resolveSizeAndState,该方法需要传入两个参数:我们测量的大小和父布局希望的大小,它会返回根据各种情况返回正确的大小。这样我们就可以不需要实现上面的模版,只需要计算出想要的大小然后调用resolveSizeAndState。之后在做自定义View的时候我会展示用这个方法来确定view的大小。
计算出height和width之后在onMeasure中别忘记调用setMeasuredDimension()方法。否则会出现运行时异常。
计算一些自定义控件需要的值 onSizeChange()
onSizeChange() 方法在view第一次被指定了大小值、或者view的大小发生改变时会被调用。所以一般用来计算一些位置和与view的size有关的值。
3、绘制View(Draw)
一旦自定义控件被创建并且测量代码写好之后,接下来你就可以实现onDraw()来绘制View了,onDraw方法包含了一个Canvas叫做画布的参数,onDraw()简单来说就两点:
Canvas决定要去画什么
Paint决定怎么画
比如,Canvas提供了画线方法,Paint就来决定线的颜色。Canvas提供了画矩形,Paint又可以决定让矩形是空心还是实心。
在onDraw方法