声明,翻译自谷歌官方文档:http://developer.android.com/training/custom-views/create-view.html
一、创建View类
一个好的自定义view应该遵循以下规则:
- 符合android标准
- 提供自定义的 styleable attributes 以便在XML layouts中试用。
- 设计辅助功能。(这个是用来支持无障碍阅读的)
- 兼容android的多平台。
Context 和AttributeSet 参数的构造方法。 这个构造函数允许 布局编辑器 来创建和编辑 你的view。
class PieChart extends View { public PieChart(Context context, AttributeSet attrs) { super(context, attrs); } }
- 在
res/values/attrs.xml资源文件中添加 <declare-styleable> 来声明自定属性。 - 在XML布局中指定 属性的值。
- 在运行时解析 指定的属性值。
- 根据解析的数据值来初始化view。
res/values/attrs.xml
资源文件中声明<declare-styleable> 标签,直接看例子:
<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>
<?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>
xmlns:android="http://schemas.android.com/apk/res/android"这个命名空间,这个是声明 android framework中的view需要的属性的命名空间,而自定义的属性命名空间需要 http://schemas.android.com/apk/res/[your package name] 这样申请。申请完后就可以通过 命名空间 名称 来调用了 。
- 在AttributeSet 的属性值不会自动解析。
- 主题Styles 不会自动申请。
AttributeSet 给 obtainStyledAttributes() 。这个方法会返回一个用来提取已经被样式化的属性值集合 TypedArray 。
<declare-styleable> 值封装到 TypedArray 中,而你需要从TypedArray 中提取需要的属性值,看例子:
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(); } }注意:
TypedArray
是系统共享资源,所以必须在用完后 回收掉!
public boolean isShowText() { return mShowText; } public void setShowText(boolean showText) { mShowText = showText; invalidate(); requestLayout(); }注意: setShowText 调用了
invalidate() 和
requestLayout() 方法。这两个方法的调用是非常重要的能够确保view的可靠性。
invalidate() 让系统重新绘制view 、
requestLayout() 通知系统重新布局view(例如view的尺寸,形状改变等)。
PieChart 声明一个自定义的事件回调OnCurrentItemChanged 来通知监听者 用户旋转了饼图。
- 使用Android的标签输入字段android:contentDescription 。
- 在适当的时候通过调用
sendAccessibilityEvent()发送辅助事件。 - 支持设备控制器,例如 轨迹球等。
onDraw() 方法。该方法的参数是Canvas 类,让view可以来绘制自己。Canvas 类定义了绘制文本、线条、位图和许多其它基本图形的方法。你可以在onDraw() 方法中创建自定义的 UI。
Paint 类。
PieChart 例子在init 方法(在构造函数中调用)中声明:
private void init() { mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTextPaint.setColor(mTextColor); if (mTextHeight == 0) { mTextHeight = mTextPaint.getTextSize(); } else { mTextPaint.setTextSize(mTextHeight); } mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPiePaint.setStyle(Paint.Style.FILL); mPiePaint.setTextSize(mTextHeight); mShadowPaint = new Paint(0); mShadowPaint.setColor(0xff101010); mShadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL)); ...提前创建对象是一个重要的优化。Views 会非常频繁的重绘,并且许多对象都需要昂贵的初始化。在 onDraw() 方法中创建绘制对象会显著的降低性能,造成UI卡顿。
PieChart 例子中,在onSizeChanged() 方法中计算了 饼图的边框、文本标签 和 其它视觉元素的 相对位置。
// Account for padding float xpad = (float)(getPaddingLeft() + getPaddingRight()); float ypad = (float)(getPaddingTop() + getPaddingBottom()); // Account for the label if (mShowText) xpad += mTextWidth; float ww = (float)w - xpad; float hh = (float)h - ypad; // Figure out how big we can make the pie. float diameter = Math.min(ww, hh);如果需要更好的控制view的布局参数,重写 onMeasure() 方法。这个方法 的参数是 View.MeasureSpec 它会描述view的 父容器分配给view的尺寸,和分配的尺寸是硬性的最大值还是只是一个建议。为了优化,这些值都会压缩成整数,可以使用 View.MeasureSpec 的静态方法来解压出存储的每个整数信息。
onMeasure() 方法的例子。在实现过程中,PieChart 视图使它的区域足够大来使饼图能够放的下它的标签。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Try for a width based on our minimum int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); int w = resolveSizeAndState(minw, widthMeasureSpec, 1); // Whatever the width ends up being, ask for a height that would let the pie // get as big as it can int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop(); int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0); setMeasuredDimension(w, h); }在代码中有三个地方需要注意:
- 考虑到view的 padding值。正如前面提到的。
- 辅助方法
resolveSizeAndState()用来创建view最终的宽度和高度。这个辅助方法通过比较view期望的大小和onMeasure() 传递过来的规范值 返回一个适当的View.MeasureSpec 值。 - onMeasure() 方法没有返回值。该方法通过调用setMeasuredDimension() 来传递结果。在onMeasure() 方法中必须调用setMeasuredDimension() ,否则会抛出 runtime exception。
3412



被折叠的 条评论
为什么被折叠?



