ViewGroup学习(一)

来源:互联网 发布:windows界面编程 汇编 编辑:程序博客网 时间:2024/05/22 04:57

  Android UI界面由以下树形结构组成, 从图中可以看出, UI界面是有View与ViewGroup两大类控件组成,在下面树形图中不管是View还是ViewGroup都是从android.view.View中派生, 而ViewGroup作为容器, 它可以装载和管理其下的一些列由android.view.View派生出来的元素(View和ViewGroup):

       

      由android.view.View派生出来的单一控件元素常见的有TextView, Button, ImageView等, 派生出的容器有LinearLayout, FrameLayout 等, 也有一些由ViewGroup派生出来的控件做为单一控件元素使用的, 比如说ListView, 当然我们也可以把ListView当做容器使用。Android通过布局可以完成很多有创意富有美感的界面, ViewGroup的作用很大,这里单独拿出来研究。

 

  ViewGroup实现了android.view.ViewParent和android.view.ViewManager两个接口, 赋予其装载子控件和管理子控件的能力。这篇主要讲Android控件如何绘制到界面上的。

  控件显示到界面上主要分三个流程, 如下图。这是一个非常自然的想法, 得到大小后才可以布局, 布局好了才可以绘制; 这三个流程都是按照上图树形结构递归的。对于这三个流程,只要对Android控件稍有研究的人都

         

会发现, 每一个控件都有measure(), layout(), draw()方法, 下面分别分析其作用:

measure 递归: 

    1、判断是否需要重新计算大小

    2、调用onMeasure, 如果是ViewGroup类型, 则遍历所有子控件的measure方法,计算出子控件大小,

    3、使用setMeasuredDimension(int, int)确定自身计算的大小

    由于第二步会调用子控件的measure方法, 在子控件的大小计算当中也会经历这三步动作, 直到整个树遍历完, 此时此控件及其子控件的大小都确定了, 在这里强调控件的大小是由父控件和自身决定的,当然取决在于父控件, 控件自身只提供参考值, 这是因为控件的measure方法是由父控件调用的, 而父控件的控件有限,可能不完全按照你的申请要求给出, 这里留待以后讨论关于布局参数问题。

在android.view.View对于measure流程已经实现了一部分:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
   // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec);
...}

对于android.view.View来说它不需要遍历子控件了, 下面贴出一个我实现的一个onMeasure :

复制代码
@Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //获取mode和size, 方便给children分配空间        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);        //TODO 这里可以检查你的大小, 或者mode        final int count = getChildCount();        for(int i = 0; i < count; i++) {            final View view = getChildAt(i);            //这里只是举一个例子, 这里给child多少大小根据实际来定            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);            int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);            view.measure(childWidthMeasureSpec, childHeightMeasureSpec);        }        // 得出自己计算出的大小, 这里也是一个例子, 可以根据所有子控件占多大空间        // 给出, 这里也根据要实现的效果看, 这部分建议看LinearLayout等容器的源码        setMeasuredDimension(widthSize, heightSize);    }
复制代码

 

layout 递归: 

    1、设置自身相对父控件的位置并判断是否需要重新布局,使用setFrame(left, top, right, bottom);

    2、调用onLayout()布局子控件

在android.view.View也实现了此流程的一部分:

public void layout(int l, int t, int r, int b) {
... onLayout(changed, l, t, r, b);
...}

下面我也简单的实现了第二步:

复制代码
@Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        final int count = getChildCount();        int widthSpan = 0;        int heightSpan = 0;        for(int i = 0; i < count; i++) {            final View child = getChildAt(i);            child.layout(widthSpan, heightSpan, child.getMeasuredWidth(), child.getMeasuredHeight());            widthSpan += child.getMeasuredWidth();            heightSpan += child.getMeasuredHeight();        }    }
复制代码

这是一个简陋的Grid布局。 

draw递归: 

    1、绘制背景 

    2、调用onDraw()绘制控件内容

    3、调用dispatchDraw()绘制所有的子控件

    4、绘制渐变边界等

    5、绘制装饰品, 比如滑动条等

