android: View的生命周期

今天看到了一篇不错的文章,是一位外国小哥写的,个人觉得不错,遂翻译之,英文好的同学可以直接移步 ——> 生肉: https://proandroiddev.com/the-life-cycle-of-a-view-in-android-6a2c4665b95e

概述

当我们查看一款App的时候,首先引起我们注意的就是屏幕上显示的内容,而屏幕上显示的内容就是 View 。

View是UI界面的基本构建块,它占据了一块矩形区域,负责绘图和事件处理。View同时也是android上其它UI控件的基类,可以用来创建其它交互式的UI组件(比如Button, TextView, 等等)。View的子类 ViewGroup 则是布局的基类,ViewGroup是一个不可见的容器,用于容纳其它的View(或其它的ViewGroup),而且还定义了相关的布局属性。

下图描述了android上的View与其它UI组件之间的关系:

View的生命周期

每个Activity 都有自己的生命周期,同样,View也有自己的生命周期。在屏幕上渲染的View必须经历这些生命周期方法才能正确地在屏幕上绘制。这些生命周期方法的每一种都有其重要性。下面我们来深入了解下View的生命周期:

构造函数

通常,我们对于View为什么有四种类型的构造函数感到困惑:

View(Context context) 
View(Context context, @Nullable AttributeSet attrs) 
View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) 
View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)

View(Context context)

当从代码动态的创建View时就会用到这个简易的构造方法。这里的参数 context 是运行视图的上下文,通过它可以访问到当前的主题(theme), 资源(resources)等东西。

View(Context context, @Nullable AttributeSet attrs)

从XML布局里加载View时调用的构造方法。当从XML文件里创建View同时也为这个View指定了一些相应的属性时,就会调用此方法。这个版本的构造函数使用的默认样式是0,因此唯一应用的属性值是上下文主题和给定AttrubiteSet中的属性值。

View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)

从XML文件执行加载并从主题属性(defStyleAttr) 中应用基本样式。View的这个构造方法允许View在加载时使用它自己定义的基本样式。比如,系统Button类的构造函数就调用了这个构造函数,并且为  defStyleAttr  这个参数提供 R.attr.buttonStyle 的样式属性;这样可以使得系统提供的按钮主题样式可以修改所有的View(特别是View的背景),以及Button类的样式属性。

参数  defStyleAttr 是当前主题的一个属性,其中包含了对样式资源的引用,这个样式资源为View的属性提供了一个默认值,不查找默认值可以将其设置为0。

View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)

从XML文件执行加载,并从主题属性或样式资源中应用特定于类的基本样式。View的这个构造方法允许View在加载时使用它自己的基本样式,与上个构造方法类似。

参数 defStyleRes 是样式资源的资源标识符,为View提供默认值,仅当 defStyleAttr 为0或在主题中找不到时使用。如果不查找默认值,则可以为0。

View的生命周期主要由三部分组成:

Attachment / Detachment (关联/分离)

Traversals(遍历)

State Save / Restore (状态保存/ 恢复)

一. Attachment / Detachment

这是将View关联到窗口(Window)或从窗口分离时的阶段。在此阶段,我们有一些方法可以接收回调来执行适当的操作。

● onAttachedToWindow()

当View关联到窗口时调用。在这个阶段,View知道它处于活动的状态并且具有可绘制的表面。因此,我们在此阶段就可以开始分配任何资源或设置listeners。

● onDetachedFromWindow()

当View与窗口分离时将调用此方法。此时,View不再具有可绘制的表面。在此阶段,你需要停止任何已执行的任务或清理任何已分配的资源。当我们在ViewGroup类上调用 removeView() 或者Activity被销毁时将调用此方法。

● onFinishInflate()

当布局加载器(LayoutInflater)从XML文件里完成了所有子View的加载时,将会调用此方法。

二. Traversals

之所以把此阶段称为“遍历”,是因为View的视图层次就像是从父节点(ViewGroup)到子节点的树状结构。因此,每个方法都是从父节点开始,一直遍历执行到最后一个节点:

 测量(Measure)阶段和布局(Layout)阶段始终是一起发生。如上图所示,这是一个顺序的过程。

● onMeasure()

 这一步用于测量View到底应该有多大。如果是ViewGroup,它会对它的每一个子View都调用测量,测量的结果可以帮助ViewGroup确定其自身的大小。

