android addview方法说明,Android View 体系竟然还能这么理解?

原标题:Android View 体系竟然还能这么理解?

本文作者

作者:ztq

链接:

0

前言

很多小伙伴可能在学习view的绘制流程源码的时候有点抓不住重点,所以在分析代码的时候绕来绕去脑袋晕乎乎的。今天我就来给大家化繁为简,只关注它最核心的东西。

从数据结构与算法还有设计模式的角度带领大家真正去掌握。我这篇文章旨在让大家能更深刻理解View绘制流程的设计,不涉及具体的细节。

最好的效果是大家先看这篇文章,然后根据文中介绍的知识点去自行查看源码。或者感到吃力的话可以结合别的大牛写的文章去看源码。^_^

1

先修知识点

首先,View体系的数据结构就是树形结构。

ViewGroup继承View,而且ViewGroup持有View的引用,所以这不就是一个树的节点嘛。数据结构跟他的算法是相关的,所以至少你要掌握树的遍历,尤其是树的先序遍历,也就是深度遍历。

在view体系设计中也涉及到了几个设计模式,分别是组合模式,责任链模式,模板方法模式。(当然还有其他如观察者模式,适配器模式等等,不在这次的讨论范围。)

组合模式

如果想实现一个树状的关系,那么就可以使用组合模式。如View和ViewGroup的关系,ViewGroup继承于View,同时也含有子View的引用集合。组合模式一般用于树形结构,所以在这里不需要展开。你只需要知道,View体系本身就是组合模式的体现。

责任链模式

如果想实现一个调用可以让多个类都有机会去处理,那么可以使用责任链模式。类Node含有一个自己的引用,相当于一个链表指针,指向下一个节点。

classNode{

publicString name;

publicNode next;

publicNode( String name){

this.name = name;

}

publicvoidoperate( intnum){

//1.自己先来处理

System. out.println(String.format( "我是节点%s,我在处理:%d", name, num));

//2.分发给下一节点处理

if(next != null) {

next.operate(num);

}

}

}

publicclassMain{

publicstaticvoidmain(String[] args){

Node[] nodes = newNode[ 5];

Node head = nodes[ 0] = newNode( "0");

for( inti = 1; i < 5; i++) { //构造的链表为:0->1->2->3->4

nodes[i] = newNode(i + "");

head.next = nodes[i];

head = nodes[i];

}

head = nodes[ 0];

head.operate( 100);

}

}

结果为:

我是节点 0,我在处理: 100

我是节点 1,我在处理: 100

我是节点 2,我在处理: 100

我是节点 3,我在处理: 100

我是节点 4,我在处理: 100

通过把每个处理者看成是链表上面的一个节点,实现一个调用可以分发给多个处理者去处理。

模板方法模式

如果某一个功能逻辑的流程是比较固定的,但是有一定的步骤,那么可以通过模板方法模式把具体步骤交给子类去实现。

这个怎么理解呢?

以上面责任链模式为例,每个节点的operate的流程是固定的:

1.自己处理消息,

2.把消息分发给下一个节点。

但是可以发现上面的例子有点鸡肋,因为每个Node节点的处理是完全一样的,这看起来没什么意义。

好吧,那结合模板方法模式来进行一个改造。

abstractclassNode{

publicString name;

publicNode next;

publicNode( String name){

this.name = name;

}

publicvoidoperate( intnum){

//1.自己先来处理

intresult = onOperate(num);

System. out.println(String.format( "处理的结果:%d", result));

//2.分发给下一节点处理

if(next != null) {

next.operate(result);

}

}

//抽象方法,具体的处理交给子类根据自己的需求去实现

protectedabstractintonOperate( intnum);

}

可以看到把原先自己直接处理的逻辑抽成了一个抽象函数,这样子类就必须去实现onOperate方法去做自己的处理逻辑。

假设有这样的需求:

实现三个节点,一个是进行+1的操作;一个是进行-1的操作;一个是乘2的操作。

