承接上一篇: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);
}
}
效果图片
总结:
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,需要通过百度学习。