需要实现一个相册的展示,默认最多显示九张,超过了就显示前九张。很常见的功能,比如新浪微博、微信等:
不打算再弄个新标题,就在图片加载的后面来个二级标题——九张图片相册的展示(微信微博等)。下面开始进入正题。
1、分析
选择的上面两张图片还是比较有代表性的,先来分析下他们。
a、九张图片3×3的显示,很自然的想到,其他的比如6张3×2,五张的话3+2的显示;比较特殊的就是四张图片显示时时2×2,而不是3+1的显示。
b、图片之间有间隔
c、点击图片跳到下一个界面展示
d、另外一点也是不定的一点,这个相册宽度要不要充满屏幕,还是固定宽度,这个就看自己需求咯。
用UI Automator Viewer查看,新浪的整个就是一个view,微信的是FrameLayout + n * view。网上搜了下,有没有现成的实现,貌似不好搜,不知道用什么关键字,带九的话都是关于.9图片的。找不到就算了,利用自己现在掌握的,从头写个吧。
第一个想到的还是自己去继承一个已经存在的布局,比如LinearLayout,TableLayout等。不过又想起周一的面试被鄙视的情形,没有自己画过一个view,都是继承、拼凑的,所以想,这次自己写吧。然后可能需要自己去画图片,实现监听,等等,代价很大,还是放弃咯。最后选择方案:继承ViewGroup,里面添加ImageView,这样不用关心单击事件和Image的绘制。
view上决定了剩下的就是处理这个控件对外的接口。设置一个监听,处理图片的点击事件;设置展示图片的数据源;在加几个小的设置,比如设置默认加载的图片size,加载图片之前的显示,加载失败的显示。设置图片的数据源,这个是传一个url还是Bitmap或者其他,最后选择了url,在内部实现图片的记载。所以为什么标题用了一个二级标题——还是用volley的ImageLoader实现图片的加载。在测试的时候发现,可能还需要自己去实现图片的本地保存,这个后面再说。
2、实现
直接贴代码吧,注释写的都比较详细:
/**
*
* 九张图片相册的展示
* http://blog.csdn.net/ttdevs
* @author ttdevs 2014年3月13日
*
*/
public class NineImageView extends ViewGroup implements OnClickListener {
/** 该view的item单击事件接口 */
public interface OnItemClickListener {
/**
* 单击回调,会把所有的url都返回,用户自己计算当前url在所有url中的位置
*
* @param mLists
* 相册所有的url
* @param url
* 当前被点击的url
*/
public void onItemClick(List<String> mLists, String url);
}
/** 图片的边距 */
private static final int ITEM_PADDING = 4;
/** 图片的默认尺寸 */
private static final int ITEM_WIDTH = 300;
private OnItemClickListener mListener;
private ImageLoader mImageLoader;
private List<ImageContainer> containerList; // 还没有充分的利用,用来取消请求
private List<ImageView> cacheViewList;
private List<ImageView> viewList;
private List<String> mLists;
private int mDefaultImageId;
private int mErrorImageId;
public NineImageView(Context context) {
this(context, null);
}
public NineImageView(Context context, AttributeSet attrs) {
super(context, attrs);
mImageLoader = VolleyQueue.getImageLoader(); // 拿到ImageLoader
cacheViewList = new ArrayList<ImageView>();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mLists != null && mLists.size() > 0) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width;
// 开始继续自己的高度
// 是宽的1倍(7-9张)还是1/3(1-3张)或者2/3(4-6张)
int size = mLists.size();
if (size <= 3) {
height = width / 3;
} else if (size <= 6) {
height = 2 * width / 3;
} else {
height = width;
}
setMeasuredDimension(width, height);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
// 发起请求,不知道这个地方是不是合适。
// 暂时没发现问题(所有请求都是异步的)
startReqeustImage(); // TODO
}
/** 发起网络请求 */
private void startReqeustImage() {
for (ImageView image : viewList) {
requestImageFromNetWork(image);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (mLists != null && mLists.size() > 0) {
int width = (right - left) / 3; // 每张图片的宽占整个父view的1/3
int height = width; // 宽和好一样,也就是一个正方形的图片
layoutItemImage(width, height);
}
}
/** 对相册中的图片进行布局 */
private void layoutItemImage(int width, int height) {
// 脑袋不好使,第一次写的很恶心,后来优化成这个样子
int size = mLists.size() >= 9 ? 9 : mLists.size(); // 只处理9条,大于9时只处理前9条
int mod = (size == 4) ? 2 : 3; // 4条的时候是2*2,其他都是3*X
int sLeft, sTop, sRight, sBottom;
for (int i = 0; i < size; i++) {
sLeft = (i % mod) * width + ITEM_PADDING;
sTop = (i / mod) * height + ITEM_PADDING;
sRight = ((i % mod) + 1) * width - ITEM_PADDING;
sBottom = ((i / mod) + 1) * height - ITEM_PADDING;
viewList.get(i).layout(sLeft, sTop, sRight, sBottom);
}
}
@Override
protected void onDetachedFromWindow() {
// TODO 这个地方不知道是否合适
if (containerList != null && containerList.size() > 0) {
for (ImageContainer container : containerList) {
container.cancelRequest();
}
}
super.onDetachedFromWindow();
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
invalidate();
}
/**
* 设置数据源:数据源的字符串集合
*
* @param lists
* 数据源
* @param defaultImage
* 默认显示的图片
* @param errorImage
* 加载失败时候显示的图片
*/
public void setImageList(List<String> lists, int defaultImage, int errorImage) {
if (defaultImage == 0 || errorImage == 0) {
return;
} else {
mDefaultImageId = defaultImage;
mErrorImageId = errorImage;
}
if (mLists == null) {
mLists = new ArrayList<String>();
} else {
mLists.clear();
}
mLists.addAll(lists);
if (viewList == null) {
viewList = new ArrayList<ImageView>();
} else {
viewList.clear();
}
if (containerList == null) {
containerList = new ArrayList<ImageContainer>();
} else {
for (ImageContainer container : containerList) {
container.cancelRequest();
}
containerList.clear();
}
int size = (mLists.size()) >= 9 ? 9 : mLists.size(); // 默认最多显示9个
// 这部分是用来实现对view的复用,
// 当某时刻的某个相册大于等于9就等同于初始化的时候就创建了9个ImageView
if (size > cacheViewList.size()) { // 维护自己的复用ImageView
int add = size - cacheViewList.size();
for (int i = 0; i < add ; i++) {
ImageView ivImage = new ImageView(getContext());
ivImage.setImageResource(mDefaultImageId);
ivImage.setOnClickListener(this);
ivImage.setScaleType(ScaleType.CENTER_CROP);
cacheViewList.add(ivImage);
}
}
for (int i = 0; i < size; i++) {
ImageView ivImage = cacheViewList.get(i);
ivImage.setImageResource(mDefaultImageId);
ivImage.setTag(mLists.get(i));
viewList.add(ivImage);
addView(ivImage);
}
invalidate();// requestLayout();
}
/** 请求网络图片 */
private void requestImageFromNetWork(final ImageView ivImage) {
int width = getMeasuredWidth();
width = (width == 0) ? ITEM_WIDTH : (width / 3);
ImageContainer container = mImageLoader.get((String) ivImage.getTag(), new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (mErrorImageId != 0) {
ivImage.setImageResource(mErrorImageId);
}
}
@Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
ivImage.setImageBitmap(response.getBitmap());
} else if (mDefaultImageId != 0) {
ivImage.setImageResource(mDefaultImageId);
}
}
}, width, width);
containerList.add(container);
}
/**
* 监听item的单击事件
*
* @param listener
*/
public void setOnItemClickListener(OnItemClickListener listener) {
mListener = listener;
}
/**
* item(imageview) onclick
*/
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onItemClick(mLists, (String) v.getTag());
}
}
}
3、总结
需要用到volley中的ImageLoader。剩下的就可以直接在你的布局中使用了。不熟悉view的整个处理流程,对部分资源的回收还不知道如何处理。等看明白了再回来优化。开始的时候提到可能要做图片缓存的情况,是因为在测试的时候发现网络加载图片很卡顿,不过我测试的都是1MB以上的图片。考虑到节约流量,可能需要在缓存的时候将图片保存到本地,避免下次去请求。
最终效果如下: