• 欢迎访问风的记忆博客网站,如有疑问请加作者QQ或者微信联系。作者QQ:524100248,微信号:sendtion。

Android View绘制13问13答

安卓教程 sendtion 4年前 (2016-03-23) 1088次浏览 已收录 0个评论 扫描二维码

来源:AndroidChina

1.view 的绘制流程分几步,从哪开始?哪个过程结束以后能看到 view?

答:从 ViewRoot 的 performTraversals 开始,经过 measure,layout,draw 三个流程。draw 流程结束以后就可以在屏幕上看到 view 了。

2.view 的测量宽高和实际宽高有区别吗?

答:基本上百分之 99 的情况下都是可以认为没有区别的。有两种情况,有区别。第一种 就是有的时候会因为某些原因 view 会多次测量,那第一次测量的宽高 肯定和最后实际的宽高 是不一定相等的,但是在这种情况下

最后一次测量的宽高和实际宽高是一致的。此外,实际宽高是在 layout 流程里确定的,我们可以在 layout 流程里 将实际宽高写死 写成硬编码,这样测量的宽高和实际宽高就肯定不一样了,虽然这么做没有意义 而且也不好。

3.view 的 measureSpec 由谁决定?顶级 view 呢?

答:由 view 自己的 layoutparams 和父容器  一起决定自己的 measureSpec。一旦确定了 spec,onMeasure 中就可以确定 view 的宽高了。

顶级 view 就稍微特殊一点,对于 decorView 的测量在 ViewRootImpl 的源码里。

1
//desire的这2个参数就代表屏幕的宽高,
1
  
1
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
1
  
1
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
1
  
1
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
1
  
1
//decorView的measureSpec就是在这里确定的,其实比普通view的measurespec要简单的多
1
  
1
//代码就不分析了 一目了然的东西
1
  
1
private
1
static
1
int
1
getRootMeasureSpec(
1
int
1
windowSize,
1
int
1
rootDimension) {
1
        
1
int
1
measureSpec;
1
        
1
switch
1
(rootDimension) {
1
        
1
case
1
ViewGroup.LayoutParams.MATCH_PARENT:
1
            
1
// Window can't resize. Force root view to be windowSize.
1
            
1
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
1
            
1
break
1
;
1
        
1
case
1
ViewGroup.LayoutParams.WRAP_CONTENT:
1
            
1
// Window can resize. Set max size for root view.
1
            
1
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
1
            
1
break
1
;
1
        
1
default
1
:
1
            
1
// Window wants to be an exact size. Force root view to be that size.
1
            
1
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
1
            
1
break
1
;
1
        
1
}
1
        
1
return
1
measureSpec;
1
}

4.对于普通 view 来说,他的 measure 过程中,与父 view 有关吗?如果有关,这个父 view 也就是 viewgroup 扮演了什么角色?

答:看源码:

1
//对于普通view的measure来说 是由这个view的 父view ,也就是viewgroup来触发的。
1
//也就是下面这个measureChildWithMargins方法
1
protected
1
void
1
measureChildWithMargins(View child,
1
            
1
int
1
parentWidthMeasureSpec,
1
int
1
widthUsed,
1
            
1
int
1
parentHeightMeasureSpec,
1
int
1
heightUsed) {
1
         
1
//第一步 先取得子view的 layoutParams 参数值
1
        
1
final
1
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
1
        
1
//然后开始计算子view的spec的值,注意这里看到 计算的时候除了要用子view的 layoutparams参数以外
1
        
1
//还用到了父view 也就是viewgroup自己的spec的值
1
        
1
final
1
int
1
childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
1
                
1
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
1
                        
1
+ widthUsed, lp.width);
1
        
1
final
1
int
1
childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
1
                
1
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
1
                        
1
+ heightUsed, lp.height);
1
        
