Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android视图绘制流程完全解析,带你一步步深入了解View(二)

Android视图绘制流程完全解析,带你一步步深入了解View(二)

作者头像
用户1158055
发布于 2018-01-05 09:21:23
发布于 2018-01-05 09:21:23
1.5K00
代码可运行
举报
文章被收录于专栏:郭霖郭霖
运行总次数:0
代码可运行

在上一篇文章中,我带着大家一起剖析了一下LayoutInflater的工作原理,可以算是对View进行深入了解的第一步吧。那么本篇文章中,我们将继续对View进行深入探究,看一看它的绘制流程到底是什么样的。如果你还没有看过我的上一篇文章,可以先去阅读 Android LayoutInflater原理分析,带你一步步深入了解View(一)  。

相信每个Android程序员都知道,我们每天的开发工作当中都在不停地跟View打交道,Android中的任何一个布局、任何一个控件其实都是直接或间接继承自View的,如TextView、Button、ImageView、ListView等。这些控件虽然是Android系统本身就提供好的,我们只需要拿过来使用就可以了,但你知道它们是怎样被绘制到屏幕上的吗?多知道一些总是没有坏处的,那么我们赶快进入到本篇文章的正题内容吧。

要知道,任何一个视图都不可能凭空突然出现在屏幕上,它们都是要经过非常科学的绘制流程后才能显示出来的。每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw(),下面我们逐个对这三个阶段展开进行探讨。

一. onMeasure()

measure是测量的意思,那么onMeasure()方法顾名思义就是用于测量视图的大小的。View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。

MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,如下所示:

  1. EXACTLY

表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  1. AT_MOST

表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  1. UNSPECIFIED

表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。

那么你可能会有疑问了,widthMeasureSpec和heightMeasureSpec这两个值又是从哪里得到的呢?通常情况下,这两个值都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。但是最外层的根视图,它的widthMeasureSpec和heightMeasureSpec又是从哪里得到的呢?这就需要去分析ViewRoot中的源码了,观察performTraversals()方法可以发现如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);

可以看到,这里调用了getRootMeasureSpec()方法去获取widthMeasureSpec和heightMeasureSpec的值,注意方法中传入的参数,其中lp.width和lp.height在创建ViewGroup实例的时候就被赋值了,它们都等于MATCH_PARENT。然后看下getRootMeasureSpec()方法中的代码,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    case ViewGroup.LayoutParams.MATCH_PARENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

可以看到,这里使用了MeasureSpec.makeMeasureSpec()方法来组装一个MeasureSpec,当rootDimension参数等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当rootDimension等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST。并且MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,也就意味着根视图总是会充满全屏的。

介绍了这么多MeasureSpec相关的内容,接下来我们看下View的measure()方法里面的代码吧,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {
        mPrivateFlags &= ~MEASURED_DIMENSION_SET;
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
        }
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
            throw new IllegalStateException("onMeasure() did not set the"
                    + " measured dimension by calling"
                    + " setMeasuredDimension()");
        }
        mPrivateFlags |= LAYOUT_REQUIRED;
    }
    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;
}

注意观察,measure()这个方法是final的,因此我们无法在子类中去重写这个方法,说明Android是不允许我们改变View的measure框架的。然后在第9行调用了onMeasure()方法,这里才是真正去测量并设置View大小的地方,默认会调用getDefaultSize()方法来获取视图的大小,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

这里传入的measureSpec是一直从measure()方法中传递过来的。然后调用MeasureSpec.getMode()方法可以解析出specMode,调用MeasureSpec.getSize()方法可以解析出specSize。接下来进行判断,如果specMode等于AT_MOST或EXACTLY就返回specSize,这也是系统默认的行为。之后会在onMeasure()方法中调用setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。

当然,一个界面的展示可能会涉及到很多次的measure,因为一个布局中一般都会包含多个子视图,每个视图都需要经历一次measure过程。ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

这里首先会去遍历当前布局下的所有子视图,然后逐个调用measureChild()方法来测量相应子视图的大小,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

