`
xixinfei
  • 浏览: 414162 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

 
阅读更多

http://www.cnblogs.com/mengdd/p/3332882.html

AndroidView的绘制过程

  当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点。

  绘制过程从布局的根节点开始,从根节点开始测量和绘制整个layout tree。

  每一个ViewGroup 负责要求它的每一个孩子被绘制,每一个View负责绘制自己。

  因为整个树是按顺序遍历的,所以父节点会先被绘制,而兄弟节点会按照它们在树中出现的顺序被绘制。

  

  绘制是一个两遍(two pass)的过程:一个measure pass和一个layout pass。

  测量过程(measuring pass)是在measure(int, int)中实现的,是从树的顶端由上到下进行的。

  在这个递归过程中,每一个View会把自己的dimension specifications传递下去。

  在measure pass的最后,每一个View都存储好了自己的measurements,即测量结果。

 

  第二个是布局过程(layout pass),它发生在 layout(int, int, int, int)中,仍然是从上到下进行(top-down)。

  在这一遍中,每一个parent都会负责用测量过程中得到的尺寸,把自己的所有孩子放在正确的地方。

 

尺寸的父子关系处理

  当一个View对象的 measure() 方法返回时,它的 getMeasuredWidth() 和 getMeasuredHeight()值应该被设置好了,并且它的所有子孙的值也应该一起被设置好了。

  一个View对象的measured width 和measured height的值必须考虑到它的父容器给它的限制。

  这样就保证了在measure pass的最后,所有的parent都接受了它的所有孩子的measurements结果。

 

  注意:一个parent可能会不止一次地对它的孩子调用measure()方法。

  比如,第一遍的时候,一个parent可能测量它的每一个孩子,并没有指定尺寸,parent只是为了发现它们想要多大;

  如果第一遍之后得知,所有孩子的无限制的尺寸总和太大或者太小,parent会再次对它的孩子调用measure()方法,这时候parent会设定规则,介入这个过程,使用实际的值。

  (即,让孩子自由发展不成,于是家长介入)。

 

布局属性说明

  LayoutParams是View用来告诉它的父容器它想要怎样被放置的参数。

  最基本的LayoutParams基类仅仅描述了View想要多大,即指明了尺寸属性。

  即View在XML布局时通常需要指明的宽度和高度属性。

  每一个维度都可以指定成下列三种值之一:

  1.FILL_PARENT (API Level 8之后重命名为MATCH_PARENT),表示View想要尽量和它的parent一样大(减去边距)。

  2.WRAP_CONTENT,表示View想要刚好大到可以包含它的内容(包括边距)。

  3.具体的数值

  ViewGroup的不同子类(不同的布局类)有相应的LayoutParams子类,其中会包含更多的布局相关属性。

 

onMeasure方法

  onMeasure方法是测量view和它的内容,决定measured width和measured height的,这个方法由 measure(int, int)方法唤起,子类可以覆写onMeasure来提供更加准确和有效的测量。

  有一个约定:在覆写onMeasure方法的时候,必须调用 setMeasuredDimension(int,int)来存储这个View经过测量得到的measured width and height。

  如果没有这么做,将会由measure(int, int)方法抛出一个IllegalStateException

 

  onMeasure方法的声明如下:

protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)

 

  其中两个输入参数:

  widthMeasureSpec

  heightMeasureSpec

  分别是parent提出的水平和垂直的空间要求

  这两个要求是按照View.MeasureSpec类来进行编码的。

  参见View.MeasureSpec这个类的说明:这个类包装了从parent传递下来的布局要求,传递给这个child。

  每一个MeasureSpec代表了对宽度或者高度的一个要求。

  每一个MeasureSpec有一个尺寸(size)和一个模式(mode)构成。

  MeasureSpecs这个类提供了把一个<size, mode>的元组包装进一个int型的方法,从而减少对象分配。当然也提供了逆向的解析方法,从int值中解出size和mode。

 

  有三种模式:

  UNSPECIFIED

  这说明parent没有对child强加任何限制,child可以是它想要的任何尺寸。

  EXACTLY

  Parent为child决定了一个绝对尺寸,child将会被赋予这些边界限制,不管child自己想要多大。

  AT_MOST

  Child可以是自己任意的大小,但是有个绝对尺寸的上限。

 

  覆写onMeasure方法的时候,子类有责任确保measured height and width至少为这个View的最小height和width。

  (getSuggestedMinimumHeight() and getSuggestedMinimumWidth())。

 

onLayout

  这个方法是在layout pass中被调用的,用于确定View的摆放位置和大小。方法声明:

protected void onLayout (boolean changed, int left, int top, int right, int bottom)

 

  其中的上下左右参数都是相对于parent的。

  如果View含有child,那么onLayout中需要对每一个child进行布局。

 

自定义View Demo

  API Demos中的LabelView类是一个继承自View的自定义类的例子:

 

复制代码
/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.apis.view;

// Need the following import to get access to the app resources, since this
// class is in a sub-package.
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import com.example.android.apis.R;


/**
 * Example of how to write a custom subclass of View. LabelView
 * is used to draw simple text views. Note that it does not handle
 * styled text or right-to-left writing systems.
 *
 */
public class LabelView extends View {
    private Paint mTextPaint;
    private String mText;
    private int mAscent;
    
    /**
     * Constructor.  This version is only needed if you will be instantiating
     * the object manually (not from a layout XML file).
     * @param context
     */
    public LabelView(Context context) {
        super(context);
        initLabelView();
    }

    /**
     * Construct object, initializing with any attributes we understand from a
     * layout file. These attributes are defined in
     * SDK/assets/res/any/classes.xml.
     * 
     * @see android.view.View#View(android.content.Context, android.util.AttributeSet)
     */
    public LabelView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initLabelView();

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.LabelView);

        CharSequence s = a.getString(R.styleable.LabelView_text);
        if (s != null) {
            setText(s.toString());
        }

        // Retrieve the color(s) to be used for this view and apply them.
        // Note, if you only care about supporting a single color, that you
        // can instead call a.getColor() and pass that to setTextColor().
        setTextColor(a.getColor(R.styleable.LabelView_textColor, 0xFF000000));

        int textSize = a.getDimensionPixelOffset(R.styleable.LabelView_textSize, 0);
        if (textSize > 0) {
            setTextSize(textSize);
        }

        a.recycle();
    }

    private final void initLabelView() {
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        // Must manually scale the desired text size to match screen density
        mTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
        mTextPaint.setColor(0xFF000000);
        setPadding(3, 3, 3, 3);
    }

    /**
     * Sets the text to display in this label
     * @param text The text to display. This will be drawn as one line.
     */
    public void setText(String text) {
        mText = text;
        requestLayout();
        invalidate();
    }

    /**
     * Sets the text size for this label
     * @param size Font size
     */
    public void setTextSize(int size) {
        // This text size has been pre-scaled by the getDimensionPixelOffset method
        mTextPaint.setTextSize(size);
        requestLayout();
        invalidate();
    }

    /**
     * Sets the text color for this label.
     * @param color ARGB value for the text
     */
    public void setTextColor(int color) {
        mTextPaint.setColor(color);
        invalidate();
    }

    /**
     * @see android.view.View#measure(int, int)
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));
    }

    /**
     * Determines the width of this view
     * @param measureSpec A measureSpec packed into an int
     * @return The width of the view, honoring constraints from measureSpec
     */
    private int measureWidth(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            // We were told how big to be
            result = specSize;
        } else {
            // Measure the text
            result = (int) mTextPaint.measureText(mText) + getPaddingLeft()
                    + getPaddingRight();
            if (specMode == MeasureSpec.AT_MOST) {
                // Respect AT_MOST value if that was what is called for by measureSpec
                result = Math.min(result, specSize);
            }
        }

        return result;
    }

    /**
     * Determines the height of this view
     * @param measureSpec A measureSpec packed into an int
     * @return The height of the view, honoring constraints from measureSpec
     */
    private int measureHeight(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        mAscent = (int) mTextPaint.ascent();
        if (specMode == MeasureSpec.EXACTLY) {
            // We were told how big to be
            result = specSize;
        } else {
            // Measure the text (beware: ascent is a negative number)
            result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop()
                    + getPaddingBottom();
            if (specMode == MeasureSpec.AT_MOST) {
                // Respect AT_MOST value if that was what is called for by measureSpec
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    /**
     * Render the text
     * 
     * @see android.view.View#onDraw(android.graphics.Canvas)
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(mText, getPaddingLeft(), getPaddingTop() - mAscent, mTextPaint);
    }
}
复制代码

 

 

参考资料

  API Guides:How Android Draws Views

  http://developer.android.com/guide/topics/ui/how-android-draws.html

  API Guides:Custom Components

  http://developer.android.com/guide/topics/ui/custom-components.html

  View onMeasure:

  http://developer.android.com/reference/android/view/View.html

  ViewGroup.LayoutParams:

  http://developer.android.com/reference/android/view/ViewGroup.LayoutParams.html

分享到:
评论

相关推荐

    Android View 的onMeasure方法详解和例子解释

    首先,`onMeasure()`方法在Android框架中被用于计算自定义View的尺寸。当布局解析XML时,每个View都会根据其`layout_width`和`layout_height`属性被调用`onMeasure()`。这两个属性可以设置为`match_parent`、`wrap_...

    android 自定义view比较综合的例子

    5. 动态布局:在自定义View中动态添加或移除子View,以实现可变布局。 6. 触摸事件处理:通过MotionEvent监听用户的触摸行为,处理滑动、拖拽等操作。 7. 数据绑定:使用DataBinding库将数据与View绑定,减少代码量...

    Android中View绘制流程

    本篇文章将深入探讨Android中View的绘制流程,以及如何通过自定义ViewGroup进行更复杂的布局管理。 首先,我们要了解Android视图的生命周期,其中绘制过程包括`onMeasure()`, `onLayout()`, 和 `onDraw()`三个主要...

    自定义滑动按钮为例图文剖析Android自定义View绘制

    本文将以自定义滑动按钮为例,深入解析Android自定义View的绘制过程。 首先,了解View和ViewGroup的关系至关重要。View是所有UI元素的基础类,它负责绘制和事件处理。而ViewGroup则作为容器,用于管理并布局多个...

    Android 自定义View绘制居中文本

    在Android开发中,自定义View是一项常见的需求,它允许开发者根据特定的设计要求或者功能扩展来创建独特的用户界面组件。本文将深入探讨如何实现一个自定义的View,使其能够精确地绘制水平和垂直居中的文本,效果...

    Android自定义View绘制的方法及过程(二)

    Android自定义View绘制的方法及过程是Android开发中非常重要的一部分,本文将详细介绍Android自定义View绘制的方法及过程,包括onSizeChanged、onDraw、onMeasure顺序的解释。 在Android中,自定义View的绘制过程...

    Android 自定义View视图

    通过实践这样一个自定义罗盘视图的项目,开发者不仅能学会如何创建自定义View,还能深入理解Android图形绘制、事件处理以及传感器数据的运用,这些都是Android应用开发中的重要技能。通过不断的练习和学习,开发者...

    Android自定义View基本绘制流程及实例

    布局阶段由`onLayout()`方法处理,确定View在父View中的位置。最后,绘制阶段在`onDraw()`方法中进行,这是实现View外观的关键步骤。 在`onDraw()`方法中,主要使用Canvas对象进行绘制。Canvas提供了丰富的绘图API...

    Android中自定义View

    在自定义View中添加动画效果,可以使用ViewPropertyAnimator、ValueAnimator或者自定义View的invalidate()方法来实现。ViewPropertyAnimator提供了一种简洁的方式来改变View的属性,而ValueAnimator可以控制属性随...

    Android自定义view之画圆环(手把手教你如何一步步画圆环)的示例

    3. **更新进度**:为了让圆环显示动态的进度变化,我们需要在自定义View中添加一个成员变量来存储当前进度,并提供一个公共方法来更新进度。在进度改变时,调用`invalidate()`方法使View重新绘制。 4. **动画效果**...

    Android 自定义View实现水平温度计

    - 在`onMeasure()`方法中,我们需要指定自定义View的大小。可以根据需求设置固定尺寸或者基于内容计算尺寸。 - `LayoutParams`用于设置View在父容器中的布局参数,如宽度和高度。 6. **Android Studio项目结构**...

    android自定义View滑块移动

    本教程以"android自定义View滑块移动"为例,深入探讨如何实现一个可以在屏幕上自由移动的滑块,并理解Android中View的绘制流程。 首先,我们要明白在Android中创建自定义View的基本步骤。这通常包括以下几个部分: ...

    Android-自定义View绘制一个太极旋转图片demo

    综上所述,这个"Android-自定义View绘制一个太极旋转图片demo"涵盖了Android开发中的自定义视图绘制、动画效果以及资源管理等多个核心知识点。通过实践这个项目,开发者不仅可以提升Android图形绘制的能力,还能掌握...

    android 自定义View界面大合集

    1. **自定义View的基本原理**:自定义View通常基于`View`或`ViewGroup`类进行扩展,通过重写关键方法如`onDraw()`、`onMeasure()`和`onLayout()`来实现绘图逻辑和布局管理。理解这些方法的生命周期和工作原理是创建...

    android自定义veiw——波浪线

    在自定义View中,我们可能需要在布局文件中使用自定义View,并通过引用R类来访问资源。例如,如果波浪线的颜色来自一个XML颜色资源,可以在`onDraw()`中通过`ContextCompat.getColor(this, R.color.wave_color)`获取...

    android 自定义view及自定义属性

    在Android开发中,自定义View和自定义属性是提升应用个性化和功能扩展性的重要手段。本文将深入探讨这两个核心概念,以及如何在实际项目中应用它们。 ### 自定义View 自定义View允许开发者创建自己的视图组件,以...

    android自定义View之NotePad出鞘记

    在Android开发中,自定义View是一项重要的技能,它允许开发者根据需求创建独特的用户界面元素,提升应用的用户体验和个性化程度。本篇文章将深入探讨如何在Android中实现一个自定义的NotePad视图,即模拟一个笔记本...

    android自定义的边缘凹凸的View

    在Android开发中,自定义View是一项重要的技能,它允许开发者根据设计需求创造出独特且富有交互性的界面元素。本文将深入探讨如何实现一个“android自定义的边缘凹凸的View”,也就是一个模仿卡券效果的视图。这个...

    android自定义组件(七) onMeasure测量尺寸

    `onMeasure`方法在Android的View类中被定义,它是测量组件大小的核心方法。当一个View或ViewGroup需要确定其子视图的尺寸时,会调用这个方法。通常,自定义组件需要重写`onMeasure`以确保它们能正确地根据内容或特定...

    android 自定义View 实例

    在Android开发中,自定义View是一项重要的技能,它允许开发者根据需求创建独特的用户界面元素,以实现更加丰富和个性化的交互体验。本实例主要讲解如何在Android中自定义View,并提供了一个可作为参考的demo。 首先...

Global site tag (gtag.js) - Google Analytics