在Android的UI中,View是至关重要的一个组件,它是用户界面的基本构建块。在View的绘制过程中,涉及到很多重要的概念和技术。本文将详细介绍Android View的绘制过程,让你能够更好地理解和掌握Android的UI开发。
什么是View? View是Android系统中的一个基本组件,它是用户界面上的一个矩形区域,可以用来展示文本、图片、按钮等等。View可以响应用户的交互事件,比如点击、滑动等等。在Android中,所有的UI组件都是继承自View类。
View的绘制过程 View的绘制过程可以分为三个阶段:测量、布局和绘制。下面我们将逐一介绍这三个阶段。
测量阶段(Measure) 测量阶段是View绘制过程的第一个重要阶段。在测量阶段,系统会调用View的onMeasure
方法,测量View的宽度和高度。在这个过程中,系统会根据View的LayoutParams和父容器的大小来计算出View的大小。
例:下面代码是一个自定义View的onMeasure方法例程。在测量过程中,我们设定了View的大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { setMeasuredDimension(widthSize, heightMeasureSpec); return ; } int desiredWidth = getPaddingLeft() + getPaddingRight() + defaultWidth; int measuredWidth; if (desiredWidth < widthSize) { measuredWidth = desiredWidth; } else { measuredWidth = widthSize; } int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int measuredHeight = defaultHeight; if (heightMode == MeasureSpec.EXACTLY) { measuredHeight = heightSize; } else if (heightMode == MeasureSpec.AT_MOST) { measuredHeight = Math.min(defaultHeight, heightSize); } setMeasuredDimension(measuredWidth, measuredHeight); }
在测量阶段结束后,系统会将计算好的宽度和高度传递给布局阶段。
布局阶段(Layout) 布局阶段是View绘制过程的第二个重要阶段。在布局阶段,系统会调用View的onLayout
方法,将View放置在父容器中的正确位置。在这个过程中,系统会根据View的LayoutParams和父容器的位置来确定View的位置。
例:下面代码是一个自定义ViewGroup的onLayout方法例程。在布局过程中,我们遍历子View,并根据LayoutParams确定子View的位置和大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override protected void onLayout (boolean changed, int l, int t, int r, int b) { int count = getChildCount(); int left = getPaddingLeft(); int top = getPaddingTop(); int right = getMeasuredWidth() - getPaddingRight(); int bottom = getMeasuredHeight() - getPaddingBottom(); for (int i = 0 ; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() == GONE) { continue ; } LayoutParams lp = (LayoutParams) child.getLayoutParams(); int childLeft = left + lp.leftMargin; int childTop = top + lp.topMargin; int childRight = right - lp.rightMargin; int childBottom = bottom - lp.bottomMargin; child.layout(childLeft, childTop, childRight, childBottom); } }
绘制阶段(Draw) 绘制阶段是View绘制过程的最后一个重要阶段。在绘制阶段,系统会调用View的onDraw
方法,绘制View的内容。在这个过程中,我们可以使用Canvas对象来绘制各种形状、文本和图片等等。
例:下面代码是一个自定义View的onDraw方法例程。在绘制过程中,我们使用Paint对象绘制了一段文本。
1 2 3 4 5 6 7 8 9 10 11 12 @Override protected void onDraw (Canvas canvas) { super .onDraw(canvas); String text = "Hello World" ; Paint paint = new Paint(); paint.setTextSize(50 ); paint.setColor(Color.RED); paint.setAntiAlias(true ); canvas.drawText(text, 0 , getHeight() / 2 , paint); }
除了绘制内容,我们还可以在绘制阶段绘制View的背景和前景。系统会调用drawBackground
和drawForeground
方法来绘制背景和前景。值得注意的是,View的绘制顺序是:先绘制背景,再绘制内容,最后绘制前景。
View的绘制流程 View的绘制流程可以看作是一个递归调用的过程,下面我们将具体介绍这个过程。
Step 1:创建View 在View绘制过程的开始阶段,我们需要创建一个View对象,并将它添加到父容器中。在这个过程中,系统会调用View的构造函数,并将View的LayoutParams传递给它。
Step 2:测量View 接下来,系统会调用View的measure
方法,测量View的宽度和高度。在这个过程中,View会根据自身的LayoutParams和父容器的大小来计算出自己的宽度和高度。
Step 3:布局View 在测量完成后,系统会调用View的layout
方法,将View放置在父容器中的正确位置。在这个过程中,View会根据自身的LayoutParams和父容器的位置来确定自己的位置。
Step 4:绘制背景 在布局完成后,系统会调用View的drawBackground
方法,绘制View的背景。在这个过程中,我们可以使用Canvas对象来绘制各种形状、文本和图片等等。
Step 5:绘制内容 接下来,系统会调用View的onDraw
方法,绘制View的内容。在这个过程中,我们可以使用Canvas对象来绘制各种形状、文本和图片等等。
Step 6:绘制前景 在绘制内容完成后,系统会调用View的drawForeground
方法,绘制View的前景。在这个过程中,我们同样可以使用Canvas对象来绘制各种形状、文本和图片等等。
Step 7:绘制子View 接着,系统会递归调用ViewGroup的dispatchDraw
方法,绘制所有子View的内容。在这个过程中,我们可以使用Canvas对象来绘制各种形状、文本和图片等等。
Step 8:完成绘制 最后,所有的View绘制完成,整个View树也就绘制完成。
例:下面代码是一个自定义ViewGroup的绘制流程例程。在绘制过程中,我们先画背景,再绘制每个子View的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class MyViewGroup extends ViewGroup { public MyViewGroup (Context context) { super (context); } public MyViewGroup (Context context, AttributeSet attrs) { super (context, attrs); } @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(widthSize, heightSize); } @Override protected void onLayout (boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); int left, top, right, bottom; for (int i = 0 ; i < childCount; i++) { View childView = getChildAt(i); left = childView.getLeft(); top = childView.getTop(); right = childView.getRight(); bottom = childView.getBottom(); childView.layout(left, top, right, bottom); } } @Override protected void onDraw (Canvas canvas) { super .onDraw(canvas); canvas.drawColor(Color.WHITE); } @Override protected void dispatchDraw (Canvas canvas) { super .dispatchDraw(canvas); for (int i = 0 ; i < getChildCount(); i++) { View childView = getChildAt(i); childView.draw(canvas); } } }
在ViewGroup的绘制流程中,系统会先调用ViewGroup的draw
方法,然后依次调用dispatchDraw
方法和绘制每个子View的draw
方法。ViewGroup的绘制顺序是先绘制自己的背景,再绘制每个子View的内容和背景,最后绘制自己的前景。
总结 本文详细介绍了Android View的绘制过程,包括测量阶段、布局阶段和绘制阶段。同时,我们还在代码实现的角度,详细说明了Android ViewGroup的绘制流程,帮助你更好地理解和掌握Android的UI开发。
推荐 android_startup : 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。
AwesomeGithub : 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。
flutter_github : 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。
android-api-analysis : 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。
daily_algorithm : 每日一算法,由浅入深,欢迎加入一起共勉。