您的位置:首页 > 其它

ViewGroup学习之绘制过程

2015-01-27 11:31 197 查看
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);
}




下面是效果图:

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