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

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

项目已经发布在github上

如何打造一个不掉帧的九宫格列表_第1张图片
九宫格列表示意图

前期准备

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



    
        
        
        
        
        
        
        
        
    

定义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

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

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> models;

    public MainAdapter(Context context, ArrayList> 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> models;
    Context context;
    public RVAdapter(ArrayList> 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

你可能感兴趣的:(如何打造一个不掉帧的九宫格列表)