Android自定义View博客阅读总结



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>

我们定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型:
有:reference     引用;color            颜色;boolean       布尔值;dimension   尺寸值;float            浮点值;integer        整型值;string          字符串;enum          枚举值;fraction,flag;取值类型

2 在使用该自定义View的xml中为相应的属性声明属性值,我们知道在xml中为属性赋值有几种不同的方式,这三个方式适合构造器里的参数实现关联的,看下面介绍,这个例子中使用第一种:

直接在layout中使用属性:

布局XML文件中可以像内建属性一样使用它们。唯一不同是自定义属性属于不同的命名空间。
http://schemas.android.com/apk/res/[你的自定义View所在的包路径]

  
  
<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>
 
设置style并在style中设置属性:

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 获取自定义属性

AttributeSet是对自定义的属性文件attrs.xml文件解析后的结果,封装为AttributeSet对象。存储的都是原始数据,但对数据进行了简单的加工。
由此构造器帮我们返回了布局文件XML的解析结果,拿到这个结果,我们该怎么做呢?接下来,我们来看看View类对于这个是怎么处理的:
    
    
[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);

于是,找到一个跟属性很相关的类TypeArray,那么接下来,我在自定义控件的构造方法上也获取一下TypeArray这个类:
翻看一下TypeArray的源码会发现,TypeArray是不继承任何类(除了Object)的,也就是说,TypeArray相当于一个工具类,通过context.obtainStyledAttributes方法,将AttributeSet和属性的类型传递进去,比如AttributeSet相当于原材料,属性类型相当于图纸,context.obtainStyledAttributes相当于加工厂加工成所对象的属性,封装到TypeArray这个类里。 然后由该TypeArray来对属性进行设置
obtainStyledAttributes方法有四个,我们最常用的是有两个参数的obtainStyledAttributes( AttributeSet set , int [] attrs),其参数直接styleable中获得
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.MyView);
调用结束后务必调用recycle()方法,否则这次的设定会对下次的使用造成影响:a.recycle();    
obtainStyledAtributes
  我们要获取的属性值都是通过这个函数返回的TypedArray获得的,这是官方说明:obtainStyledAttributes,以下是函数原型:
     
     
public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
 参数:
    set:属性值的集合
    attrs:我们要获取的属性的资源ID的一个数组,如同ContextProvider中请求数据库时的Projection数组,就是从一堆属性中我们希望查询什么属性的值
    defStyleAttr:这个是当前Theme中的一个attribute,是指向style的一个引用,当在layout xml中和style中都没有为View指定属性时,会从Theme中这个attribute指向的Style中查找相应的属性值,这就是defStyle的意思,如果没有指定属性值,就用这个值,所以是默认值,但这个attribute要在Theme中指定,且是指向一个Style的引用,如果这个参数传入0表示不向Theme中搜索默认值
    defStyleRes:这个也是指向一个Style的资源ID,但是仅在defStyleAttr为0或defStyleAttr不为0但Theme中没有为defStyleAttr属性赋值时起作用
   假如声明属性的三种XML方式都用了,这是此时的代码, CustomizeStyle是在Style.xml中的设置的Style,同理 DefaultCustomizeStyle
     
     
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);

  对于一个属性可以在多个地方指定它的值,如XML直接定义,style,Theme,而这些位置定义的值有一个优先级,按优先级从高到低依次是:
 
        1. 直接在XML中定义>style定义>由defStyleAttr定义的值>defStyleRes指定的默认值>直接在Theme中指定的值
        2. defStyleAttr(即defStyle)不为0且在当前Theme中可以找到这个attribute的定义时,defStyleRes不起作用,所以attr_four虽然在defStyleRes(DefaultCustomizeStyle)中定义了,但取到的值仍为null。

以下是TypeArray类里的方法,这见名知意:


当在构造方法中获取到这些设置好的属性值时,取出其值,就可以在代码中进行处理了。代码示例:

  1. public CustomTitleView(Context context, AttributeSet attrs, int defStyle)  
  2.     {  
  3.         super(context, attrs, defStyle);  
  4.         /** 
  5.          * 获得我们所定义的自定义样式属性 
  6.          */  
  7.         TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);  
  8.         int n = a.getIndexCount();  
  9.         for (int i = 0; i < n; i++)  
  10.         {  
  11.             int attr = a.getIndex(i);  
  12.             switch (attr)  
  13.             {  
  14.             case R.styleable.CustomTitleView_titleText:  
  15.                 mTitleText = a.getString(attr);  
  16.                 break;  
  17.             case R.styleable.CustomTitleView_titleTextColor:  
  18.                 // 默认颜色设置为黑色  
  19.                 mTitleTextColor = a.getColor(attr, Color.BLACK);  
  20.                 break;  
  21.             case R.styleable.CustomTitleView_titleTextSize:  
  22.                 // 默认设置为16sp,TypeValue也可以把sp转化为px  
  23.                 mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(  
  24.                         TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));  
  25.                 break;  
  26.   
  27.             }  
  28.   
  29.         }  
  30.         a.recycle();  
  31.  

5 自定义绘制

主要是重写onDraw,onMesure

自定义View,分为两种,一种是自定义控件(继承View类),另一种是自定义布局容器(继承ViewGroup)。如果是自定义控件,则一般需要重载两个方法,一个是onMeasure(),用来测量控件尺寸,另一个是onDraw(),用来绘制控件的UI。而自定义布局容器,则一般需要实现/重载三个方法,一个是onMeasure(),也是用来测量尺寸;一个是onLayout(),用来布局子控件;还有一个是dispatchDraw(),用来绘制UIAndroid开发实践:自定义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();
}
}
}
      3,onDraw()不提供3d图形api的支持。如果你需要3d图形支持,必须继承SurfaceView而不是View,并且通过单独的线程画图。

这里来个传送门,关于绘画讲的碉堡天,自定义控件其实很简单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

另外参考了:

Android 自定义View (一) - Hongyang  Android自定义控件——自定义属性  Android中自定义样式与View的构造函数中的第三个参数defStyle的意义
注,这是为阅读了大量博客后,对他们的综合了一下,代码包括文字都是东拼西凑,把我当初迷惑的知识点清算一下,也又理清一次自定义思绪。算是我最自定义View的一次总结笔记。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值