classAddNodeextendsNode{

publicAddNode{

super( "加法器");

}

@Override

protectedintonOperate( intnum){

System.out.println(String.format( "我是%s,我将对%d进行加1操作", name, num));

returnnum + 1;

}

}

classMinusNodeextendsNode{

publicMinusNode{

super( "减法器");

}

@Override

protectedintonOperate( intnum){

System.out.println(String.format( "我是%s,我将对%d进行减1操作", name, num));

returnnum - 1;

}

}

classMultiNodeextendsNode{

publicMultiNode{

super( "乘法器");

}

@Override

protectedintonOperate( intnum){

System.out.println(String.format( "我是%s,我将对%d进行乘2操作", name, num));

returnnum * 2;

}

}

publicclassMain{

publicstaticvoidmain( String[] args){

intnum = 100;

//做运算:(num+1)*2-1 = 201

Node add= newAddNode;

Node minus = newMinusNode;

Node multi = newMultiNode;

add.next = multi;

multi.next = minus;

add.operate(num);

}

}

结果为:

我是加法器,我将对 100进行加 1操作

处理的结果: 101

我是乘法器,我将对 101进行乘 2操作

处理的结果: 202

我是减法器,我将对 202进行减 1操作

处理的结果: 201

模板方法模式体现在:因为每个节点具体的消息处理逻辑是不一样的,通过把operate流程固定,把消息处理逻辑写成抽象函数onOperate交给节点子类去实现。这样不同的节点就可以做不同的处理了。

发现一个彩蛋了没?onOperate函数怎么那么熟悉?

先来想想经常接触到的onXXX方法onCreate,onMeasure,onInterceptTouchEvent……没错,事实上掌握了这几个设计模式,很多时候源码的阅读都会很流畅了。

如触摸事件分发,View绘制的三大过程,Activity生命周期回调,AsyncTask...等等的机制和原理。推荐大家一定要找时间深入研究,成体系地学习一下设计模式。这是高级工程师架构设计必备技能。

树的遍历

为了真正关注核心点而不被其他的东西干扰带偏,所以我假定View树是一个二叉树,或者说我选取一个二叉树View树来进行分析。

首先来回顾一下树的遍历(递归版):

0a62600d1484b1e17af2a1713ee00d5f.png

classNode{

intid;

Node left;

Node right;

publicNode( intid){

this.id = id;

}

}

publicclassMain {

publicstatic void main( String[] args) {

Node[] nodes = newNode[ 8];

for( inti = 0; i < 8; i++) {

nodes[i] = newNode(i);

}

nodes[ 0]. left= nodes[ 1];

nodes[ 0]. right= nodes[ 2];

nodes[ 1]. left= nodes[ 3];

nodes[ 2]. left= nodes[ 4];

nodes[ 2]. right= nodes[ 5];

nodes[ 3]. left= nodes[ 6];

nodes[ 3]. right= nodes[ 7];

dfs(nodes[ 0]);

}

privatestatic void dfs(Node root) {

if(root == null) {

return;

}

System.out.println(root.id);

if(root. left!= null) {

dfs(root. left);

}

if(root. right!= null) {

dfs(root. right);

}

}

}

结果为:

0

1

3

6

7

2

4

5

相信很多人都能写出上面的深度遍历代码,but,这显然不够“java”,严格来说这是c语言形式的写法,只是把节点看成是数据实体,不那么面向对象。那好,我们来实现更加面向对象的深度遍历写法。

面向对象也就是类里面有数据也有行为,那我们就把遍历的行为交给类去做。说白了就是把dfs函数写成成员函数。

classNode{

intid;

Node left;

Node right;

publicNode( intid){

this.id = id;

}

//把dfs写成成员函数

publicvoiddfs{

System. out.println( this.id);

if(left != null) {

left.dfs;

}

if(right != null) {

right.dfs;

}

}

}