可以看到,在第4行和第6行分别调用了getChildMeasureSpec()方法来去计算子视图的MeasureSpec,计算的依据就是布局文件中定义的MATCH_PARENT、WRAP_CONTENT等值,这个方法的内部细节就不再贴出。然后在第8行调用子视图的measure()方法,并把计算出的MeasureSpec传递进去,之后的流程就和前面所介绍的一样了。

当然,onMeasure()方法是可以重写的,也就是说,如果你不想使用系统默认的测量方式,可以按照自己的意愿进行定制,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyView extends View {

	......
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		setMeasuredDimension(200, 200);
	}

}

这样的话就把View默认的测量流程覆盖掉了,不管在布局文件中定义MyView这个视图的大小是多少,最终在界面上显示的大小都将会是200*200。

需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。

由此可见,视图大小的控制是由父视图、布局文件、以及视图本身共同完成的,父视图会提供给子视图参考的大小,而开发人员可以在XML文件中指定视图的大小,然后视图本身会对最终的大小进行拍板。

到此为止,我们就把视图绘制流程的第一阶段分析完了。

二. onLayout()

measure过程结束后,视图的大小就已经测量好了,接下来就是layout的过程了。正如其名字所描述的一样,这个方法是用于给视图进行布局的,也就是确定视图的位置。ViewRoot的performTraversals()方法会在measure结束后继续执行,并调用View的layout()方法来执行此过程,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);

layout()方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的。可以看到,这里还把刚才测量出的宽度和高度传到了layout()方法中。那么我们来看下layout()方法中的代码是什么样的吧,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void layout(int l, int t, int r, int b) {
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    boolean changed = setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
        }
        onLayout(changed, l, t, r, b);
        mPrivateFlags &= ~LAYOUT_REQUIRED;
        if (mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }
    mPrivateFlags &= ~FORCE_LAYOUT;
}

在layout()方法中,首先会调用setFrame()方法来判断视图的大小是否发生过变化,以确定有没有必要对当前的视图进行重绘,同时还会在这里把传递过来的四个参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量。接下来会在第11行调用onLayout()方法,正如onMeasure()方法中的默认行为一样,也许你已经迫不及待地想知道onLayout()方法中的默认行为是什么样的了。进入onLayout()方法,咦?怎么这是个空方法,一行代码都没有?!

没错,View中的onLayout()方法就是一个空方法,因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置。既然如此,我们来看下ViewGroup中的onLayout()方法是怎么写的吧,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

可以看到,ViewGroup中的onLayout()方法竟然是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。没错,像LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的。由于LinearLayout和RelativeLayout的布局规则都比较复杂,就不单独拿出来进行分析了,这里我们尝试自定义一个布局,借此来更深刻地理解onLayout()的过程。

自定义的这个布局目标很简单,只要能够包含一个子视图,并且让子视图正常显示出来就可以了。那么就给这个布局起名叫做SimpleLayout吧,代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class SimpleLayout extends ViewGroup {

	public SimpleLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		if (getChildCount() > 0) {
			View childView = getChildAt(0);
			measureChild(childView, widthMeasureSpec, heightMeasureSpec);
		}
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		if (getChildCount() > 0) {
			View childView = getChildAt(0);
			childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
		}
	}

}

代码非常的简单,我们来看下具体的逻辑吧。你已经知道,onMeasure()方法会在onLayout()方法之前调用,因此这里在onMeasure()方法中判断SimpleLayout中是否有包含一个子视图,如果有的话就调用measureChild()方法来测量出子视图的大小。

接着在onLayout()方法中同样判断SimpleLayout是否有包含一个子视图,然后调用这个子视图的layout()方法来确定它在SimpleLayout布局中的位置,这里传入的四个参数依次是0、0、childView.getMeasuredWidth()和childView.getMeasuredHeight(),分别代表着子视图在SimpleLayout中左上右下四个点的坐标。其中,调用childView.getMeasuredWidth()和childView.getMeasuredHeight()方法得到的值就是在onMeasure()方法中测量出的宽和高。

