您的位置:首页 > 运维架构 > 网站架构

Android群英传之Android控件架构与自定义控件

2016-07-23 00:17 513 查看

1、Android控件架构

ViewGroup可以包含多个View,形成控件树。上层控件负责下层子控件的测量与绘制。findViewById是在控件树中进行深度优先搜索。ViewGroup实现了ViewParent接口,Viewparent定义了一个控件作为父控件的职责,负责子布局与父布局的交互,例如requestLayout。

UI界面架构图



每个Activity都有一个Window对象,一般由PhoneWindow实现,各种监听事件都通过WindowManagerService接收,PhoneWindow将DecorView作为整个应用窗口的根View。DecorView分为两部分:TitleView和ContentView。

为什么requestWindowFeature方法一定要在setContentView之前才能生效?

因为一般的视图树会默认基本分为两部分,上面TitleBar下面Content,在requestWindowFeature(Window.Feature_NO_TITLE)后,DecorView只剩Content。而在setContentView后,WindowManagerService会回调onResume,把整个DecorView添加到PhoneWindow,让其显示。若在setContentView之后再设置requestWindowFeature,因为DecorView已经被添加了,所以再改变也没有用了。

2、View的测量

即使我们不看Android的View源码,对于一个手机上的视图,我们也应该想到,它应该是先确定大小,再确定它的位置,最后进行绘制。

核心类:MeasureSpec

核心方法:MeasureSpec.getMode、MeasureSpec.getSize

MeasureSpec是一个32位的int值,高2位是测量模式,低30位是测量大小。

测量模式有三种

1. EXACTLY:精确值模式,属性设置为具体数值或match_parent时,使用此模式

2. AT_MOST:最大值模式,属性设置为wrap_content时,使用此模式

3. UNSPECIFIED:不指定大小测量模式,通常在绘制自定义View时才会用到

View类默认的onMeasure方法只支持EXACTLY模式,想让自定义View支持wrap_content属性,必须重写onMeasure方法来指定wrap_content时的大小

重写onMeasure后,最终的的工作就是把测量后的宽高值作为参数设置给setMeasuredDimension方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//计算宽和高
//模板代码...
widthMeasureSpec = measureWidth(widthMeasureSpec);
heightMeasureSpec = measureHeight(heightMeasureSpec);
setMeasuredDimension(widthMeasureSpec,heightMeasureSpec);
}


计算宽度值的模板代码(计算高度同理)

private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
//未定义模式时的大小
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}


3、View的绘制

重写View的onDraw(Canvas canvas),在画布上绘图即可

通常情况下,为啥Canvas对象的创建要传入参数Bitmap?(好吧,通常情况?我为啥不知道)

穿进去的Bitmap与通过Bitmap创建的Canvas是紧紧联系在一起的,这个Bitmap用来存储所有绘制在Canvas上的像素信息,当使用Bitmap创建Canvas后,后面调用的所有Canvas.drawXXX方法都发生在这个Bitmap上。

还是不太明白,绘制内容都放在bitmap上而不绘制在画布上,貌似有利于重用bitmap在多个画布上?

4、ViewGroup的测量

ViewGroup在测量时遍历所有的子View,调用子View的measure方法来返回每一个子view的大小。

测量完毕后,就把子View放到合适的位置,就是Layout的过程。

5、ViewGroup的绘制

除非要指定ViewGroup的背景,否则ViewGroup的onDraw不会被调用,但是ViewGroup会使用dispatchDraw来遍历子View,调用他们的绘制方法。

6、自定义View

有三种自定义View的方式

1)对现有控件进行拓展

对现有控件进行拓展的代码结构:

@Override
protected void onDraw(Canvas canvas) {
//在回调父类方法之前实现自己的逻辑,对TextView来说就是在绘制文本之前
super.onDraw(canvas);
//在回调父类方法之后实现自己的逻辑,对TextView来说就是在绘制文本之后
}


书中对TextView进行拓展的例子

private void initView() {
mPaint1 = new Paint();
mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
mPaint1.setStyle(Paint.Style.FILL);
mPaint2 = new Paint();
mPaint2.setColor(Color.YELLOW);
mPaint2.setStyle(Paint.Style.FILL);
}

@Override
protected void onDraw(Canvas canvas) {
// 绘制外层矩形
canvas.drawRect(
0,
0,
getMeasuredWidth(),
getMeasuredHeight(),
mPaint1);
// 绘制内层矩形
canvas.drawRect(
10,
10,
getMeasuredWidth() - 10,
getMeasuredHeight() - 10,
mPaint2);
canvas.save();
// 绘制文字前平移10像素
canvas.translate(10, 0);
// 父类完成的方法,即绘制文本
super.onDraw(canvas);
canvas.restore();
}


2)创建复合控件

一般需要继承一个合适的ViewGroup

a、定义属性

在res资源目录的value目录下创建一个attrs.xml,一般形如:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TitleBar">
<attr name="title" format="string" />
<attr name="titleTextColor" format="color" />
<attr name="titleTextSize" format="dimension" />
</declare-styleable>
</resources>


自定义属性的使用

//获取自定义属性的对象 TypedArray
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTitle);

//getString 获取String类型的值
mTitle = ta.getString(R.styleable.MyTitle_title);
//getColor 获取Color类型的值
mTitleColor = ta.getColor(R.styleable.MyTitle_titleTextColors, 0);
//getDimension 获取尺寸类型的值
mTitleSize = ta.getDimension(R.styleable.MyTitle_titleTextSize, 0);
//一般到最后,调用recyle避免重新创建时的错误
ta.recycle();


b、组合控件

给自定义控件里面的基本控件设置前面获取的属性。

定义接口,实现回调

c、引用UI模板

//引入命名空间,此命名空间为custom
xmlns:custom="http://schemas.android.com/apk/res-auto"

//自定义属性的定义
custom:title="标题"
custom:titleTextColor="#123412"
custom:titleTextSize="15sp"


3)重写View实现全新的控件

创建自定义View的难点在于绘制控件和实现交互,通常需要继承View类,并重写onDraw、onMeasure等方法来实现绘制逻辑,同时通过重写onTouchEvent等触控事方法来实现交互逻辑。

7、自定义ViewGroup

一般需要重写onMeasure方法来对子View进行测量,onLayout来确定子View的位置,onTouchEvent来增加响应时间。

8、事件拦截机制

核心类:MotionEvent

核心方法:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent

dispatchTouchEvent方法一般不管。ViewGroup比View多重写了一个onInterceptTouchEvent方法,也就是事件拦截机制的核心方法。

onInterceptTouchEvent方法返回值:true,拦截,false,不拦截

onTouchEvent方法返回值:true,代表已自行处理,不往上传递,未处理(其实可以偷偷处理,就告诉你没处理false),继续往上传递
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android ui 架构 控件 布局