publicclassMain{

publicstaticvoidmain(String[] args){

Node[] nodes = newNode[ 8];

for( inti = 0; i < 8; i++) {

nodes[i] = newNode(i);

}

nodes[ 0].left = nodes[ 1];

nodes[ 0].right = nodes[ 2];

nodes[ 1].left = nodes[ 3];

nodes[ 2].left = nodes[ 4];

nodes[ 2].right = nodes[ 5];

nodes[ 3].left = nodes[ 6];

nodes[ 3].right = nodes[ 7];

//dfs(nodes[0]);

nodes[ 0].dfs;

}

}

结果为:

0

1

3

6

7

2

4

5

好了,树的遍历主要是想说明Java版的面向对象的写法。

因为我在百度随意搜索了一下,发现基本都是用c语言版本的写法来写的。

2

View的Measure流程的核心

前面洋洋洒洒写了那么多,现在终于可以应用啦。理解上面的知识点能让你更加容易理解复杂的View的Measure流程。

为了真正关注核心点而不被其他的东西干扰带偏,所以我假定View树是一个二叉树,或者说我选取一个二叉树View树来进行分析。

测量就是计算每个View的大小,先来定义View类。

abstractclassView{

intid;

intwidth;

intheight;

View left;

View right;

publicView( intid){

this.id = id;

}

final publicvoidmeasure( intwidth, intheight){

//1.具体如何测量交给子类决定

onMeasure(width, height);

}

//设置测量值

publicvoidsetMeasuredDimension( intw, inth){

width = w;

height = h;

System. out.println(String.format( "%d的测量结果是w=%d,h=%d", id, width, height));

}

protectedabstractvoidonMeasure( intwidth, intheight);

}

很简陋的一个类,但是包含了最基本的要素了。measure方法里就用了模板方法模式,把具体如何测量交给子类实现。而且用final关键字,所以子类不能覆写measure,也就是说measure方法的流程不让改动。

注意:下文子节点是指View树的子节点,父节点是指View树的父节点,注意跟父类子类区分开。这是两回事来的。

好了,再来实现两个子类,不妨就叫TextView,ImageView。TextView具体的测量就是把父节点传递过来的值减去10,而ImageView是减去20。

classTextViewextendsView{

publicTextView( intid){

super(id);

}

@Override

protectedvoidonMeasure( intwidth, intheight){

intmyW = width - 10;

intmyH = height - 10;

setMeasuredDimension(myW, myH);

//去测量子节点

if(left != null) {

left.measure(myW, myH);

}

if(right != null) {

right.measure(myW, myH);

}

}

}

classImageViewextendsView{

publicImageView( intid){

super(id);

}

@Override

protectedvoidonMeasure( intwidth, intheight){

intmyW = width - 20;

intmyH = height - 20;

setMeasuredDimension(myW, myH);

//去测量子节点

if(left != null) {

left.measure(myW, myH);

}

if(right != null) {

right.measure(myW, myH);

}

}

}

大家可以看到,子节点的测量也是交给子类去负责分发测量了。跟之前讨论模板方法模式时有点不同,但是本质上是一样的。只是模板方法模式的例子是父类负责分发,这里是子类分发。

f685b154859de52cf2016b19ed968299.png

构造上图的View树进行测试。

publicclassMain{

publicstaticvoidmain(String[] args){

View decorView = newImageView( 0);

View imageView1 = newImageView( 1);

View imageView2 = newImageView( 2);

View textView3 = newTextView( 3);

View textView4 = newTextView( 4);

decorView.left = imageView1;

decorView.right = imageView2;

imageView1.left = textView3;

imageView1.right = textView4;

//获取window窗口大小(一般是手机屏幕大小),假设是1080x1920

intwindowW = 1080;

intwindowH = 1920;

decorView.measure(windowW, windowH);

}

}

结果为:

0的测量结果是w= 1060,h= 1900

1的测量结果是w= 1040,h= 1880

3的测量结果是w= 1030,h= 1870

4的测量结果是w= 1030,h= 1870

2的测量结果是w= 1040,h= 1880

根据上图和运行结果可知,View的测量是深度遍历的。测量到一个节点时,这个节点负责去发起子节点的测量,这是责任链模式;而为了把具体测量实现交给子类,使用了模板方法模式。

3

更进一步