这样就已经把SimpleLayout这个布局定义好了,下面就是在XML文件中使用它了,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<com.example.viewtest.SimpleLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
	
    <ImageView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher"
        />
    
</com.example.viewtest.SimpleLayout>

可以看到,我们能够像使用普通的布局文件一样使用SimpleLayout,只是注意它只能包含一个子视图,多余的子视图会被舍弃掉。这里SimpleLayout中包含了一个ImageView,并且ImageView的宽高都是wrap_content。现在运行一下程序,结果如下图所示:

OK!ImageView成功已经显示出来了,并且显示的位置也正是我们所期望的。如果你想改变ImageView显示的位置,只需要改变childView.layout()方法的四个参数就行了。

在onLayout()过程结束后,我们就可以调用getWidth()方法和getHeight()方法来获取视图的宽高了。说到这里,我相信很多朋友长久以来都会有一个疑问,getWidth()方法和getMeasureWidth()方法到底有什么区别呢?它们的值好像永远都是相同的。其实它们的值之所以会相同基本都是因为布局设计者的编码习惯非常好,实际上它们之间的差别还是挺大的。

首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

观察SimpleLayout中onLayout()方法的代码,这里给子视图的layout()方法传入的四个参数分别是0、0、childView.getMeasuredWidth()和childView.getMeasuredHeight(),因此getWidth()方法得到的值就是childView.getMeasuredWidth() - 0 = childView.getMeasuredWidth() ,所以此时getWidth()方法和getMeasuredWidth() 得到的值就是相同的,但如果你将onLayout()方法中的代码进行如下修改:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
	if (getChildCount() > 0) {
		View childView = getChildAt(0);
		childView.layout(0, 0, 200, 200);
	}
}

这样getWidth()方法得到的值就是200 - 0 = 200,不会再和getMeasuredWidth()的值相同了。当然这种做法充分不尊重measure()过程计算出的结果,通常情况下是不推荐这么写的。getHeight()与getMeasureHeight()方法之间的关系同上,就不再重复分析了。

到此为止,我们把视图绘制流程的第二阶段也分析完了。

三. onDraw()

