如何打造一个不掉帧的九宫格列表

2017-01-14 10:20:11来源:http://www.jianshu.com/p/d22db2ff419d作者:r17171709人点击


我们在实现类似微博或者朋友圈的功能时,RecyclerView或者ListView的复用问题是一个很头疼的问题,列表上多一个少一个让人很头疼。大多数人会选择简单粗暴的方式比如addView以及removeAllViews来处理,但是这样带来的问题是,滑动可能不是很流畅,特别是当你布局复杂度越高,绘制所需时间越多的时候,简直可以说卡爆了。那么我们有什么其他好办法来处理这个问题呢?今天我们就来一起来学习吧


项目已经发布在github上




九宫格列表示意图
前期准备

我们以上方的九宫格图片为例,我们要首先定义一些参数,比如图片间的间隙、每行图片的数量、整体九宫格列表的宽高,还有单张图片的宽高。


<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NineGridlayoutStyle">
<!-- 每张图片的间隙 -->
<attr name="space" format="dimension"></attr>
<!-- 每排有多少张图片 -->
<attr name="column_image" format="integer"></attr>
<!-- layout的宽度 -->
<attr name="total_width" format="dimension"></attr>
<!-- 只有单张图片时的宽度 -->
<attr name="oneimage_width" format="dimension"></attr>
</declare-styleable>
</resources>

定义NineGridlayout.java


Context context;
//layout的宽度
int width=0;
//每张图片的间隙
int space=0;
//每排有多少张图片
int column_image=0;
//只有单张图片时的宽度
int oneimage_width=0;
public NineGridlayout(Context context) {
this(context, null);
}
public NineGridlayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
this.context=context;
TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.NineGridlayoutStyle);
space=array.getDimensionPixelSize(R.styleable.NineGridlayoutStyle_space, 10);
column_image=array.getInteger(R.styleable.NineGridlayoutStyle_column_image, 3);
width=array.getDimensionPixelSize(R.styleable.NineGridlayoutStyle_total_width, 600);
oneimage_width=array.getDimensionPixelSize(R.styleable.NineGridlayoutStyle_oneimage_width, 260);
array.recycle();
}

九宫格内嵌图片控件

我采用Fresco作为九宫格的图片显示控件,这是动态生成SimpleDraweeView的方法,都是一些简单的处理


private SimpleDraweeView getSimpleDraweeView() {
GenericDraweeHierarchyBuilder builder=new GenericDraweeHierarchyBuilder(getResources());
RoundingParams params=new RoundingParams();
params.setBorder(Color.WHITE, 3);
params.setRoundAsCircle(true);
GenericDraweeHierarchy hierarchy=builder
.setPlaceholderImage(R.mipmap.ic_launcher)
.setPlaceholderImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
.setFailureImage(R.mipmap.ic_launcher)
.setFailureImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
.setRetryImage(R.mipmap.ic_launcher)
.setRetryImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
.setRoundingParams(params)
.build();
SimpleDraweeView simpleDraweeView=new SimpleDraweeView(context);
simpleDraweeView.setHierarchy(hierarchy);
return simpleDraweeView;
}

每一张图片的宽高

首先要明确当前视图中每一个图片的宽高,因为视图复用会这个值是会发生错乱的。没有图片就为0,一张图片宽高刚才说过了是个定值,多张图片其实也是一个定值,也就是单行中所有图片的平均值大小。同样这里计算过程中也要考虑到图片的间隙。


int itemWH;
//没有的时候宽高均为0
if (models.size()==0) {
itemWH=0;
}
//只有一个的时候重设置宽高
else if (models.size()==1) {
itemWH=oneimage_width-space*2;
}
else {
itemWH=(width-(column_image+1)*space)/column_image;
}

整体视图的宽高

九宫格视图只会有三种情况:有图片、有一张图片、有多张图片。没有图片的情况下,视图宽高均为0;有一张图片的时候,视图宽高为特定设置的值;当有多张图片的时候,视图宽度为列数*单张图片宽度,视图高度为行数*单张图片高度。其中还有间隙宽度高度不能忘记考虑


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//没有的时候宽高均为0
if (models.size()==0) {
itemWH=0;
}
//只有一个的时候重设置宽高
else if (models.size()==1) {
itemWH=oneimage_width-space*2;
}
else {
itemWH=(width-(column_image+1)*space)/column_image;
}
if (models.size()==0) {
setMeasuredDimension(0, 0);
}
else if (models.size()==1) {
setMeasuredDimension(oneimage_width, oneimage_width);
}
else {
//总行数
int row=(models.size()-1)/column_image+1;
//最大列数
int column=0;
if (column_image>models.size()) {
column=models.size();
}
else {
column=column_image;
}
//通过每个item的宽高计算出layout整体宽高
setMeasuredDimension(space*(column+1)+column*itemWH, space*(row+1)+row*itemWH);
}
}

设置图片控件的位置

遍历当前布局中的所有图片控件,进行异步加载,他们在布局中的位置根据他们所处的宽高分别定位


@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
if (models.get(i).getImage()==null) {
continue;
}
SimpleDraweeView imageView= (SimpleDraweeView) getChildAt(i);
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(models.get(i).getImage()))
.setResizeOptions(new ResizeOptions(itemWH, itemWH)).build();
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setImageRequest(request).setAutoPlayAnimations(true).build();
imageView.setController(draweeController);
int row=i/column_image+1;
int column=i%column_image+1;
int left=space*column + itemWH*(column-1);
int top=space*row + itemWH*(row-1);
int right=left+itemWH;
int bottom=top+itemWH;
imageView.layout(left, top, right, bottom);
}
}