有的小伙伴可能说了,你这个跟Android实际的View代码出入有点大啊,你看都没有体现出View跟ViewGroup呢!好吧,那我们来实现更加贴近Android的代码实现吧。

e8dc9410c6edfe97947e9d3752b8a858.png

publicclassMain{

publicstaticvoidmain(String[] args){

LinearLayout linearLayout0 = newLinearLayout( 0);

LinearLayout linearLayout1 = newLinearLayout( 1);

TextView textView2 = newTextView( 2);

LinearLayout linearLayout3 = newLinearLayout( 3);

LinearLayout linearLayout4 = newLinearLayout( 4);

linearLayout0.left = linearLayout1;

linearLayout0.right = textView2;

linearLayout1.left = linearLayout3;

linearLayout1.right = linearLayout4;

//获取window窗口大小,假设是1080x1920

intwindowW = 1080;

intwindowH = 1920;

linearLayout0.measure(windowW,windowH);

}

}

classView{

intid;

intwidth;

intheight;

publicView( intid){

this.id = id;

}

finalpublicvoidmeasure( intwidth, intheight){

//1.具体如何测量交给子类决定

onMeasure(width, height);

}

//设置测量值

publicvoidsetMeasuredDimension( intw, inth){

width = w;

height = h;

System.out.println(String.format( "%d的测量结果是w=%d,h=%d", id, width, height));

}

protectedvoidonMeasure( intwidth, intheight){

//默认实现为直接设置父类传递过来的参数

setMeasuredDimension(width, height);

}

}

classViewGroupextendsView{

publicViewGroup( intid){

super(id);

}

//ViewGroup才有子View

View left;

View right;

@Override

protectedvoidonMeasure( intwidth, intheight){

//默认实现为把width,height减去50作为自己的参数

intmyW = width - 50;

intmyH = height - 50;

setMeasuredDimension(myW, myH);

//发起子节点的测量

if(left != null) {

left.measure(myW, myH);

}

if(right != null) {

right.measure(myW, myH);

}

}

}

//View的子类没有子节点,只需要关心自己的测量

classTextViewextendsView{

publicTextView( intid){

super(id);

}

//实现自己的测量逻辑,把width,height减去10

@Override

protectedvoidonMeasure( intwidth, intheight){

setMeasuredDimension(width - 10, height - 10);

}

}

//ViewGroup的子类有子节点,需要发起子节点的测量

classLinearLayoutextendsViewGroup{

publicLinearLayout( intid){

super(id);

}

//把width,height减去30作为自己的参数

@Override

protectedvoidonMeasure( intwidth, intheight){

intmyW = width - 30;

intmyH = height - 30;

setMeasuredDimension(myW, myH);

//负责发起子节点的测量,这里实现为先测量右节点再测量左节点

if(right != null) {

right.measure(myW, myH);

}

if(left != null) {

left.measure(myW, myH);

}

}

}

结果为:

0的测量结果是w= 1050,h= 1890

2的测量结果是w= 1040,h= 1880

1的测量结果是w= 1020,h= 1860

4的测量结果是w= 990,h= 1830

3的测量结果是w= 990,h= 1830

重要的点都在代码上注释了。

可以看到,之前的遍历顺序是01342,现在是02143了,因为LinearLayout是先进行右节点的测量。

4

总结

View的体系设计用到了许多设计模式,这里主要是责任链模式和模板方法模式,理解设计模式能更加容易读懂源码。

View的遍历是深度遍历,需要掌握Java版的实现。

layout以及draw流程的核心也差不多也是这样,大家跟着我说的去分析源码效果更好。注意子节点的draw流程直接由父类发起了,子类只需要在onDraw中绘制自己的内容即可。

文中关注的重点在于如何实现一颗View树的测量过程。还有很多细节没有涉及,例如MeasureSpec。实际上View最终测量结果是结合我们在xml自己定义的参数和父View自己的参数去决定的。

小提示:大家在看递归代码时可以结合画一下调用栈去分析。

如有写得不准确的地方,欢迎交流指正。^_^返回搜狐,查看更多

责任编辑:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值