Andorid view 绘制原理

时间:2022-11-04 20:59:06


承接上一篇:​​Android View 绘制原理​

blog新地址: 进入 ​​newbie’s home​

1.Onlayout()

对于自定义View,分为两种:

1.是自定义控件(继承View类).
2.是自定义布局容器(继承ViewGroup)。

自定义View时候注意几个点

如果是自定义控件(View),则一般需要重载两个方法,一个是onMeasure(),
用来测量控件尺寸,另一个是onDraw(),用来绘制控件的UI。

而自定义布局容器,则一般需要实现/重载三个方法,一个是onMeasure(),
也是用来测量尺寸;一个是onLayout(),用来布局子控件;
还有一个是dispatchDraw(),用来绘制UI。

相对于(控件)View自定义 布局容器(viewgroup)有点难度,这里拿Viewgroup进行举例。

1.ViewGroup类的onLayout()函数是abstract型,继承者必须实现,由于ViewGroup的定位就是一个容器,
用来盛放子控件的,所以就必须定义要以什么的方式来盛放,
比如LinearLayout就是以横向或者纵向顺序存放,而RelativeLayout则以相对位置来摆放子控件,
同样,我们的自定义ViewGroup也必须给出我们期望的布局方式,而这个定义就通过onLayout()函数来实现。

2.自定义Viewgroup 派生类

第一步,则是自定ViewGroup的派生类,继承默认的构造函数。

public class MesureViewgroup extends ViewGroup {
public MesureViewgroup(Context context) {
super(context, null);
}

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

public MesureViewgroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

}

3.重载OnMesure()

这里需要注意的是,自定义ViewGroup的onMeasure()方法中,除了计算自身的尺寸外,
还需要调用measureChildren()函数来计算子控件的尺寸。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//要求子类进行
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
3.  实现onLayout()方法

//abstrct class,子类必须重写
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//进行布局排列
}
1)参数changed表示view有新的尺寸或位置搜索;
2)参数l表示相对于父view的Left位置;
3)参数t表示相对于父view的Top位置;
4)参数r表示相对于父view的Right位置;
5)参数b表示相对于父view的Bottom位置。


到这一步已经完成自定义的前期工作,接下来需要设置viweproup的排列方式,这里介绍水平排列
方式加深理解。

需求:水平排列,当超过一屏自动换行到下一行进行绘制

分析:

1,需要知道自定义的ViewGroup 宽度 getMeasuredWidth()
2.需要知道子View的宽度 子view .getMeasuredWidth()

代码:

package com.acmenxd.mvp.utils.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

/**
* Created by weichyang on 2017/5/18.
*/

public class MesureViewgroup extends ViewGroup {
public MesureViewgroup(Context context) {
super(context, null);
}

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

public MesureViewgroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//要求子类进行
measureChildren(widthMeasureSpec, heightMeasureSpec);
}

//子类必须重写
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//进行布局排列

int viewGroupWidth = getMeasuredWidth(); //parent content width

int xpoint = l; //对于父view的Left位置
int yPoint = t; //对于父view的top位置

for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
if (xpoint + childWidth > viewGroupWidth) {
//进行换行
xpoint = l;
yPoint = yPoint + childHeight; //更新相对父容器的垂直位置
}
childView.layout(xpoint, yPoint, xpoint + childWidth, yPoint + childHeight); //子类的位置由子类来实现
xpoint += childWidth; // 更新相对父容器的水平位置
}


}


}
layout.xml

<com.acmenxd.mvp.utils.widget.MesureViewgroup
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff">

<View
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="10dp"
android:background="@android:color/black" />

<View
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="10dp"
android:background="@android:color/black" />

<View
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="10dp"
android:background="@android:color/black" />

<View
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="10dp"
android:background="@android:color/black" />

</com.acmenxd.mvp.utils.widget.MesureViewgroup>
看到上面的布局xml ,你会说,添加了margin为什么没有体现?因为还没有对margin 进行兼容处理

下面我们来研究下如何添加margin效果

怎么研究,总不能凭空臆想,当然需要参照,这里参照水平布局的处理
/**
* Per-child layout information associated with ViewLinearLayout.
* 每个孩子与ViewLinearLayout相关的布局信息(存储子view的配置信息的内容类)
* @attr ref android.R.styleable#LinearLayout_Layout_layout_weight
* @attr ref android.R.styleable#LinearLayout_Layout_layout_gravity
*/
public static class LayoutParams extends ViewGroup.MarginLayoutParams {


}
其实,如果要自定义ViewGroup支持子控件的layout_margin参数,则自定义的ViewGroup类必须重载
generateLayoutParams()函数,并且在该函数中返回一个ViewGroup.MarginLayoutParams派生类对象,
这样才能使用margin参数。
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MesureViewgroup.LayoutParams(getContext(), attrs);
}


// 继承自margin,支持子视图android:layout_margin属性
public static class LayoutParams extends MarginLayoutParams {


public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}


public LayoutParams(int width, int height) {
super(width, height);
}


public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}


public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
}
}

效果图片

Andorid view 绘制原理

总结:

1.viewgroup的 onlayout() 流程,
2.参数代表的含义
3.重写 generateLayoutParams() 继承 MarginLayoutParams
MarginLayoutParams
4.onmesure()中对子类view进行mesure()----查看onmesure()分析篇: 链接

demo下载地址:​​http://pan.baidu.com/s/1c2EKQis​

Android ondraw( ) 分析

View 绘制的三个步骤,mesure,layout ,draw,前两种已经简单梳理了,下面是对draw的梳理。

/** 
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/

// Step 1, draw the background, if needed
int saveCount;

if (!dirtyOpaque) {
drawBackground(canvas);
}

// skip step 2 & 5 if possible (common case)
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);

// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}

// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);

// we're done...
return;
}

/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/

boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;

float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;

// Step 2, save the canvas' layers
忽略

// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children
dispatchDraw(canvas);

// Step 5, draw the fade effect and restore layers
忽略
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}ew源码,6个步骤清晰// Step 3, draw the content

if (!dirtyOpaque) onDraw(canvas);ondraw()方法是子类中进行定义

// Step 4, draw the children
dispatchDraw(canvas);

/**
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {

}

Viewgroup 中dispatchDraw() 主要做 view动画处理,子view绘制工作,欲了解自行前往Viewgroup中进行源码阅读

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

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

canvas + paint (画笔)=android 界面任何可见元素

这里作为一个梳理 关于Canvas api,需要通过百度学习。