声明:本文简述Android应用开发中,自定义组件的实现方式,参考和查阅部分资料,整理而成。
参考资料:
Android 手把手教您自定义ViewGroup:http://blog.csdn.net/lmj623565791/article/details/38339817
Android自定义控件:http://blog.163.com/ppy2790@126/blog/static/103242241201382210910473/
Android自定义控件开发入门:http://blog.csdn.net/sunmc1204953974/article/details/38456791
一、准备知识
1、View、ViewGroup
View是一个抽象的视图对象(虽然这个类不是抽象的),它定义了一个视图所需具有的属性和基本操作方法。
职责:1、根据测量模式和父容器(ViewGroup)给出的建议的宽和高,计算出自己的宽和高;2、在父容器(ViewGroup)为其指定的区域内绘制自己的形态。
ViewGroup本质上也是一个View,它相当于一个放置View的容器,这个容器的属性可以通过XML布局文件进行配置,比如:宽度(layout_width)、高度(layout_height)、对齐方式(layout_gravity)等。
职责:1、给容器内的childView(View)计算出建议的宽和高和测量模式(为什么只是建议呢,别忘了childView宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高);2、决定childView的位置。
这种灵活的View层次结构可以形成非常复杂的UI布局,开发者可据此设计、开发非常精致的UI界面。
2、ViewGroup和LayoutParams之间的关系
大家可以回忆一下,当在LinearLayout中写childView的时候,可以写layout_gravity,layout_weight属性;在RelativeLayout中的childView有layout_centerInParent属性,却没有layout_gravity,layout_weight,这是为什么呢?这是因为每个ViewGroup需要指定一个LayoutParams,用于确定支持childView支持哪些属性,比如LinearLayout指定LinearLayout.LayoutParams等。如果大家去看LinearLayout的源码,会发现其内部定义了LinearLayout.LayoutParams,在此类中,你可以发现weight和gravity的身影。
3、View的3种测量模式
上面提到了ViewGroup会为childView指定测量模式,下面简单介绍下三种测量模式:
EXACTLY=1:表示设置了精确的值,一般当childView设置其宽、高为精确值、match_parent时,ViewGroup会将其设置为EXACTLY;(父对象要求子对象必须严格按照它给定的值来约束自己)
AT_MOST=2:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;(子对象可以自行选择给定范围内的值)
UNSPECIFIED=0:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。(父对象没有强制要求子对象必须遵循哪些约束)
二、原理介绍
2.1、View结构原理
Android系统的视图结构的设计也采用了组合模式,即View作为所有图形的基类,Viewgroup对View继承扩展为视图容器类。2.2、View类的构造方法
View(
Context context)
Simple constructor to use when creating a view from code.
| |
View(
Context context,
AttributeSet attrs)
Constructor that is called when inflating a view from XML.
| |
View(
Context context,
AttributeSet attrs, int defStyle)
Perform inflation from XML and apply a class-specific base style.
|
2.3、给自定义View增加属性
2.3.1、在res/values 文件下定义一个attrs.xml 文件。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView">
<attr name="textColor" format="color" />
<attr name="textSize" format="dimension" />
</declare-styleable>
</resources>
2.3.2、在 MyView.java 代码编写如下,其中下面的构造方法是重点,我们获取定义的属性 R.sytleable.MyView_textColor, 获取方法中后面通常设定默认值( float textSize = a.getDimension(R.styleable.MyView_textSize, 36 ); ) , 防止我们在 xml 文件中没有定义.从而使用默认值!
MyView 就是定义在<declare-styleable name="MyView "></declare-styleable> 里的 名字,获取里面属性用 名字_ 属性 连接起来就可以.TypedArray 通常最后调用 .recycle() 方法,为了保持以后使用该属性一致性!
public MyView(Context context,AttributeSet attrs)
{
super(context,attrs);
mPaint = new Paint();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.MyView);
int textColor = a.getColor(R.styleable.MyView_textColor,
0XFFFFFFFF);
float textSize = a.getDimension(R.styleable.MyView_textSize, 36);
mPaint.setTextSize(textSize);
mPaint.setColor(textColor);
a.recycle();
}
MyView.java MyView控件全部代码如下:
package com.android.tutor;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.view.View;
public class MyView extends View {
private Paint mPaint;
private Context mContext;
private static final String mString = "Welcome to Mr Wei's blog";
public MyView(Context context) {
super(context);
mPaint = new Paint();
}
public MyView(Context context,AttributeSet attrs)
{
super(context,attrs);
mPaint = new Paint();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.MyView);
int textColor = a.getColor(R.styleable.MyView_textColor,
0XFFFFFFFF);
float textSize = a.getDimension(R.styleable.MyView_textSize, 36);
mPaint.setTextSize(textSize);
mPaint.setColor(textColor);
a.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
//设置填充
mPaint.setStyle(Style.FILL);
//画一个矩形,前俩个是矩形左上角坐标,后面俩个是右下角坐标
canvas.drawRect(new Rect(10, 10, 100, 100), mPaint);
mPaint.setColor(Color.BLUE);
//绘制文字
canvas.drawText(mString, 10, 110, mPaint);
}
}
2.3.3、将自定义的
MyView 加入布局
main.xml 文件中,并且使用自定义属性,自定义属性必须加上:
" xmlns:test ="http://schemas.android.com/apk/res/com.android.tutor" ,test是自定义属性的前缀, com.android.tutor 是包名.
main.xml 全部代码如下: <?xml
version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:test="http://schemas.android.com/apk/res/com.android.tutor"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<com.android.tutor.MyView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
test:textSize="20px"
test:textColor="#fff"
/>
</LinearLayout>
三、方法介绍
3.1、自定义控件的实现方式:
3.1.1、第一种:需要在原生控件的基本功能上进行扩展,这个时候只需要继承并对控件进行扩展。如,一种可以让TextView中的文字闪光的控件。
3.1.2、第二种:需要几个控件的组合功能,一般是自定义布局,可以用一个类继承一个布局,这个布局中包含多个控件。
3.1.3、第三种:以上两种不能解决,则独立绘制新的控件,直接从View或ViewGroup开始绘制。如,一种水波(地震波)报警动画。
3.1、开发自定义控件的步骤:
- 了解View的工作原理
- 编写继承自View的子类
- 为自定义View类增加属性
- 绘制控件
- 响应用户消息
- 自定义回调函数
3.2、自定义View的常用方法
- onFinishInflate() 回调方法,当应用从XML加载该组件并用它构建界面之后调用的方法
- onMeasure() 检测View组件及其子组件的大小时回调。
- onLayout() 当该组件需要分配其子组件的位置、大小时回调。View类中布局发生改变时会调用的方法,这个方法是所有View、ViewGroup及其派生类都具有的方法,重载该类可以在布局发生改变时作定制处理,这在实现一些特效时非常有用。
- onSizeChange() 当该组件的大小被改变时
- onDraw() 当组件将要绘制它的内容时回调。View类中用于重绘的方法,这个方法是所有View、ViewGroup及其派生类都具有的方法,也是Android UI绘制最重要的方法。开发者可重载该方法,并在重载的方法内部基于参数canvas绘制自己的各种图形、图像效果。
- onKeyDown 当按下某个键盘时
- onKeyUp 当松开某个键盘时
- onTrackballEvent 当发生轨迹球事件时
- onTouchEvent 当发生触屏事件时
- onWindowFocusChanged(boolean) 当该组件得到、失去焦点时
- onAtrrachedToWindow() 当把该组件放入到某个窗口时
- onDetachedFromWindow() 当把该组件从某个窗口上分离时触发的方法
- onWindowVisibilityChanged(int): 当包含该组件的窗口的可见性发生改变时触发的方法