onMeasure(int widthMeasureSpec, int heightMeasureSpec)
@param widthMeasureSpec Horizontal space requirements as imposed by the parent
@param heightMeasureSpec Vertical space requirements as imposed by the parent

 onMeasure() 没有返回值,由系统调用。

 setMeasuredDimension() 用于显式地设置宽度(width)和高度(height)值。

● MeasureSpec

 MeasureSpec 类封装了从父View传递到子View的布局要求。每个 MeasureSpec 代表对宽度或高度的要求 。 MeasureSpec 由大小(size)和模式(mode)组成,有三种可能的模式:

MeasureSpec.EXACTLY : 父View已经为View确定了一个确切的尺寸。不管子View有多大,都会给子View一个最大限制。这个属性给View设置了一个固定的宽度(比如为LinearLayout指定weight, 或者是为View指定match_parent属性)。

MeasureSpec.AT_MOST:  子View可以根据需要变化到它想要的大小。

MeasureSpec.UNSPECIFIED: 父View没有对子View添加任何约束,子View可以是任意大小。

● onLayout()

View在调用 onMeasure() 完成测量后即会调用此方法,目的是在于确定View在屏幕上的位置。

●  onDraw()

尺寸(通过onMeasure)和位置(通过onLayout())已经由前面的步骤计算出来了,因此View在这个时候已经可以根据上述的尺寸和位置绘制自身。在 onDraw(Canvas canvas)  中,生成或更新的Canvas对象具有要发送到GPU的 OpenGL- ES命令列表(displayList)。

注意:切勿在onDraw()方法中创建对象,因为这个方法会被多次调用

当特定的View的属性发生变化时,还有另外两个方法可以被使用,那就是:

● invalidate()

invalidate()方法用于强制重绘我们希望显示更改的特定View。 简单的说,如果View的外观发生了变化,而我们又想看到这些变化,就可以调用invalite()。

● requestLayout()

在某些情况下,View的状态会发生变化, requestLayout() 可以向android的视图系统发出信号,告诉系统,View需要重新执行自身的测量和布局阶段(onMeasure()->onLayout()->onDraw())。简单的说,当View的范围发生变化时,我们可以调用此函数。

注意:在View上调用任何方法时,必须在UI线程上,如果你正在其它线程上工作,并且想要从该线程更新View的状态,则应使用Handler。

三. State Save / Restore

●  onSaveInstanceState()

要保存视图状态,首先需要为其提供一个ID。如果你的视图层级结构中有多个具有相同ID的View, 则将保存所有的状态,为了避免这种情况,最好为View指定一个唯一的ID。

其次,你需要一个类来继承 View.BaseSavedState  , 然后保存其属性。为了更好的理解,下面举例说明:

●  onRestoreInstanceState(Parcelable state)

在这里我们需要重写此方法,并从Parcelable 读取数据,然后根据Parcelable可用的数据编写逻辑。

@Override
public Parcelable onSaveInstanceState() {
    Bundle bundle = new Bundle();
    // The vars you want to save - in this instance a string and a boolean
    String someString = "something";
    boolean someBoolean = true;
    State state = new State(super.onSaveInstanceState(), someString, someBoolean);
    bundle.putParcelable(State.STATE, state);
    return bundle;
}

@Override
public void onRestoreInstanceState(Parcelable state) {
    if (state instanceof Bundle) {
        Bundle bundle = (Bundle) state;
        State customViewState = (State) bundle.getParcelable(State.STATE);
        // The vars you saved - do whatever you want with them
        String someString = customViewState.getText();
        boolean someBoolean = customViewState.isSomethingShowing());
        super.onRestoreInstanceState(customViewState.getSuperState());
        return;
    }
    // Stops a bug with the wrong state being passed to the super
    super.onRestoreInstanceState(BaseSavedState.EMPTY_STATE); 
}

protected static class State extends BaseSavedState {
    protected static final String STATE = "YourCustomView.STATE";

    private final String someText;
    private final boolean somethingShowing;

    public State(Parcelable superState, String someText, boolean somethingShowing) {
        super(superState);
        this.someText = someText;
        this.somethingShowing = somethingShowing;
    }

    public String getText(){
        return this.someText;
    }

    public boolean isSomethingShowing(){
        return this.somethingShowing;
    }
}

<END>

原文地址:https://www.cnblogs.com/yongdaimi/p/13608937.html