虽然Android为我们提供了很多的内置控件,但是有时候我们的独特的业务需求可能这些内置控件仍然无法满足,那么此时,就需要我们自定义控件。这系列文章将向你展示如何创建一个功能强大且可重复的View控件。
目录:
- 创建自定义View Class Creating a View Class
- 自定义绘图 Custom Drawing
- 控件交互 Making the View Interactive
- 优化View控件 Optimizing the View
自定义View
一个良好的自定义View应该能和Android内置的View控件一样,具有良好的表现:具有自己的属性能与用户进行交互占据有效的内存空间。因此一个自定义的View的Class应该具备以下几个特征:
- 继承自View或者是View的子类,在Android中所有的View控件比如:Button,TextView都是View的子类。
- 符合Android的标准
- 通过xml文件提供自定义的属性
- 能与用户交互,能够响应事件,传递事件
- 兼容Android多个平台和版本
有了以上的标准,那么接下来就学习如何构造一个功能完整的自定义View吧:
大体上分为以下若干步骤:
- 继承一个View或者View子类
- 在res/values/attrs.xml文件中自定义属性
- 应用我们的自定义属性
- 添加属性和事件
- 供外界调用
- 在Android的所有View体系中,所有的控件都是继承自View的,所以我们自定义控件的时候也应该是继承View或者是View的子类,比如Button,TextView。需要注意的是:大部分时候,我们自定义View是为了想在布局文件中xml调用,此时,我们的自定义View需要覆盖如下的构造方法来能在xml中被实例化,否则会报错。
<pre>
class PieChart extends View {
public PieChart(Context context, AttributeSet attrs) {//必须有此构造函数才能在xml文件中被实例化
super(context, attrs); //调用父类的构造函数
}
}
</pre>
另外,我们之所以自定义View大多数是因为想实现业务需求的View,此时基本是需要在重写onDraw()方法来绘制我们的View。
<pre>
@Override
protected void onDraw(Canvas canvas) { //自定义View中都会重写此onDraw()方法
super.onDraw(canvas); //调用父类的onDraw();
// Draw the shadow
canvas.drawOval(mShadowBounds, mShadowPaint);
// Draw the label text
if (getShowText()) {
canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);
}
// If the API level is less than 11, we can't rely on the view animation system to
// do the scrolling animation. Need to tick it here and call postInvalidate() until the scrolling is done.
if (Build.VERSION.SDK_INT < 11) {
tickScrollAnimation();
if (!mScroller.isFinished()) {
postInvalidate();
}
}
}
</pre>
自定义属性
自定义属性后就能像Android内置的View控件一样通过属性标签来控制,我们自定义属性的时候需要在res/values/attrs.xml文件下以<declare-styleable>为根标签属性名和类型以键值对的方式存储。
<pre>
<resources>
<declare-styleable name="PieChart">
<attr name="showText" format="boolean" />
<attr name="labelPosition" format="enum">
<enum name="left" value="0"/>
<enum name="right" value="1"/>
</attr>
</declare-styleable>
</resources>
</pre>
需要注意的地方是:我们在布局文件中使用自定义属性的时候需要加上命名空间,命名空间可以加在根标签也可以加在自定义View标签内,一般直接加在根标签里。命名空间的格式如下:
http://schemas.android.com/apk/res/[yourpackagename],比如:
xmlns:custom="http://schemas.android.com/apk/res/com.example.android.customviews"
还有特殊的情况:在你自定义的View中有一个内部的自定义innerView,如果希望在xml文件中使用innerView的属性,比如PieChart内有个PieView的内部类,那么应该这样:
com.example.customviews.charting.PieChart$PieView。在xml引用自定义View布局需要使用全名,代码如下:
<pre>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews"> //自定义属性命名空间
<com.example.customviews.charting.PieChart
custom:showText="true"
custom:labelPosition="left"
/>
</LinearLayout>
</pre>在代码中应用我们自定义的属性,使xml中的属性能够产生效果
当一个view从xml布局中创建后,其使用的所有属性都会读取并传递到view 构造器函数的AttributeSet中。但是倘若直接使用AttributeSet会有一些缺点比如:styles不能生效,引用的资源不能解析。这时,我们使用另外一种方法会好很多,使用[obtainStyledAttributes()](http://developer.android.com/reference/android/content/res/Resources.Theme.html#obtainStyledAttributes(android.util.AttributeSet, int[], int, int))将在xml定义的属性封装到 TypedArray中去。代码如下:
<pre>
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(); //释放资源
}
}
</pre>添加属性事件
添加属性事件暴露方法,使view可以动态改变
<pre>
public boolean isShowText() {
return mShowText;
}
public void setShowText(boolean showText) {
mShowText = showText;
invalidate();//重绘
requestLayout();
}
</pre>
注意:invalidate()和requestLayout
requestLayout:当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view重新调用他的onMeasure onLayout来对重新设置自己位置。
特别的当view的layoutparameter发生改变,并且它的值还没能应用到view上,这时候适合调用这个方法。invalidate:View本身调用迫使view重画。
另外:一个好的自定义View应该暴露接口,提供对业务有用的部分监听机制供自己调用。比如:OnCurrentItemChanged(),onColorChanged(),onSizeChanged().
小结:
- 对于自定义View需要注意的大概就是这些,但是在实际自己写的时候仍然会有很多的问题,需要多实践,多学习别人的代码,再结合自己的业务区自定义。当然如果有第三方的控件可以满足我们的业务需求,我们亦可以引用。
Google给出的一个完整的自定义view的例子:点击下载Customview.zip