1
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1
}
1
//这个算view的spec的方法 看上去一大串 但是真的逻辑非常简单 就是根据父亲viewgroup
1
//的meaurespec 同时还有view自己的params来确定 view自己的measureSpec。
1
//注意这里的参数是padding,这个值的含义是 父容器已占用的控件的大小 所以view的Specsize
1
//的值 你们可以看到 是要减去这个padding的值的。总大小-已经用的 =可用的。 很好理解。
1
//然后就是下面的switch逻辑 要自己梳理清楚。其实也不难,主要是下面几条原则
1
//如果view采用固定宽高,也就是写死的数值那种。那就不管父亲的spec的值了,view的spec 就肯定是exactly 并且大小遵循layout参数里设置的大小。
1
//如果view的宽高是match_parent ,那么就要看父容器viewgroup的 spec的值了,如果父view的spec是exactly模式,
1
//那view也肯定是exactly,并且大小就是父容器剩下的空间。如果父容器是at_most模式,那view也是at_most 并且不会超过剩余空间大小
1
//如果view的宽高是wrap_content, 那就不管父容器的spec了,view的spec一定是at_most 并且不会超过父view 剩余空间的大小。
1
public
1
static
1
int
1
getChildMeasureSpec(
1
int
1
spec,
1
int
1
padding,
1
int
1
childDimension) {
1
        
1
int
1
specMode = MeasureSpec.getMode(spec);
1
        
1
int
1
specSize = MeasureSpec.getSize(spec);
1
        
1
int
1
size = Math.max(
1
0
1
, specSize - padding);
1
        
1
int
1
resultSize =
1
0
1
;
1
        
1
int
1
resultMode =
1
0
1
;
1
        
1
switch
1
(specMode) {
1
        
1
// Parent has imposed an exact size on us
1
        
1
case
1
MeasureSpec.EXACTLY:
1
            
1
if
1
(childDimension >=
1
0
1
) {
1
                
1
resultSize = childDimension;
1
                
1
resultMode = MeasureSpec.EXACTLY;
1
            
1
}
1
else
1
if
1
(childDimension == LayoutParams.MATCH_PARENT) {
1
                
1
// Child wants to be our size. So be it.
1
                
1
resultSize = size;
1
                
1
resultMode = MeasureSpec.EXACTLY;
1
            
1
}
1
else
1
if
1
(childDimension == LayoutParams.WRAP_CONTENT) {
1
                
1
// Child wants to determine its own size. It can't be
1
                
1
// bigger than us.
1
                
1
resultSize = size;
1
                
1
resultMode = MeasureSpec.AT_MOST;
1
            
1
}
1
            
1
break
1
;
1
        
1
// Parent has imposed a maximum size on us
1
        
1
case
1
MeasureSpec.AT_MOST:
1
            
1
if
1
(childDimension >=
1
0
1
) {
1
                
1
// Child wants a specific size... so be it
1
                
1
resultSize = childDimension;
1
                
1
resultMode = MeasureSpec.EXACTLY;
1
            
1
}
1
else
1
if
1
(childDimension == LayoutParams.MATCH_PARENT) {
1
                
1
// Child wants to be our size, but our size is not fixed.
1
                
1
// Constrain child to not be bigger than us.
1
                
1
resultSize = size;
1
                
1
resultMode = MeasureSpec.AT_MOST;
1
            
1
}
1
else
1
if
1
(childDimension == LayoutParams.WRAP_CONTENT) {
1
                
1
// Child wants to determine its own size. It can't be
1
                
1
// bigger than us.
1
                
1
resultSize = size;
1
                
1
resultMode = MeasureSpec.AT_MOST;
1
            
1
}
1
            
1
break
1
;
1
        
1
// Parent asked to see how big we want to be
1
        
1
case
1
MeasureSpec.UNSPECIFIED:
1
            
1
if
1
(childDimension >=
1
0
1
) {
1
                
1
// Child wants a specific size... let him have it
1
                
1
resultSize = childDimension;
1
                
1
resultMode = MeasureSpec.EXACTLY;
1
            
1
}
1
else
1
if
1
(childDimension == LayoutParams.MATCH_PARENT) {
1
                
1
// Child wants to be our size... find out how big it should
1
                
1
// be
1
                
1
resultSize = View.sUseZeroUnspecifiedMeasureSpec ?
1
0
1
: size;
1
                
1
resultMode = MeasureSpec.UNSPECIFIED;
1
            
1
}
1
else
1
if
1
(childDimension == LayoutParams.WRAP_CONTENT) {
1
                
1
// Child wants to determine its own size.... find out how
1
                
1
// big it should be
1
                
1
resultSize = View.sUseZeroUnspecifiedMeasureSpec ?
1
0
1
: size;
1
                
1
resultMode = MeasureSpec.UNSPECIFIED;
1
            
1
}
1
            
1
break
1
;
1
        
1
}
1
        
1
return
1
MeasureSpec.makeMeasureSpec(resultSize, resultMode);
1
    
1
}

5.view 的 meaure 和 onMeasure 有什么关系?

答:看源码:

1
//view的measure是final 方法 我们子类无法修改的。
1
 
1
public
1
final
1
void
1
measure(
1
int
1
widthMeasureSpec,
1
int
1
heightMeasureSpec) {
1
        
1
boolean
1
optical = isLayoutModeOptical(
1
this
1
);
1
        
1
if
1
(optical != isLayoutModeOptical(mParent)) {
1
            
1
Insets insets = getOpticalInsets();
1
            
1
int
1
oWidth  = insets.left + insets.right;
1
            
1
int
1
oHeight = insets.top  + insets.bottom;
1
            
1
widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
1
            
1
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
1
        
1
}
1
        
1
// Suppress sign extension for the low bytes
1
        
1
long
1
key = (
1
long
1
) widthMeasureSpec <<
1
32
1
| (
1
long
1
) heightMeasureSpec & 0xffffffffL;
1
        
1
if
1
(mMeasureCache ==
1
null
1
) mMeasureCache =
1
new
1
LongSparseLongArray(
1
2
1
);
1
        
1
if
1
((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
1
                
1
widthMeasureSpec != mOldWidthMeasureSpec ||
1
                
1
heightMeasureSpec != mOldHeightMeasureSpec) {
1
            
1
// first clears the measured dimension flag
1
            
1
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
1
            
1
resolveRtlPropertiesIfNeeded();
1
            
1
int
1
cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -
1
1
1
:
1
                    
1
mMeasureCache.indexOfKey(key);
1
            
1
if
1
(cacheIndex <
1
0
1
|| sIgnoreMeasureCache) {
1
                
1
// measure ourselves, this should set the measured dimension flag back
1
                
1
onMeasure(widthMeasureSpec, heightMeasureSpec);
1
                
1
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
1
            
1
}
1
else
1
{
1
                
1
long
1
value = mMeasureCache.valueAt(cacheIndex);
1
                
1
// Casting a long to int drops the high 32 bits, no mask needed
1
                
1
setMeasuredDimensionRaw((
1
int
1
) (value >>
1
32
1
), (
1
int
1
) value);
1
                
1
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
1
            
1
}
1
            
1
// flag not set, setMeasuredDimension() was not invoked, we raise
1
            
1
// an exception to warn the developer
1
            
1
if
1
((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
1
                
1
throw
1
new
1
IllegalStateException(
1
"View with id "
1
+ getId() +
1
": "
1
                        
1
+ getClass().getName() +
1
"#onMeasure() did not set the"
1
                        
1
+
1
" measured dimension by calling"
1
                        
1
+
1
" setMeasuredDimension()"
1
);
1
            
1
}
1
            
1
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
1
        
1
}
1
        
1
mOldWidthMeasureSpec = widthMeasureSpec;
1
        
1
mOldHeightMeasureSpec = heightMeasureSpec;
1
        
1
mMeasureCache.put(key, ((
1
long
1
) mMeasuredWidth) <<
1
32
1
|
1
                
1
(
1
long
1
) mMeasuredHeight & 0xffffffffL);
1
// suppress sign extension
1
    
1
}
1
//不过可以看到的是在measure方法里调用了onMeasure方法
1
//所以就能知道 我们在自定义view的时候一定是重写这个方法!
1
 
1
protected
1
void
1
onMeasure(
1
int
1
widthMeasureSpec,
1
int
1
heightMeasureSpec) {
1
        
1
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
1
                
1
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
1
    
1
}

6.简要分析 view 的 measure 流程?

答:先回顾问题 4,viewgroup 算出子 view 的 spec 以后 会调用子 view 的 measure 方法,而子 view 的 measure 方法 我们问题 5 也看过了实际上是调用的 onMeasure 方法

所以我们只要分析好 onMeasure 方法即可,注意 onMeasure 方法的参数 正是他的父 view 算出来的那 2 个 spec 的值(这里 view 的 measure 方法会把这个 spec 里的 specSize 值做略微的修改 这个部分 不做分析 因为 measure 方法修改 specSize 的部分很简单)。

1
//可以看出来这个就是setMeasuredDimension方法的调用 这个方法看名字就知道就是确定view的测量宽高的
1
//所以我们分析的重点就是看这个getDefaultSize 方法 是怎么确定view的测量宽高的
1
 
1
protected
1
void
1
onMeasure(
1
int
1
widthMeasureSpec,
1
int
1
heightMeasureSpec) {
1
        
1
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
1
                
1
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
1
    
1
}
1
//这个方法特别简单 基本可以认为就是近似的返回spec中的specSize,除非你的specMode是UNSPECIFIED
1
//UNSPECIFIED 这个一般都是系统内部测量才用的到,这种时候返回size 也就是getSuggestedMinimumWidth的返回值
1
 
1
public
1
static
1
int
1
getDefaultSize(
1
int
1
size,
1
int
1
measureSpec) {
1
        
1
int
1
result = size;
1
        
1
int
1
specMode = MeasureSpec.getMode(measureSpec);
1
        
1
int
1
specSize = MeasureSpec.getSize(measureSpec);
1
        
1
switch
1
(specMode) {
1
        
1
case
1
MeasureSpec.UNSPECIFIED:
1
            
1
result = size;
1
            
1
break
1
;
1
        
1
case
1
MeasureSpec.AT_MOST:
1
        
1
case
1
MeasureSpec.EXACTLY:
1
            
1
result = specSize;
1
            
1
break
1
;
1
        
1
}
1
        
1
return
1
result;
1
}
1
//跟view的背景相关 这里不多做分析了
1
protected
1
int
1
getSuggestedMinimumWidth() {
1
        
1
return
1
(mBackground ==
1
null
1
) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
1
    
1
}

7.自定义 view 中 如果 onMeasure 方法 没有对 wrap_content 做处理 会发生什么?为什么?怎么解决?

答:如果没有对 wrap_content 做处理 ,那即使你在 xml 里设置为 wrap_content.其效果也和 match_parent 相同。看问题 4 的分析。我们可以知道 view 自己的 layout 为 wrap,那 mode 就是 at_most(不管父亲 view 是什么 specmode).

这种模式下宽高就是等于 specSize(getDefaultSize 函数分析可知),而这里的 specSize 显然就是 parentSize 的大小。也就是父容器剩余的大小。那不就和我们直接设置成 match_parent 是一样的效果了么?

解决方式就是在 onMeasure 里 针对 wrap 来做特殊处理 比如指定一个默认的宽高,当发现是 wrap_content 就设置这个默认宽高即可。

8.ViewGroup 有 onMeasure 方法吗?为什么?

答:没有,这个方法是交给子类自己实现的。不同的 viewgroup 子类 肯定布局都不一样,那 onMeasure 索性就全部交给他们自己实现好了。

9.为什么在 activity 的生命周期里无法获得测量宽高?有什么方法可以解决这个问题吗?

答:因为 measure 的过程和 activity 的生命周期  没有任何关系。你无法确定在哪个生命周期执行完毕以后 view 的 measure 过程一定走完。可以尝试如下几种方法 获取 view 的测量宽高。

1
//重写activity的这个方法
1
public
1
void
1
onWindowFocusChanged(
1
boolean
1
hasFocus) {
1
        
1
super
1
.onWindowFocusChanged(hasFocus);
1
        
1
if
1
(hasFocus) {
1
            
1
int
1
width = tv.getMeasuredWidth();
1
            
1
int
1
height = tv.getMeasuredHeight();
1
            
1
Log.v(
1
"burning"
1
,
1
"width=="
1
+ width);
1
            
1
Log.v(
1
"burning"
1
,
1
"height=="
1
+ height);
1
        
1
}
1
    
1
}

或者重写这个方法

1
@Override
1
    
1
protected
1
void
1
onStart() {
1
        
1
super
1
.onStart();
1
        
1
tv.post(
1
new
1
Runnable() {
1
            
1
@Override
1
            
1
public
1
void
1
run() {
1
                
1
int
1
width = tv.getMeasuredWidth();
1
                
1
int
1
height = tv.getMeasuredHeight();
1
            
1
}
1
        
1
});
1
    
1
}

再或者:

1
@Override
1
    
1
protected
1
void
1
onStart() {
1
        
1
super
1
.onStart();
1
        
1
ViewTreeObserver observer = tv.getViewTreeObserver();
1
        
1
observer.addOnGlobalLayoutListener(
1
new
1
ViewTreeObserver.OnGlobalLayoutListener() {
1
            
1
@Override
1
            
1
public
1
void
1
onGlobalLayout() {
1
                
1
int
1
width = tv.getMeasuredWidth();
1
                
1
int
1
height = tv.getMeasuredHeight();
1
                
1
tv.getViewTreeObserver().removeOnGlobalLayoutListener(
1
this
1
);
1
            
1
}
1
        
1
});
1
    
1
}

10.layout 和 onLayout 方法有什么区别?

答:layout 是确定本身 view 的位置 而 onLayout 是确定所有子元素的位置。layout 里面 就是通过 serFrame 方法设设定本身 view 的 四个顶点的位置。这 4 个位置以确定 自己 view 的位置就固定了

然后就调用 onLayout 来确定子元素的位置。view 和 viewgroup 的 onlayout 方法都没有写。都留给我们自己给子元素布局

11.draw 方法 大概有几个步骤?

答: 一共是 4 个步骤, 绘制背景———绘制自己——–绘制 chrildren—-绘制装饰。

12.setWillNotDraw 方法有什么用?

答:这个方法在 view 里。

1
/**
1
     
1
* If this view doesn't do any drawing on its own, set this flag to
1
     
1
* allow further optimizations. By default, this flag is not set on
1
     
1
* View, but could be set on some View subclasses such as ViewGroup.
1
     
1
*
1
     
1
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
1
     
1
* you should clear this flag.
1
     
1
*
1
     
1
* @param willNotDraw whether or not this View draw on its own
1
     
1
*/
1
    
1
public
1
void
1
setWillNotDraw(
1
boolean
1
willNotDraw) {
1
        
1
setFlags(willNotDraw ? WILL_NOT_DRAW :
1
0
1
, DRAW_MASK);
1
    
1
}

用于设置标志位的 也就是说 如果你的自定义 view 不需要 draw 的话,就可以设置这个方法为 true。这样系统知道你这个 view 不需要 draw 可以优化执行速度。viewgroup 一般都默认设置这个为 true,因为 viewgroup 多数都是只负责布局

不负责 draw 的。而 view 这个标志位 默认一般都是关闭的。

13.自定义 view 有哪些需要注意的点?

答:主要是要处理 wrap_content 和 padding。否则 xml 那边设置这 2 个属性就根本没用了。还有不要在 view 中使用 handler 因为人家已经提供了 post 方法。如果是继承自 viewGroup,那在 onMeasure 和 onLayout 里面 也要考虑

padding 和 layout 的影响。也就是说 specSize 要算一下 。最后就是如果 view 的动画或者线程需要停止,可以考虑在 onDetachedFromWindow 里面来做。

针对上述的几点,给出几个简单的自定义 view 供大家理解。

给出一个圆形的 view 范例:

1
package
1
com.example.administrator.motioneventtest;
1
import
1
android.content.Context;
1
import
1
android.graphics.Canvas;
1
import
1
android.graphics.Color;
1
import
1
android.graphics.Paint;
1
import
1
android.util.AttributeSet;
1
import
1
android.view.View;
1
/**
1
 
1
* Created by Administrator on 2016/2/4.
1
 
1
*/
1
public
1
class
1
CircleView
1
extends
1
View {
1
    
1
private
1
int
1
mColor = Color.RED;
1
    
1
private
1
Paint mPaint =
1
new
1
Paint(Paint.ANTI_ALIAS_FLAG);
1
    
1
private
1
void
1
init() {
1
        
1
mPaint.setColor(mColor);
1
    
1
}
1
    
1
@Override
1
    
1
protected
1
void
1
onMeasure(
1
int
1
widthMeasureSpec,
1
int
1
heightMeasureSpec) {
1
        
1
super
1
.onMeasure(widthMeasureSpec, heightMeasureSpec);
1
        
1
int
1
widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
1
        
1
int
1
widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
1
        
1
int
1
heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
1
        
1
int
1
heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
1
        
1
//处理为wrap_content时的情况
1
        
1
if
1
(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
1
            
1
setMeasuredDimension(
1
200
1
,
1
200
1
);
1
        
1
}
1
else
1
if
1
(widthSpecMode == MeasureSpec.AT_MOST) {
1
            
1
setMeasuredDimension(
1
200
1
, heightSpecSize);
1
        
1
}
1
else
1
if
1
(heightSpecMode == MeasureSpec.AT_MOST) {
1
            
1
setMeasuredDimension(widthSpecSize,
1
200
1
);
1
        
1
}
1
    
1
}
1
    
1
@Override
1
    
1
protected
1
void
1
onDraw(Canvas canvas) {
1
        
1
super
1
.onDraw(canvas);
1
        
1
//处理padding的情况
1
        
1
final
1
int
1
paddingLeft = getPaddingLeft();
1
        
1
final
1
int
1
paddingRight = getPaddingRight();
1
        
1
final
1
int
1
paddingTop = getPaddingTop();
1
        
1
final
1
int
1
paddingBottom = getPaddingBottom();
1
        
1
int
1
width = getWidth() - paddingLeft - paddingRight;
1
        
1
int
1
height = getHeight() - paddingTop - paddingBottom;
1
        
1
int
1
radius = Math.min(width, height) /
1
2
1
;
1
        
1
canvas.drawCircle(paddingLeft + width /
1
2
1
, paddingTop + height /
1
2
1
, radius, mPaint);
1
    
1
}
1
    
1
public
1
CircleView(Context context, AttributeSet attrs,
1
int
1
defStyleAttr) {
1
        
1
super
1
(context, attrs, defStyleAttr);
1
        
1
init();
1
    
1
}
1
    
1
public
1
CircleView(Context context) {
1
        
1
super
1
(context);
1
        
1
init();
1
    
1
}
1
    
1
public
1
CircleView(Context context, AttributeSet attrs) {
1
        
1
super
1
(context, attrs);
1
        
1
init();
1
    
1
}
1
}

然后下面再给出一个范例,稍微复杂一点是自定义 viewgroup 了(主要是加强对 onMeasure 和 onLayout 的理解), 需求如下:

一个水平的 viewgroup,内部的子元素 为了简单 我们假定他们的宽高都是一样的。来写一个这样的简单的 viewgroup。

1
package
1
com.example.administrator.motioneventtest;
1
import
1
android.content.Context;
1
import
1
android.util.AttributeSet;
1
import
1
android.util.Log;
1
import
1
android.view.View;
1
import
1
android.view.ViewGroup;
1
/**
1
 
1
* Created by Administrator on 2016/2/4.
1
 
1
*/
1
//这里我们只处理了padding的状态 没有处理margin的状态,子view的margin 对measure和layout的影响
1
//就留给读者自己完成了
1
public
1
class
1
CustomHorizontalLayout
1
extends
1
ViewGroup {
1
    
1
//设置默认的控件最小是多少 这里不提供自定义属性了 写死在代码里 你们可以自行拓展
1
    
1
final
1
int
1
minHeight =
1
0
1
;
1
    
1
final
1
int
1
minWidth =
1
0
1
;
1
    
1
public
1
CustomHorizontalLayout(Context context) {
1
        
1
super
1
(context);
1
    
1
}
1
    
1
public
1
CustomHorizontalLayout(Context context, AttributeSet attrs) {
1
        
1
super
1
(context, attrs);
1
    
1
}
1
    
1
public
1
CustomHorizontalLayout(Context context, AttributeSet attrs,
1
int
1
defStyleAttr) {
1
        
1
super
1
(context, attrs, defStyleAttr);
1
    
1
}
1
    
1
@Override
1
    
1
protected
1
void
1
onMeasure(
1
int
1
widthMeasureSpec,
1
int
1
heightMeasureSpec) {
1
        
1
super
1
.onMeasure(widthMeasureSpec, heightMeasureSpec);
1
        
1
int
1
measureWidth =
1
0
1
;
1
        
1
int
1
measureHeight =
1
0
1
;
1
        
1
final
1
int
1
childCount = getChildCount();
1
        
1
measureChildren(widthMeasureSpec, heightMeasureSpec);
1
        
1
int
1
widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
1
        
1
int
1
widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
1
        
1
int
1
heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
1
        
1
int
1
heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
1
        
1
final
1
View childView = getChildAt(
1
0
1
);
1
        
1
final
1
int
1
paddingLeft = getPaddingLeft();
1
        
1
final
1
int
1
paddingRight = getPaddingRight();
1
        
1
final
1
int
1
paddingTop = getPaddingTop();
1
        
1
final
1
int
1
paddingBottom = getPaddingBottom();
1
        
1
//没有子控件 时 我们的宽高要作特殊处理
1
        
1
if
1
(childCount ==
1
0
1
) {
1
            
1
//当没有子控件时,如果长宽有一个为wrap 那么就让这个控件以最小的形式展现
1
            
1
//这里我们最小设置为0
1
            
1
if
1
(widthSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.AT_MOST) {
1
                
1
setMeasuredDimension(minWidth, minHeight);
1
            
1
}
1
else
1
{
1
                
1
//否则根据我们的layout属性来
1
                
1
setMeasuredDimension(getLayoutParams().width, getLayoutParams().height);
1
            
1
}
1
        
1
}
1
else
1
if
1
(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
1
            
1
measureWidth = childView.getMeasuredWidth() * childCount;
1
            
1
measureHeight = childView.getMeasuredHeight();
1
            
1
setMeasuredDimension(paddingLeft + measureWidth + paddingRight, paddingTop + measureHeight + paddingBottom);
1
        
1
}
1
else
1
if
1
(heightSpecMode == MeasureSpec.AT_MOST) {
1
            
1
measureHeight = childView.getMeasuredHeight();
1
            
1
setMeasuredDimension(paddingLeft + paddingRight + widthSpecSize, paddingTop + paddingBottom + measureHeight);
1
        
1
}
1
else
1
if
1
(widthSpecMode == MeasureSpec.AT_MOST) {
1
            
1
measureWidth = childView.getMeasuredWidth() * childCount;
1
            
1
setMeasuredDimension(paddingLeft + paddingRight + measureWidth, paddingTop + paddingBottom + heightSpecSize);
1
        
1
}
1
    
1
}
1
    
1
@Override
1
    
1
protected
1
void
1
onLayout(
1
boolean
1
changed,
1
int
1
l,
1
int
1
t,
1
int
1
r,
1
int
1
b) {
1
        
1
final
1
int
1
paddingLeft = getPaddingLeft();
1
        
1
final
1
int
1
paddingRight = getPaddingRight();
1
        
1
final
1
int
1
paddingTop = getPaddingTop();
1
        
1
final
1
int
1
paddingBottom = getPaddingBottom();
1
        
1
//左边初始位置为0
1
        
1
int
1
childLeft =
1
0
1
+ paddingLeft;
1
        
1
final
1
int
1
childCount = getChildCount();
1
        
1
for
1
(
1
int
1
i =
1
0
1
; i < childCount; i++) {
1
            
1
final
1
View childView = getChildAt(i);
1
            
1
if
1
(childView.getVisibility() != View.GONE) {
1
                
1
final
1
int
1
childWidth = childView.getMeasuredWidth();
1
                
1
childView.layout(childLeft,
1
0
1
+ paddingTop, childLeft + childWidth, paddingTop + childView.getMeasuredHeight());
1
                
1
childLeft += childWidth;
1
            
1
}
1
        
1
}
1
    
1
}
1
}

风的记忆 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Android View 绘制 13 问 13 答
喜欢 (0)
[sendtion@126.com]
分享 (0)
sendtion
关于作者:
一个不断奋斗追逐梦想的少年~
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址