draw递归在android.view.View已经有完整的实现, 自定义ViewGroup时一般只需要重写onDraw实现如何绘制内容就够了, 当然所有的流程都可以重写, 如果需要的话。下面看一下android.view.View里面draw递归的原型:

复制代码
public void draw(Canvas canvas) {        // Step 1, draw the background, if needed        ...// Step 2, draw the content        onDraw(canvas);        // Step 3, draw the children        dispatchDraw(canvas);        // Step 4, draw the fade effect and restore layers        ...
    
     //Step 5, draw decorations onDrawScrollBars(canvas);}
复制代码

     上面三个递归, 解决了一颗控件树的显示问题, 现在大家会很奇怪, 到底是谁发起这个递归, 即最上层的父控件到底是谁, 查看源码可以看到, 在android.view下面有一个ViewRoot(更新后变成ViewRootImpl)隐藏类, 在其performTraversals()方法中发起这三个递归,这个类没有研究太深入, 以后补上。在performTraversals()中大概的流程是:

复制代码
private void performTraversals() {     final View host = mView;     ...     host.measure();     ...     host.layout();     ...     host.draw();     ...}
复制代码

这样就实现了一个大的递归, 把完整的界面给绘制出来了。下面我自己写一个实现ViewGroup的Demo:

复制代码
package com.ui.viewgroup;import android.content.Context;import android.graphics.Canvas;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;public class ViewGroupImpl extends ViewGroup {    public class LayoutParams extends ViewGroup.LayoutParams {        public int left = 0;        public int top = 0;        public LayoutParams(int width, int height) {            super(width, height);        }        public LayoutParams(int left, int top, int width, int height) {            super(width, height);            this.left = left;            this.top = top;        }    }    public ViewGroupImpl(Context context) {        this(context, null);    }    public ViewGroupImpl(Context context, AttributeSet attrs) {        super(context, attrs);    }    public void addInScreen(View child, int left, int top, int width, int height) {        addView(child, new LayoutParams(left, top, width, height));    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);        // 检测控件大小是否符合要求        if(widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {            throw new IllegalArgumentException("不合法的MeasureSpec mode");        }        // 计算子控件大小        final int count = getChildCount();        for(int i = 0; i < count; i++) {            final View child = getChildAt(i);            final LayoutParams lp = (LayoutParams)child.getLayoutParams();            //确定大小的            final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,                    MeasureSpec.EXACTLY);            final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,                    MeasureSpec.EXACTLY);            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);        }        // 设置计算的控件大小        setMeasuredDimension(widthSize, heightSize);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        final int count = getChildCount();        LayoutParams lp;        for(int i = 0; i < count; i++) {            final View child = getChildAt(i);            lp = (LayoutParams)child.getLayoutParams();            //相对父控件坐标            child.layout(lp.left, lp.top, lp.left + lp.width, lp.top + lp.width);        }    }    // draw递归 不需要我们接管,    @Override    public void draw(Canvas canvas) {        super.draw(canvas);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);    }    @Override    protected void dispatchDraw(Canvas canvas) {        super.dispatchDraw(canvas);    }}
复制代码

Activity:

复制代码
@Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ViewGroupImpl viewGroupImpl = new ViewGroupImpl(this);        setContentView(viewGroupImpl, new LayoutParams(LayoutParams.MATCH_PARENT,                LayoutParams.MATCH_PARENT));        // 因为此时无法获取viewGroupImpl的实际大小, 所以只好假设一个大小        final int parentWidth = 400;        final int parentHeight = 700;        final int maxWidthSize = parentWidth / 4;        final int maxHeightSize = parentHeight / 4;        Random random = new Random();        for(int i = 0; i < 50; i++) {            int left = random.nextInt(parentWidth) - 10;            int top = random.nextInt(parentHeight) - 10;            int width = random.nextInt(maxWidthSize) + 10;            int height = random.nextInt(maxHeightSize) + 10;            ImageView child = new ImageView(this);            child.setImageResource(R.drawable.ic_launcher);            viewGroupImpl.addInScreen(child, left, top, width, height);        }
复制代码

下面是效果图:

原文转自:http://www.cnblogs.com/thinear/archive/2012/07/23/2605153.html