加载图片

这边就是重头戏了,因为每次adapter中复用的时候,都是在调用的这个方法。当你是首次使用这个视图的时候,就直接大胆的往里面加,有多少加多少。当你复用的时候,就会出现2种情况,一种是新复用的地方图片比之前的多,那么就将不足的地方补充上去;另外一种情况就是新复用的地方图片比之前的少,这种情况就需要你将多余的图片控件删除。这样就得到当前应该显示的控件数量


ViewGroup.LayoutParams params=new LayoutParams(itemWH, itemWH);
//从来没有创建过
if (oldNum==0) {
for (NineGridImageModel model : models) {
SimpleDraweeView imageView=getSimpleDraweeView();
addView(imageView, params);
}
}
else {
//新创建的比之前的要少,则减去多余的部分
if (oldNum>models.size()) {
removeViews(models.size()-1, oldNum - models.size());
}
//新创建的比之前的要少,则添加缺少的部分
else if (oldNum<models.size()) {
for (int i = 0; i < models.size() - oldNum; i++) {
SimpleDraweeView imageView=getSimpleDraweeView();
addView(imageView, params);
}
}
}
oldNum=models.size();

讨论完有图片的情况,没有图片的情况也不能忘记,其实很简单,全部删除即可


removeAllViews();
oldNum=0;

怎么样,是不是很简单。重点就在于视图复用部分子view的添加与删除


使用

我建议,虽然我们的视图已经处理了无图片、单张图片以及多张图片的情况,但是UI可能会比较复杂,所以我建议还是用ItemViewType去处理


ListView的使用
public class MainAdapter extends BaseAdapter {
private final static int EMPTY_IMAGE=0;
private final static int ONE_IMAGE=1;
private final static int MORE_IMAGE=2;
private Context context;
private ArrayList<ArrayList<NineGridImageModel>> models;
public MainAdapter(Context context, ArrayList<ArrayList<NineGridImageModel>> models) {
this.context = context;
this.models = models;
}
@Override
public int getCount() {
return models.size();
}
@Override
public Object getItem(int position) {
return models.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (getItemViewType(position)==EMPTY_IMAGE) {
ViewHolder viewHolder;
if (convertView==null) {
viewHolder=new ViewHolder();
convertView=LayoutInflater.from(context).inflate(R.layout.adapter_empty, parent, false);
convertView.setTag(viewHolder);
}
else {
viewHolder= (ViewHolder) convertView.getTag();
}
}
else if (getItemViewType(position)==ONE_IMAGE) {
ViewHolder viewHolder;
if (convertView==null) {
viewHolder=new ViewHolder();
convertView=LayoutInflater.from(context).inflate(R.layout.adapter_one, parent, false);
viewHolder.one_image= (SimpleDraweeView) convertView.findViewById(R.id.one_image);
convertView.setTag(viewHolder);
}
else {
viewHolder= (ViewHolder) convertView.getTag();
}
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(models.get(position).get(0).getImage()))
.setResizeOptions(new ResizeOptions(Dp2Px(context, 250), Dp2Px(context, 250))).build();
DraweeController draweeController = Fresco.newDraweeControllerBuilder()
.setImageRequest(request).setAutoPlayAnimations(true).build();
viewHolder.one_image.setController(draweeController);
}
else if (getItemViewType(position)==MORE_IMAGE) {
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.adapter_more, parent, false);
viewHolder.adapter_ninelayout = (NineGridLayout) convertView.findViewById(R.id.adapter_ninelayout);
convertView.setTag(viewHolder);
}
else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.adapter_ninelayout.setImageData(models.get(position));
}
return convertView;
}
@Override
public int getItemViewType(int position) {
if (models.get(position).size()==0) {
return EMPTY_IMAGE;
}
else if (models.get(position).size()==1) {
return ONE_IMAGE;
}
else if (models.get(position).size()>1) {
return MORE_IMAGE;
}
return super.getItemViewType(position);
}
@Override
public int getViewTypeCount() {
return 3;
}
class ViewHolder {
NineGridLayout adapter_ninelayout;
SimpleDraweeView one_image;
}
public int Dp2Px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
}

RecyclerView的使用

如果对UI定制区别不是那么大的话,那么直接使用即可


public class RVAdapter extends RecyclerView.Adapter {
ArrayList<ArrayList<NineGridImageModel>> models;
Context context;
public RVAdapter(ArrayList<ArrayList<NineGridImageModel>> models, Context context) {
this.models = models;
this.context = context;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view=LayoutInflater.from(context).inflate(R.layout.adapter_more, parent, false);
return new RVMoreViewHolder(view);
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
((RVMoreViewHolder) holder).adapter_ninelayout.setImageData(models.get(position));
}
@Override
public int getItemCount() {
return models.size();
}
public class RVMoreViewHolder extends RecyclerView.ViewHolder {
NineGridLayout adapter_ninelayout;
public RVMoreViewHolder(View itemView) {
super(itemView);
adapter_ninelayout= (NineGridLayout) itemView.findViewById(R.id.adapter_ninelayout);
}
}
}

看看最终效果



最终效果
扩展

看到这里,不知道你有没有觉得跟传统九宫格列表比起来还少了一点什么,是的,单张图没有做到自适应图片宽高。这个其实也很好处理,具体就请参考我的开源工程吧


参考链接
panyiho/ NineGridView




最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台