Android 自定义View 二

时间:2023-02-09 20:45:51

在上一篇文章android 自定义view 中我们总结了自定义View的几大步骤如下:

1、自定义View的属性

2、在View的构造方法中获得我们自定义的属性

3、重写onMesure方法

4、重写onLayout方法

5、重写onDraw方法

因为上篇文章中我们完成了LabelImageView中的一个LabelView子view,所以不用重写onLayout方法,下面接着上篇文章我们来完成LabelImageView这个ViewGroup控件。既然LabelImageView是一个ViewGroup,那么我们就必须自己决定其子控件的布局,我们的子控件就是一个ImgeView和许多的LabelView。而onLayout就是给子控件布局的地方。在上代码前,我们先看看效果:

Android 自定义View 二

上面的效果就是在ImageView里面可以添加我们自己的标签,这个在某些APP里面可以应用到,下面是LabelImageView的onLayout方法代码:

 @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if(changed){
int childCount = getChildCount();
for(int i=0;i<childCount;i++){
View v = getChildAt(i);
if(v instanceof ImageView){ //布局ImageView
v.layout(l+getPaddingLeft(),t+getPaddingTop(),r-getPaddingRight(),b-getPaddingBottom());
}else if(v instanceof LabelView){//布局LabelView
LabelData labelData = ((LabelView)v).getLabelData();
float pos[] = labelData.getPosition();
int cl = (int) (getWidth()*pos[0]);
int ct = (int) (getHeight()*pos[1]);
int cr = cl+ v.getMeasuredWidth();
int cb = ct+ v.getMeasuredHeight();
v.layout(cl,ct,cr,cb);
}
}
}
}

其实就是获取子控件,然后调用子控件的layout方法进行布局,注意这个changed属性,google官方api里说当view有新的size或position时changed为true,但当我用动画改变一个view的大小或位置时,onlayout方法是不会调用的,用setLayoutParams重新设置View的宽高,changed确实会为true。


在android开发时,有时我们需要得到某个View的宽高,最简单的方法就是调用View的getHeight()和getWidth()了,但是使用时很多时候都发现他们返回的是0,得不到我们想要的值。下面打印下activity的启动流程来分析下:

Android 自定义View 二

只有在activity调用了onAttachToWindow后,才会开启view的onMeasure方法,测量view的大小,只有在调用了view的onlayout方法后,view才有真正的宽高,view的getWidth()和getHeight()才能得到真的的值。所以我们在activity的onResume方法里及其前面的生命周期里都无法得到view的宽高。如果我们必须要得到view的宽高呢,有办法么?答案当然是有,下面介绍三种常见的方法:

方法一:

//手动调用测量方法。 制定测量规则 参数表示size + mode
int width =View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
int height =View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
mLabelImageView.measure(width,height);
//调用measure方法之后就可以获取宽高。
height=mLabelImageView.getMeasuredHeight();
width=mLabelImageView.getMeasuredWidth();
Log.e(TAG,"----------- measure width: "+ width + " height: " + height);

注意,measure()方法是实际测量的方法,而在绘制布局过程中调用的onMeasure()只是制定测量规则. 在自定义布局中我们一般重写onMeasure()方法,measure()方法是final的,子类无法重写。 measure函数有2个参数,int widthMeasureSpec 和 int heightMeasureSpec表示具体的测量规则。 
这两个数值不是普通的数值, 它表示: size + mode ,例如: 
int widthMeasureSpec= View.MeasureSpec.makeMeasureSpec(1000,View.MeasureSpec.EXACTLY); 
int heightMeasureSpec= View.MeasureSpec.makeMeasureSpec(1000,View.MeasureSpec.AT_MOST); 
模式分为: 
View.MeasureSpec.EXACTLY:表示父视图希望子类的大小是specSize中制定的大小. 
View.MeasureSpec.AT_MOST:父试图希望子类的大小最高不超过specSize中制定的大小. 
View.MeasureSpec.UNSPECIFIED:父试图不对子类实施任何限制,子试图可以得到自己想得到的任意大小. 

值得一提的是View.MeasureSpec.UNSPECIFIED 其值为0,所以前面可以简写成:view.measure(0,0),但是值得注意的是,这个只是测量值,一般情况下view的测量值宽高跟真实值宽高是一致的,但是也有不一样的情况,比如ImageView设置不同的scaleType,测量值跟真实值有可以就不一样。

方法二(设置View树桩结构监听器):

  mLabelImageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onGlobalLayout() {
mLabelImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int h = mLabelImageView.getHeight();
int w = mLabelImageView.getWidth();
Log.e(TAG,"----------- onGlobalLayout width: "+ w + " height: " + h);
}
});

方法三(增加组件绘制之前的监听):

   mLabelImageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mLabelImageView.getViewTreeObserver().removeOnPreDrawListener(this);
int h = mLabelImageView.getHeight();
int w = mLabelImageView.getWidth();
Log.e(TAG,"----------- onPreDraw width: "+ w + " height: " + h);
return false;
}
});
下面看看打印信息:

Android 自定义View 二
   如上正确打印出了view的宽高,推荐使用后面两种方式。

未完待续!

代码github: https://github.com/nickyangjun/NickStudyDemo

更多精彩Android技术可以关注我们的微信公众号,扫一扫下方的二维码或搜索关注公共号: Android老鸟
                                                Android 自定义View 二