measure和layout的过程都结束后,接下来就进入到draw的过程了。同样,根据名字你就能够判断出,在这里才真正地开始对视图进行绘制。ViewRoot中的代码会继续执行并创建出一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工作。draw()方法内部的绘制过程总共可以分为六步,其中第二步和第五步在一般情况下很少用到,因此这里我们只分析简化后的绘制过程。代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void draw(Canvas canvas) {
	if (ViewDebug.TRACE_HIERARCHY) {
	    ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
	}
	final int privateFlags = mPrivateFlags;
	final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
	        (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
	mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
	// Step 1, draw the background, if needed
	int saveCount;
	if (!dirtyOpaque) {
	    final Drawable background = mBGDrawable;
	    if (background != null) {
	        final int scrollX = mScrollX;
	        final int scrollY = mScrollY;
	        if (mBackgroundSizeChanged) {
	            background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
	            mBackgroundSizeChanged = false;
	        }
	        if ((scrollX | scrollY) == 0) {
	            background.draw(canvas);
	        } else {
	            canvas.translate(scrollX, scrollY);
	            background.draw(canvas);
	            canvas.translate(-scrollX, -scrollY);
	        }
	    }
	}
	final int viewFlags = mViewFlags;
	boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
	boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
	if (!verticalEdges && !horizontalEdges) {
	    // Step 3, draw the content
	    if (!dirtyOpaque) onDraw(canvas);
	    // Step 4, draw the children
	    dispatchDraw(canvas);
	    // Step 6, draw decorations (scrollbars)
	    onDrawScrollBars(canvas);
	    // we're done...
	    return;
	}
}

可以看到,第一步是从第9行代码开始的,这一步的作用是对视图的背景进行绘制。这里会先得到一个mBGDrawable对象,然后根据layout过程确定的视图位置来设置背景的绘制区域,之后再调用Drawable的draw()方法来完成背景的绘制工作。那么这个mBGDrawable对象是从哪里来的呢?其实就是在XML中通过android:background属性设置的图片或颜色。当然你也可以在代码中通过setBackgroundColor()、setBackgroundResource()等方法进行赋值。

接下来的第三步是在第34行执行的,这一步的作用是对视图的内容进行绘制。可以看到,这里去调用了一下onDraw()方法,那么onDraw()方法里又写了什么代码呢?进去一看你会发现,原来又是个空方法啊。其实也可以理解,因为每个视图的内容部分肯定都是各不相同的,这部分的功能交给子类来去实现也是理所当然的。

第三步完成之后紧接着会执行第四步,这一步的作用是对当前视图的所有子视图进行绘制。但如果当前的视图没有子视图,那么也就不需要进行绘制了。因此你会发现View中的dispatchDraw()方法又是一个空方法,而ViewGroup的dispatchDraw()方法中就会有具体的绘制代码。

以上都执行完后就会进入到第六步,也是最后一步,这一步的作用是对视图的滚动条进行绘制。那么你可能会奇怪,当前的视图又不一定是ListView或者ScrollView,为什么要绘制滚动条呢?其实不管是Button也好,TextView也好,任何一个视图都是有滚动条的,只是一般情况下我们都没有让它显示出来而已。绘制滚动条的代码逻辑也比较复杂,这里就不再贴出来了,因为我们的重点是第三步过程。

通过以上流程分析,相信大家已经知道,View是不会帮我们绘制内容部分的,因此需要每个视图根据想要展示的内容来自行绘制。如果你去观察TextView、ImageView等类的源码,你会发现它们都有重写onDraw()这个方法,并且在里面执行了相当不少的绘制逻辑。绘制的方式主要是借助Canvas这个类,它会作为参数传入到onDraw()方法中,供给每个视图使用。Canvas这个类的用法非常丰富,基本可以把它当成一块画布,在上面绘制任意的东西,那么我们就来尝试一下吧。

这里简单起见,我只是创建一个非常简单的视图,并且用Canvas随便绘制了一点东西,代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyView extends View {

	private Paint mPaint;

	public MyView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		mPaint.setColor(Color.YELLOW);
		canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
		mPaint.setColor(Color.BLUE);
		mPaint.setTextSize(20);
		String text = "Hello View";
		canvas.drawText(text, 0, getHeight() / 2, mPaint);
	}
}

可以看到,我们创建了一个自定义的MyView继承自View,并在MyView的构造函数中创建了一个Paint对象。Paint就像是一个画笔一样,配合着Canvas就可以进行绘制了。这里我们的绘制逻辑比较简单,在onDraw()方法中先是把画笔设置成黄色,然后调用Canvas的drawRect()方法绘制一个矩形。然后在把画笔设置成蓝色,并调整了一下文字的大小,然后调用drawText()方法绘制了一段文字。

就这么简单,一个自定义的视图就已经写好了,现在可以在XML中加入这个视图,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.example.viewtest.MyView 
        android:layout_width="200dp"
        android:layout_height="100dp"
        />

</LinearLayout>

将MyView的宽度设置成200dp,高度设置成100dp,然后运行一下程序,结果如下图所示:

图中显示的内容也正是MyView这个视图的内容部分了。由于我们没给MyView设置背景,因此这里看不出来View自动绘制的背景效果。

当然了Canvas的用法还有很多很多,这里我不可能把Canvas的所有用法都列举出来,剩下的就要靠大家自行去研究和学习了。

