1,自定义View的步骤:
首先要搞明白你的自定义View是用作布局还是用作控件,需要有什么属性,此处是以控件为例:
1 定义自定义属性
在res/values/ 下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="titleText" format="string" />
<attr name="titleTextColor" format="color" />
<attr name="titleTextSize" format="dimension" />
<declare-styleable name="CustomTitleView">
<attr name="titleText" />
<attr name="titleTextColor" />
<attr name="titleTextSize" />
</declare-styleable>
</resources>
有:reference 引用;color 颜色;boolean 布尔值;dimension 尺寸值;float 浮点值;integer 整型值;string 字符串;enum 枚举值;fraction,flag;取值类型
2 在使用该自定义View的xml中为相应的属性声明属性值,我们知道在xml中为属性赋值有几种不同的方式,这三个方式适合构造器里的参数实现关联的,看下面介绍,这个例子中使用第一种:
直接在layout中使用属性:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.customview01.view.CustomTitleView
android:layout_width="200dp"
android:layout_height="100dp"
custom:titleText="3712"
custom:titleTextColor="#ff0000"
custom:titleTextSize="40sp" />
</RelativeLayout>
Application和Activity可以指定theme,可以在theme中指定在当前Application或Activity中属性的默认值
3 创建一个类,或继承View完全自定义或继承View的派生子类
必须提供一个能够获取Context和作为属性的AttributeSet对象的构造函数,获取属性,当view从XML布局中创建了之后,XML标签中所有的属性都从资源包中读取出来并作为一个AttributeSet传递给view的构造函数。
重载父类的构造方法,View有三个构造方法,。
如果在Code中实例化一个View会调用第一个构造函数;如果在xml中定义(即自定义布局)会调用第二个构造函数,而第三个构造函数是不调用的;第三个构造函数要由View(我们自定义的或系统预定义的View,如此处的CustomTextView和Button)显式调用,如下面的代码片段。所以,自定义View时,通常自定义布局使用第二个构造方法,自定义小部件使用到第三个构造方法。
public Button(Context context) {
this(context, null);
}
public Button(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Button(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
4 获取自定义属性
[java] view plaincopyprint?
public View(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
[java] view plaincopyprint?
public View(Context context, AttributeSet attrs, int defStyle) {
this(context);
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,
defStyle, 0);
public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
public CustomTextView(Context context) {
super(context);
}
public CustomTextView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.CustomizeStyle);
}
public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Customize, defStyle, R.style.DefaultCustomizeStyle);
以下是TypeArray类里的方法,这见名知意:
当在构造方法中获取到这些设置好的属性值时,取出其值,就可以在代码中进行处理了。代码示例:
- public CustomTitleView(Context context, AttributeSet attrs, int defStyle)
- {
- super(context, attrs, defStyle);
- /**
- * 获得我们所定义的自定义样式属性
- */
- TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
- int n = a.getIndexCount();
- for (int i = 0; i < n; i++)
- {
- int attr = a.getIndex(i);
- switch (attr)
- {
- case R.styleable.CustomTitleView_titleText:
- mTitleText = a.getString(attr);
- break;
- case R.styleable.CustomTitleView_titleTextColor:
- // 默认颜色设置为黑色
- mTitleTextColor = a.getColor(attr, Color.BLACK);
- break;
- case R.styleable.CustomTitleView_titleTextSize:
- // 默认设置为16sp,TypeValue也可以把sp转化为px
- mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
- break;
- }
- }
- a.recycle();
5 自定义绘制
主要是重写onDraw,onMesure
自定义View,分为两种,一种是自定义控件(继承View类),另一种是自定义布局容器(继承ViewGroup)。如果是自定义控件,则一般需要重载两个方法,一个是onMeasure(),用来测量控件尺寸,另一个是onDraw(),用来绘制控件的UI。而自定义布局容器,则一般需要实现/重载三个方法,一个是onMeasure(),也是用来测量尺寸;一个是onLayout(),用来布局子控件;还有一个是dispatchDraw(),用来绘制UI。Android开发实践:自定义ViewGroup的onLayout()分析
关于onDraw()和dispatchDraw():
View组件的绘制会调用draw(Canvas canvas)方法,draw过程中主要是先画Drawable背景,对 drawable调用setBounds(),然后是draw(Canvas c)方法。有点注意的是背景drawable的实际大小会影响view组件的大小,drawable的实际大小通过 getIntrinsicWidth()和getIntrinsicHeight()获取,当背景比较大时view组件大小等于背景drawable的大 小。
画完背景后,draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法,dispatchDraw()主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。值得注意的是ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法, 而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法,或者自定制一个Drawable,重写它的draw(Canvas c)和 getIntrinsicWidth(),getIntrinsicHeight()方法,然后设为背景。Android 中 更新视图的函数ondraw() 和dispatchdraw()的区别
Android给我们提供了一个onDraw(Canvas canvas)方法来让我们绘制自己想要的东西,也提供了画画的Paint和Canvas,一个是画笔而一个是画布。在onDraw方法中,画布Canvas作为参数被传递进来,Paint需要我们new出来。这里要注意:
1,初始化画笔的操作不要放在draw方法里,因为draw或layout的过程有可能是一个频繁重复执行的过程,我们知道new是需要分配内存空间的,如果在一个频繁重复的过程中去大量地new对象内存爆不爆我不知道,但是浪费内存那是肯定的!所以Android不建议我们在这两个过程中去实例化对象;
2,在Android中提供了一个叫invalidate()的方法来让我们重绘我们的View,而有时候我们需要在非UI线程更新UI,但Android中非UI线程是不能直接更新UI的!Android给我们提供了一个更便捷的方法:postInvalidate();用它替代我们原来的invalidate()即可:
例,这时在UI Activity中:
public class MainActivity extends Activity {
private CustomView mCustomView;// 我们的自定义View
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取控件
mCustomView = (CustomView) findViewById(R.id.main_cv);
/*
* 开线程
*/
new Thread(mCustomView).start();
}
}
在自定义view中:
@Override
public void run() {
/*
* 确保线程不断执行不断刷新界面
*/
while (true) {
try {
/*
* 如果半径小于200则自加否则大于200后重置半径值以实现往复
*/
if (radiu <= 200) {
radiu += 10;
// 刷新View
postInvalidate();
} else {
radiu = 0;
}
// 每执行一次暂停40毫秒
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这里来个传送门,关于绘画讲的碉堡天,自定义控件其实很简单1/12
这是自定义View的典型完整步骤,不是说自定义View都这么做的,当我们自定义View时,根据需要选择上面的步骤。比如有些自定义View不用定义属性,直接使用继承父类的属性就可以了;也比如我们自定义个布局,只是为了比父类布局多个特性等。
2,良好的自定义View
易用,标准,开放。
一个设计良好的自定义view和其他设计良好的类很像。封装了某个具有易用性接口的功能组合,这些功能能够有效地使用CPU和内存,并且十分开放的。但是,除了开始一个设计良好的类之外,一个自定义view应该:
l 符合安卓标准
l 提供能够在Android XML布局中工作的自定义样式属性
l 发送可访问的事件
l 与多个Android平台兼容。
Android框架提供了一套基本的类和XML标签来帮您创建一个新的,满足这些要求的view。忘记提供属性和事件是很容易的,尤其是当您是这个自定义view的唯一用户时。请花一些时间来仔细的定义您view的接口以减少未来维护时所耗费的时间。一个应该遵从的准则是:暴露您view中所有影响可见外观的属性或者行为。
3,优化
3.1 降低刷新频率
为了提高view的运行速度,减少来自于频繁调用的程序的不必要的代码。从onDraw()方法开始调用,这会给你带来最好的回报。
特别地,在onDraw()方法中你应该减少冗余代码,冗余代码会带来使你view不连贯的垃圾回收。初始化的冗余对象,或者动画之间的,在动画运行时,永远都不会有所贡献。
加之为了使onDraw()方法更有依赖性,你应该尽可能的不要频繁的调用它。大部分时候调用 onDraw()方法就是调用invalidate()的结果,所以减少不必要的调用invalidate()方法。
有可能的,调用四种参数不同类型的invalidate(),而不是调用无参的版本。无参变量需要刷新整个view,而四种参数类型的变量只需刷新指定部分的view.这种高效的调用更加接近需求,也能减少落在矩形屏幕外的不必 要刷新的页面。
3.2 善用硬件加速
作为Android3.0,Android2D图表系统可以通过大部分新的Android装置自带GPU(图表处理单元)来增加,对于许多应用程序 来说,GPU硬件加速度能带来巨大的性能增加,但是对于每一个应用来讲,并不都是正确的选择。Android框架层更好地为你提供了控制应用程序部分硬件 是否增加的能力。
怎样在你的应用,活动,或者窗体级别中使用加速度类,请查阅Android开发者指南中的Hardware Acceleration类。注意到在开发者指南中的附加说明,你必须在你的AndroidManifest.xml 文件中的<uses-sdk android:targetSdkVersion="11"/>中将应用目标API设置到11或者更高的级别。
一旦你使用硬件加速度类,你可能没有看到性能的增长,手机GPUs非常擅长某些任务,例如测量,翻转,和平移位图类的图片。特别地,他们不擅长其他的任务,例如画直线和曲线。为了利用GPU加速度类,你应该增加GPU擅长的操作数量,和减少GPU不擅长的操作数量。
以上参考 Android 自定义View步骤
4,坑:
① 用xml定义Layout时,Root element 最好使用merge
当我们需要继承一个布局比较复杂的ViewGroup(比较多的是LinearLayout、RelativeLayout)时,通常会用xml来写布局,然后在自定义的View类中inflate这个定义了layout的xml文件。
首先新建一个名为 MyLayout 的 class 文件,在 init 方法中解析稍后定义的xml文件。然后新建一个取名为my_layout的布局文件, 这时坑来了:正确的做法是把 Root element 设置成merge;用 LinearLayout 做 Root element 后,布局多了一个层级,成了影响性能的一个因素。
② 重载子类构造函数时要弄清楚父类做了哪些操作
例如,Button类在构造函数中使用了一个defStyleAttr, 而如果this(context, attrs,0);写法会忽略掉这个defStyleAttr - com.android.internal.R.attr.buttonStyle
如果不确定父类构造方法,就老老实实地写成supper种形式,例:
public class MyButton extends Button {
public MyButton(Context context) {
super(context);
init();
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
}
另外参考了:
Android 自定义View (一) - Hongyang Android自定义控件——自定义属性 Android中自定义样式与View的构造函数中的第三个参数defStyle的意义