自定义列表多图片ViewGroup

在列表中展示图片是非常常用的功能,微信、微博、qq都有同样的功能, 如果针对不同数量的图片采用不同的type将会有很多类型,会造成列表卡顿,RecyclerView中嵌套RecyclerView也是一种非常不好的方案。所以需要自定义ViewGroup解决这个问题。

1.创建

创建WeiboItemPicsView继承ViewGroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class WeiboItemPicsView extends ViewGroup implements View.OnClickListener, ImageInterface, Runnable {
public WeiboItemPicsView(Context context) {
super(context);
init(context);
}

public WeiboItemPicsView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public WeiboItemPicsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public WeiboItemPicsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
}

2.初始化

初始化, 主要完成子View的添加

1
2
3
4
5
6
7
8
9
10
private void init(Context context) {
addItemViews();
mSpaceWidth = getResources().getDimensionPixelSize(R.dimen.weibo_image_space);
}

protected void addItemViews() {
for (int i = 0; i < 9; i ++) {
addView(createImageView(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
}

3.测量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int availableWidth = width - getPaddingLeft() - getPaddingRight();
int height = 0;
if (mPicUrls != null && mPicUrls.size() != 0) {
if (mPicUrls.size() == 1) {
height = measureChildOnOneImage(availableWidth);
}else {
height = measureChildOnMultipleImage(availableWidth);
}
}
// LogUtil.d(this, "onMeasure width = %s height = %s", width, height);
height = height + getPaddingTop() + getPaddingBottom();
setMeasuredDimension(width, height);
}

MeasureSpec可以自行google,控件是根据手机宽度控制子View的宽高。

对于单张图片单独处理,需要根据图片宽高来控制View的宽高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
protected int measureChildOnOneImage(int availableWidth) {
View child = getChildAt(0);
int imageHeight = 0;
if (child != null && child.getVisibility() != GONE) {
int imageWidth;
PicUrl picUrl = mPicUrls.get(0);
if (picUrl.getHeight() > 0 && picUrl.getWidth() > 0) {
if (picUrl.getWidth() * 1.0f / picUrl.getHeight() < MAX_RADIO) { //宽比高小很多 竖着的图
imageWidth = (int) (availableWidth * 1.0f / 2);
imageHeight = (int) (imageWidth * 1.34f);
} else if (picUrl.getHeight() * 1.0f / picUrl.getWidth() < MAX_RADIO) {//宽比高大很多 横着的图
imageWidth = (int) (availableWidth * 1.0f / 3 * 2);
imageHeight = (int) (imageWidth / 1.34f);
} else { //接近正方形
imageWidth = (int) (availableWidth * 1.0f / 3 * 2);
imageHeight = imageWidth;
}
} else { //没有宽度信息就是默认正方形
imageWidth = (int) (availableWidth * 1.0f / 3 * 2);
imageHeight = imageWidth;
}
child.measure(MeasureSpec.makeMeasureSpec(imageWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(imageHeight, MeasureSpec.EXACTLY));
}
return imageHeight;
}

对于多张图片,大小都是控件大小的1/3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected int measureChildOnMultipleImage(int availableWidth) {
int defaultImageWidth = (availableWidth - 2 * mSpaceWidth) / 3;
int imageLine = mPicUrls.size() == 4 ? 2 : mPicUrls.size() / 4 + 1;
int height = defaultImageWidth * imageLine + (imageLine - 1) * mSpaceWidth;
for (int i = 0; i < getChildCount(); i ++) {
View child = getChildAt(i);

if (child.getVisibility() == View.GONE) {
continue;
}

child.measure(MeasureSpec.makeMeasureSpec(defaultImageWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(defaultImageWidth, MeasureSpec.EXACTLY));
}

return height;
}

4.定位

根据控件的Index确定View的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mPicUrls == null || mPicUrls.size() == 0)
return;
for (int i = 0; i < getChildCount(); i++) {
ItemImageView childView = (ItemImageView) getChildAt(i);
int startX = getPaddingLeft();
int startY = getPaddingTop();
if (childView.getVisibility() == View.GONE) {
continue;
}
if (i < mPicUrls.size()) {
if (mPicUrls.size() == 1) {
childView.layout(startX, startY, startX + childView.getMeasuredWidth(), startY + childView.getMeasuredHeight());
}else if (mPicUrls.size() == 4) {
int imageWidth = childView.getMeasuredWidth();
int line = i / 2; // 0 1 2
int column = i % 2;// 0 1 2
int left = startX + column * imageWidth + column * mSpaceWidth;
int top = startY + line * imageWidth + line * mSpaceWidth;
int right = left + imageWidth;
int button = top + imageWidth;
childView.layout(left, top, right, button);
// LogUtil.d(this, "item image index = %s left = %s top = %s right = %s button = %s",
// i, left, top, right, button);
}else {
int imageWidth = childView.getMeasuredWidth();
int line = i / 3; // 0 1 2
int column = i % 3;// 0 1 2
int left = startX + column * imageWidth + column * mSpaceWidth;
int top = startY + line * imageWidth + line * mSpaceWidth;
int right = left + imageWidth;
int button = top + imageWidth;
childView.layout(left, top, right, button);
// LogUtil.d(this, "item image index = %s left = %s top = %s right = %s button = %s",
// i, left, top, right, button);
}
}
}
}

5.加载图片

设置控件显示, 对于单张图片控件复用需要判断是否宽高相同。post runnable 这个很重要,因为加载图片需要在View测量完以后才会有大小数据,所以在通过这个方式使加载图片在View测量后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void setPics(List<PicUrl> picUrls) {
if (null == picUrls || picUrls.size() == 0) {
setVisibility(GONE);
}else {
setVisibility(VISIBLE);

for (int i = 0; i < getChildCount(); i++) {
if (i < picUrls.size()) {
getChildAt(i).setVisibility(VISIBLE);
}else {
getChildAt(i).setVisibility(GONE);
}
}

//当只有一张的时候复用 图片宽度是有变化 需要判断
boolean isNeedRequestLayout = false;
if (mPicUrls != null
&& mPicUrls.size() == picUrls.size()
&& mPicUrls.size() == 1
&& !isImageWidthAndHeightSame(mPicUrls.get(0), picUrls.get(0))) { //这个时候图片长度一样
isNeedRequestLayout = true;
}

if (isNeedRequestLayout && !isLayoutRequested()) {
requestLayout();
}

this.mPicUrls = picUrls;

//因为请求重新绘制requestLayout是通过主线程handler发送消息, 这个再通过handler发送消息展示图片就会在绘制以后
post(this);
}
}

源码地址