到此为止,我们把视图绘制流程的第三阶段也分析完了。整个视图的绘制过程就全部结束了,你现在是不是对View的理解更加深刻了呢?感兴趣的朋友可以继续阅读 Android视图状态及重绘流程分析,带你一步步深入了解View(三) 。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2013-12-26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
用AI指挥另一个AI,GAN+CLIP的组合成了“CG艺术家”
不需要改变数据集,只需要给CLIP下达的命令中把“虚幻引擎”几个字加上,再让CLIP去指挥GAN,图像就立马变成了高大上的CG艺术风。
量子位
2021/07/19
7890
原画师惊呆:这个爆火AI真把梦境画成现实了!下载APP人人可用
明敏 梦晨 发自 凹非寺 量子位 | 公众号 QbitAI 没想到,平常都是我拿着最新AI的Demo到处去安利。 这回竟然被美术圈的朋友安利了一个AI绘画APP??? 话不多说先看效果: 这摩登又玄幻的画面,一上来就冲击到我了。 抽象的线条兼具了美感和想象力,同时还传递出未来城市的感觉。 要不是被剧透,我还真不一定能立刻猜出来这是出自AI之手。 而且除了能对照片进行二改,这个AI还能根据文字命题、凭自己想象作画。 比如输入落日飞车四个字,在AI的“想象”中会是这样: 另外还可以画出不同画风,目前已支持2
量子位
2022/04/28
1.2K0
原画师惊呆:这个爆火AI真把梦境画成现实了!下载APP人人可用
特别详细!带你一文了解扩散模型(不含任何公式)
扩散模型(Diffusion Models)能够生成超棒的图片、视频和音乐。它们的名字来源于自然界的扩散现象,就像水里的墨水慢慢散开一样。在AI的世界里,扩散模型通过逆转扩散过程来生成新数据,也就是说,它通过在数据中添加随机噪声,然后再逆转这个过程,从而从噪声数据中恢复原始数据分布,这样就能创造出新的数据。
ShuYini
2024/03/11
14.1K0
特别详细!带你一文了解扩散模型(不含任何公式)
图像、视频生成大一统!MSRA+北大全华班「女娲」模型怒刷8项SOTA,完虐OpenAI DALL-E
前脚刚推出取得了40多个新SOTA的Florence「佛罗伦萨」吊打CLIP,横扫40多个SOTA。
新智元
2021/11/29
9071
图像、视频生成大一统!MSRA+北大全华班「女娲」模型怒刷8项SOTA,完虐OpenAI DALL-E
效率碾压DALL·E 2和Imagen,谷歌新模型达成新SOTA,还能一句话搞定PS
Alex 羿阁 发自 凹非寺 量子位 | 公众号 QbitAI 新年伊始,谷歌AI又开始发力文字-图像生成模型了。 这次,他们的新模型Muse(缪斯)在CC3M数据集上达成了新SOTA(目前最佳水平)。 而且其效率远超火爆全球的DALL·E 2和Imagen (这俩都属于扩散模型),以及Parti (属于自回归模型)。 ——单张512x512分辨率图像的生成时间被压缩到仅1.3秒。 在图像编辑方面,只需一句文字指令,就可以对原始图像进行编辑。 (貌似不用再为学ps头秃了~) 如果想要效果更精准,还能选定
量子位
2023/02/28
2680
效率碾压DALL·E 2和Imagen,谷歌新模型达成新SOTA,还能一句话搞定PS
Nvidia「艺术家神器」GauGAN发布第二代!训练超1000万张图片,两个词就能生成风景画
最近,英伟达发布了实时绘画工具GauGAN的第二代,主要特性是支持输入文本来生成图像。
磐创AI
2021/12/01
1.5K0
Nvidia「艺术家神器」GauGAN发布第二代!训练超1000万张图片,两个词就能生成风景画
OpenAI DALL·E 3来了,集成ChatGPT,生图效果太炸了
终于,OpenAI 的文生图 AI 工具 DALL-E 系列迎来了最新版本 DALL・E 3,而上个版本 DALL・E 2 还是在去年 4 月推出的。
机器之心
2023/09/21
9870
OpenAI DALL·E 3来了,集成ChatGPT,生图效果太炸了
多模态新王登基!OpenAI发布DALL·E 2,生成图像「指哪打哪」
---- 新智元报道   编辑:LRS 【新智元导读】大艺术家重磅升级!最近OpenAI发布升级版DALL·E 2,不仅分辨率提升了4倍,准确率更高,业务也更广了:除了生成图像,还能二次创作! 2021年1月,OpenAI放了一个大招:DALL-E模型,让自然语言和图像成功牵手,输入一段不管多离谱的文本,都能生成图片! 比如经典的「牛油果形状的扶手椅」,还有新奇生物「一个长颈鹿乌龟」。 当时看着已经够神奇了吧? 时隔一年,OpenAI结合另一个多模态模型CLIP,发布了第二个版本DALL·E 2!
新智元
2022/04/11
5230
多模态新王登基!OpenAI发布DALL·E 2,生成图像「指哪打哪」
一段话让模型自曝「系统提示词」!ChatGPT、Bing无一幸免
而这位名为Bryce Drennan的网友则表示,可以让ChatGPT自己说出来!
量子位
2023/10/25
3921
一段话让模型自曝「系统提示词」!ChatGPT、Bing无一幸免
独家 | 这张骑马的宇航员图片是AI感知世界的一个里程碑(附链接)
文:Will Douglas Heaven April 6, 2022(2022 年4月6日)翻译:陈超校对:zrx 本文约3400字,建议阅读10分钟本文介绍了DALL-E的升级版,DALL-E 2对于人工智能的意义。 DALL-E 2021年初OpenAI的制图神经网络DALL-E一经发布,该项目便以新方法整合不同概念的类人化能力得到瞩目。DALL-E根据需求制作的图片是超现实且卡通化的,他们展现出了AI已经学会了世界是如何融合在一起的关键课程。DALL-E的鳄梨手扶椅具有鳄梨和椅子的关键特征;穿着t
数据派THU
2022/04/27
1.8K0
独家 | 这张骑马的宇航员图片是AI感知世界的一个里程碑(附链接)
AIGC生图技术:从GAN到最新的生成模型架构
人工智能生成内容(AIGC)技术,尤其是在图像生成领域,近年来取得了显著的进展。从最初的生成对抗网络(GAN)到如今的多种生成模型架构,AIGC技术已成为图像创作、艺术生成、虚拟现实、游戏开发等领域的重要工具。本文将深入探讨AIGC生图技术的演变历程,重点从GAN模型的基础到目前的最新生成模型架构,并结合代码实例展示其实现过程。
一键难忘
2025/03/12
5330
详解AI作画算法原理
在艺术与科技的交汇处,AI作画正以惊人的创造力刷新着我们对美的认知。这一领域融合了深度学习、计算机视觉和生成模型的前沿技术,让机器能够“想象”并创作出令人惊叹的图像。本文将深入浅出地探讨AI作画的核心算法原理,分析常见问题与易错点,并通过一个简单的代码示例,带领大家一窥AI艺术创作的奥秘。
Jimaks
2024/04/29
6660
1句话生成视频AI爆火!Meta最新SOTA模型让网友大受震撼
---- 新智元报道   编辑:编辑部 【新智元导读】Meta新模型Make-A-Video,可以从文本一键生成视频了!AI的进展太神了…… 给你一段话,让你做个视频,你能行吗? Meta表示,我可以啊。 你没听错:使用AI,你也可以变成电影人了! 近日,Meta推出了新的AI模型,名字起得也是非常直接:做个视频(Make-A-Video)。 这个模型强大到什么程度? 一句话,就能实现「三马奔腾」的场景。 就连LeCun都说,该来的总是会来的。 视觉效果超炫 话不多说,咱们直接看效果。 俩袋鼠在厨
新智元
2022/10/08
1.1K0
1句话生成视频AI爆火!Meta最新SOTA模型让网友大受震撼
吴恩达的2022年终盘点:生成式AI、ViT、大模型
近日,吴恩达在圣诞节的《The Batch》特刊上发布了一年一度的年终盘点。在过去的一年,生成式AI迎来爆发式增长,由人工智能生成的图片在社交平台疯狂传播,引发大量争议的同时也推动了投资;视觉 Transformer(ViT) 的工作也出现爆炸性增长,在过去一年中,研究人员共计发表超过 17,000 篇 ViT 论文;AlphaCode、Codex 等的推出便利了开发者,大受欢迎;与此同时,研究人员也在不断拓宽语言模型的边界,在解决可信度、偏见和实时性等问题方面做出持续不断的努力。
Datawhale
2023/01/10
6750
吴恩达的2022年终盘点:生成式AI、ViT、大模型
首次不依赖生成模型,一句话让AI修图!
来源:机器之心 本文约4500字,建议阅读9分钟 本文为你介绍一套基于可微矢量渲染器的解决方案。 2022 年是人工智能生成内容(AI Generated Content,AIGC)爆发的一年,其中一个热门方向就是通过文字描述(text prompt)来对图片进行编辑。已有方法通常需要依赖在大规模数据集上训练的生成模型,不仅数据采集和训练成本高昂,且会导致模型尺寸较大。这些因素给技术落地于实际开发和应用带来了较高的门槛,限制了 AIGC 的发展和创造力发挥。 针对以上痛点,网易互娱 AI Lab 与上海交通
数据派THU
2023/03/29
4410
首次不依赖生成模型,一句话让AI修图!
分享15个全球顶尖的AIGC图片生成平台
人工智能正在改变许多行业的格局,而其中改变最直观和影响最大的就是AIGC领域的图像创作。
非喵鱼
2022/12/31
33.2K0
分享15个全球顶尖的AIGC图片生成平台
生成式AI正在改变一切,但热度散后,还会剩什么呢?
大数据文摘转载自数据派THU 作者:Will Douglas Heaven 翻译:顾伟嵩 校对:欧阳锦 没有人知道OpenAI的DALL-E在2022年会那么受欢迎,也没有人知道它的崛起会留给我们什么。 数字艺术家埃里克·卡特(Erik Carter)使用文本转换到图像的AI工具DALL-E2创作了这幅令人不安的图像。 显然,OpenAI已经引起了一些轰动。2021年快结束的时候,一小队研究人员在该公司旧金山办事处正讨论一个想法。他们建立了OpenAI的文本到图像模型DALL-E的新版本,这是一种将简短
大数据文摘
2023/02/23
3400
生成式AI正在改变一切,但热度散后,还会剩什么呢?
广告创意行业的AI革命
近年来,生成式人工智能(Generative AI)的突破性进展正在重塑广告创意行业。作为OpenAI与微软联合推出的尖端技术,DALL-E 3通过Azure云平台的深度集成,不仅大幅提升了图像生成的效率与质量,还为广告行业带来了从创意构思到落地的全链条革新。本文将深入探讨DALL-E 3的技术优势、与Azure结合的实际应用场景,以及其对广告行业的影响与未来潜力。
Michel_Rolle
2025/03/24
1.2K0
多模态图像版「GPT-3」来了!OpenAI推出DALL-E模型,一句话即可生成对应图像
最近,OpenAI官宣了一个基于Transformer的语言模型--DALL-E,使用了GPT-3的120亿参数版本。取名DALL-E,是为了向艺术家萨尔瓦多-达利(Salvador Dali )和皮克斯的机器人WALL-E致敬。
新智元
2021/01/11
1.4K0
多模态图像版「GPT-3」来了!OpenAI推出DALL-E模型,一句话即可生成对应图像
AIGC:DALL·E 2, Stable Diffusion和 Midjourney工作原理简介
在过去的几年里,人工智能(AI)取得了极大的进展,而AI的新产品中有AI图像生成器。这是一种能够将输入的语句转换为图像的工具。文本转图像的AI工具有许多,但最突出的就属DALLE 2、Stable Diffusion和Midjourney了。
Freedom123
2024/03/29
5480
AIGC:DALL·E 2, Stable Diffusion和 Midjourney工作原理简介
推荐阅读
相关推荐
用AI指挥另一个AI,GAN+CLIP的组合成了“CG艺术家”
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验