美文网首页各种viewAndroid自定义ViewUI 效果
如何打造一个不掉帧的九宫格列表

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

作者: 皮球二二 | 来源:发表于2016-08-18 21:35 被阅读1185次

    我们在实现类似微博或者朋友圈的功能时,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

    相关文章

      网友评论

      • Jlanglang:recyclerview本来就支持这种布局😁
        皮球二二:@Jlanglang 我不能确定rv是否可以嵌套调用
      • 76bd968e8967:请问一下谁在 Fragment 上用了九宫格 ?
      • BoomHe:厉害 正好项目有这个 需求 :pray:

      本文标题:如何打造一个不掉帧的九宫格列表

      本文链接:https://www.haomeiwen.com/subject/rrnmsttx.html