最近在学习Android的View和ViewGroup的绘制流程,以及自定义View的过程,对于每一个Android的学习者来说,这都是一个比较大的门槛,但是你真的值得去这样做,因为在学习自定义View的过程中还真的是能学到很多有用的东西,对综合能力会有很大的提升。
我把我的学习笔记记录下来,方便其他的新手入门使用,因为有很多的知识点,对于刚刚接触自定义View的同学来说是很难理解的,我把常用的技巧记录下来,也方便我以后查阅。
上一篇讲述了如何自定义一个View,总体来说整个流程是比较简单的,其大致的思路就是:
- 继承自View
- 重写onDraw方法
- 递增动画进度,递增后调用postInvalidateDelayed()方法来控制View的播放动画
而关于自定义ViewGroup就要用到更多的方法了。这里主要用到onMeasure和onLayout方法,关于这两个方法,网上讲解的也有很多,例如洪洋大神的这篇博客,但是大多数都是从一个很高的层面上来讲的,我等渣渣并不能真正的理解怎么具体来重写这两个方法来实现我们想要的效果。
关于onMeasure()方法
其实就是通过综合自身控件的大小需求和子控件的大小需求来设定当前控件的宽和高,常用到的东西有:
setMeasuredDimension(int w,int h)
方法:设定当前控件的宽和高如下这个模式:遍历ViewGroup的所有的子控件,然后综合所有的子控件来处理设定当前的ViewGroup的宽和高
int childCount = this.getChildCount(); for (int i = 0; i < childCount; i++) { View child = this.getChildAt(i); this.measureChild(child, widthMeasureSpec, heightMeasureSpec); int cw = child.getMeasuredWidth(); // int ch = child.getMeasuredHeight(); }
MeasureSpec.getSize(widthMeasureSpec);
和MeasureSpec.getMode(widthMeasureSpec);
通过传入WidthMeasureSpec,我们就可以得到当前ViewGroup控件的父控件对当前控件的一些要求,以及当前ViewGroup的长宽设定的Mode- 同时,在onMeasure方法中对子View进行遍历的时候,也可以加入对每一个子View的布局参数的要求,此时对于LayoutParams参数,其实就像是一个bundle,在后面的onLayout方法中可以用到,例如:
1.
LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
layoutParams.left = (i % 3) * (childWidth + hSpace);
layoutParams.top = (i / 3) * (childWidth + vSpace);
关于onLayout()方法
这个方法是在onMeasure方法之后会执行的,在此时,通过onMeasure方法已经把当前ViewGroup的宽高确定好了,这个时候就是对子View来进行遍历,并开始子View的布局了。可能经常会用到如下的东西:
1.对子View遍历并布局
int childCount = this.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = this.getChildAt(i);
//取出在onMeasure方法中存储的那个LayoutParams,在这里来调用子View的Layout方法的时候当做参数传入
LayoutParams lParams = (LayoutParams) child.getLayoutParams();
child.layout(lParams.left, lParams.top, lParams.left + childWidth,
lParams.top + childHeight);
}
好了,大致的思想就是这样,我这里没有具体讲到每一步细节,后面通过一个学习的案例来说吧。
在学习的过程中,写了一个类似于QQ空间加载图片的画廊工具,并提供简化了下载setUrl方法,使用的时候仅仅提供图片的Url数组就能自动的从网上下载图片并显示,并且在显示的过程中,会根据图片的长宽比例和大小来自调整布局中图片的大小。
使用如下:
String[] urls1 = {"http://upload.jianshu.io/collections/images/13199/Unnamed_QQ_Screenshot20150315121101.png?imageMogr2/auto-orient/strip|imageView2/2/w/180",
"http://upload.jianshu.io/collections/images/2150/3.png?imageMogr2/auto-orient/strip|imageView2/2/w/180",
"http://qzapp.qlogo.cn/qzapp/100410602/240190197CC910A185BA32398359D4C5/100",
"http://upload.jianshu.io/users/upload_avatars/654319/c54c8e001748.jpeg?imageMogr/thumbnail/90x90/quality/100",
"http://qzapp.qlogo.cn/qzapp/100410602/D3CDD72BBDF11AE46CB5260830C67958/100",
"http://wx.qlogo.cn/mmopen/ehNBaYGR6m30BKiayZTsM2Kd1DZf207Dt741Pvh4X3Rpd3zXOddHIiaj6yPuiaKxqdR4hpXk4gF09lwbQfib0fh9uvVHvpwkLcLk/0"};
downloaderGallery1.setUrls(urls1);
下面是效果图,支持根据图片的大小来调整布局
下面大体概况一下:
STEP 1 继承自ViewGroup 实现onMeasure方法
在onMeasure方法中要添加等待下载时候的图片,这个图片要添加多少个呢?其实是按照我们给定的Url的个数来确定的,这样才能达到有等待下载的效果。其核心的方法如下:
//to add subview to the layout
for (int i = 0; i < size; i++) {
ImageView view = new ImageView(context);
addView(view);
//对每一个子View设定布局,这个项目中是默认是每行显示3个,如果是只有一张图片则放大显示
measureChild(view, widthMeasureSpec, heightMeasureSpec);
LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
layoutParams.left = (i % 3) * (childWidth + hSpace);
layoutParams.top = (i / 3) * (childWidth + vSpace);
}
需要注意的是,这里的这个LayoutParams,是我在项目中定义的一个继承了ViewGroup.LayoutParams的类,所以在这里我们可以加入一个子View的left和top的位置,而right和bottom没有加入的原因是可以通过和字View的长宽来计算得到right和bottom。具体的细节请看代码。
STEP 2 实现onLayout方法,对子View来进行布局并对外部留出对点击事件的监听
for (int i = 0; i < size; i++) {
ImageView child = (ImageView) this.getChildAt(i);
LayoutParams lParams = (LayoutParams) child.getLayoutParams();
child.setBackgroundColor(waittingColor);
child.layout(lParams.left, lParams.top, lParams.left + childWidth,
lParams.top + childHeight);
final int finalI = i;
child.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
imageItemListener.onItemClick(finalI);
}
});
}
关于如果判断图片的大小的信息,看代码吧。。
项目代码在这里:项目代码