您的位置:首页 > 移动开发 > Android开发

android custom view

2016-06-03 21:48 274 查看
  Android系统提供了很多原生控件,供我们在界面开发时使用。但是,有时需要实现的界面效果没有对应的原生控件可以使用,有时需要减少UI层级,以优化界面加载显示速度,这时,就需要自定义View。

  自定义View有3种实现方式:一、compound components;二、继承View或ViewGroup;三、继承已有的View或ViewGroup的子类。其中第一种实现方式没有减少UI层级,为了尽可能提高界面加载显示速度,不提供使用。

Compound Components
  这种方式实现自定义View,首先定义一个layout.xml文件,然后继承ViewGroup的已有子类,如LinearLayout或RelativeLayout,在构造函数中使用inflate()方法加载xml文件,查找到里面的子view,为查找到的view设置响应事件,为touch事件做出相应处理,最后在需要用到该自定义view的地方引用即可。

Custom View
  自定义View主要分为4个步骤:一、定义自定义属性值及获取自定义属性的值。二、measure。三、draw。四、响应事件。

  定义自定义属性:

  在/res/values/下新建attrs.xml文件,在文件中添加节点名为<declare-styleable>的元素,name值为自定义view的类名。

  自定义的属性以<attr name="hc:margin" format="color"/>的格式添加在<declare-styleable>中。

  如果可以,尽量使用framework中已经定义的属性,如果"android:text",这样可以提高性能。

  使用自定义属性:

  如果需要使用framework中未包含的自定义属性,需要在布局文件的根节点中添加自定义命令空间,添加方式在eclipse和studio中是不同的,如果使用的自定义属性都包含在framework中,则不需要声明自定义命名空间。在eclipse中,声明自定义命名空间的格式为:xmlns:app="http://schemas.android.com/apk/package_name",在studio中,声明格式为xmlns:app="http://schemas.android.com/apk/res-auto"。

  在布局文件中引用自定义view时,需要使用全名,如 <com.hc.customview.box.BoxLayout>。

  然后在自定义view中使用命名空间引用属性,并为它们设置值即可。

  获取自定义属性的值:

  实现自定义view的包含1个、2个、3个参数的构造函数,让它们分别调用后面的那个构造函数,最后在3个参数的构造函数中做初始化。

  在java代码中用new的方式获取自定义view的对象时,调用的是1个参数的构造函数。从xml布局文件中加载出自定义view的对象时,调用的是包含两个参数的构造函数,这时会有属性值传入。

  获取属性值时,首先从attrs中加载出属性值列表TypedArray,然后从TypedArray对象中根据属性id获取各属性值,最后,因为TypedArray耗用资源很多,所以使用完后,必须马上调用它的recycle()方法释放资源。

TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.BoxLayout, 0, defStyleAttr);

int strokeWidth = a.getDimensionPixelSize(R.styleable.BoxLayout_separatorWidth, 0);
int strokeColor = a.getColor(R.styleable.BoxLayout_separatorColor, 0);
int mColumnCount = a.getInteger(R.styleable.BoxLayout_numColumns, DEFAULT_COUNT);

a.recycle();
  获取到属性值后,应使用setXxx()方法,第一、将属性值保存到成员变量中,第二、更新自定义view中各组成部分的bounds,第三、调用invalidate(),重绘。setXxx()方法也可以被其它类调用,用于更新view的状态。

  构造函数

  在构造函数中,通常在获取到自定义属性值之后,初始化Paint对象。

  测量 ( onMeasure )

  在构造函数执行之后,布局和绘制之前,会执行测量。测量从根节点开始,然后测量子节点,依次类推,同级节点按照在xml文件中定义的顺序执行。

  同一个view的onMeasure()可能会执行多次,如线性布局添加了weight,如相对布局。

  view的onMeasure()方法由它的父布局调用,并传入宽高的MeasureSpec。每个MeasureSpec包含两个信息,高位表示specMode,低位表示specSize。specMode有3种值,EXACTLY,AT_MOST,NO_SPECIFIED,当为EXACTLY时,相应的宽高必须是父布局传入的值,也就是specSize,当为AT_MOST时,如果请求的宽高小于specSize,相应的宽高就是请求的值,如果请求的值大于specSize,相应的宽高就是specSize,当为NO_SPECIFIED时,相应的宽高就是请求的值。当view的宽高设置为固定值时,specMode为EXACTLY,specSize为设置的固定值。当view的宽高设置为wrap_content或match_parent时,specMode为AT_MOST,specSize为父布局能分配给本view的最大宽高。

  根据specMode和请求的宽高获取最终的宽高,可以使用 View.resolveSize(getDesiredWidth(), widthMeasureSpec) 方法。

  获取到view的宽高后,必须调用 setMeasuredDimension(widthSize, heightSize) ,告知父布局自己的宽高,因为测量其它布局的宽高时,会依赖本布局的宽高值。

  在viewGroup的情况下,除了要获取到本身的宽高外,还要指定子布局的宽高,设置子布局的宽高使用 measureChildren(blockSpec, blockSpec) 方法。其中传入的MeasureSpec值通过 MeasureSpec.makeMeasureSpec(blockDimension, MeasureSpec.EXACTLY) 方法生成,需要传入size和mode,size值根据本viewGroup的宽高值生成。

  布局 ( onLayout )

  继承ViewGroup实现自定义view时,需要实现onLayout()方法,在里面指定子布局的位置。依次遍历ViewGroup中的子布局,获取它们的left/top/right/bottom值,然后调用子布局的layout()方法,将left/top/right/bottom值分别设置上去。例如:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int row, col, left, top;
for (int i = 0; i < getChildCount(); i++) {
row = i / mColumnCount;
col = i % mColumnCount;
View child = getChildAt(i);
left = col * child.getMeasuredWidth();
top = row * child.getMeasuredHeight();

child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
}
}


  onSizeChanged()

  Android系统开始时不知道view的宽高,当测量完成后,系统知道view的宽高,此时,会调用onSizeChanged()方法。因此,可以在该方法中设置view中各组成部分的bounds,这样,就不必再在onDraw()中获取bounds,提高性能。

  onSizeChanged()执行后,系统会再次调用onMeasure()。

  绘制 ( draw )

  绘制图片时,可以先拿到图片的Drawable对象,然后在measure之后,设置Drawable对象的bounds,这样在onDraw()中调用Drawable对象的draw()方法,传入Canvas对象就可以直接绘制图片了。

  绘制文字时,可以先在measure后,生成一个StaticLayout对象,在生成时传入文本、Paint对象、textWidth等设置,这样StaticLayout就知道了该用什么样式绘制什么内容,然后再调用Canvas的translate(x, y)方法,设置绘制区域的左上顶点,这样StaticLayout就知道了该从哪个位置开始绘制。

  Canvas对象的save()方法可以保存当前canvas的状态信息(包括位置),restore()方法可以恢复前一次调用save()方法时canvas的状态。

  自定义View继承自ViewGroup时,onDraw()方法在子view绘制前绘制,所以可以绘制一些背景色。但是ViewGroup()的onDraw()方法默认不执行,如果需要执行,需要调用 setWillNotDraw(false) 方法。

  自定义View继承自ViewGroup时,dispatchDraw(canvas)方法会在子view的onDraw()方法执行后执行,因此在此绘制的图片会显示在子view上方。

  调用流程



继承自已有View或ViewGroup子类
  实现相应方法,处理相应事件。

参考及相关
  https://newcircle.com/s/post/1663/tutorial_enhancing_android_ui_with_custom_views_dave_smith_video

  https://github.com/songshanqingzi/CustomView
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android