RecyclerView实现错落有致的图片布局

4,127 阅读3分钟

生活不止有眼前的苟且,还有诗和远方。不,还有未来的苟且和将就。不停留于目前的苟且,为了不在当前做一条咸鱼,那么需要翻个身,一点点努力,总有咸鱼翻身之时。

前不久公司要实现类似下图的效果

一看到效果图,首先就想到应该获取宽和高,然后一行动态拼接两张图,那么如何使两张图搞好拼接成一行,并且高度一样呢。

我们来计算一下: 一行有两张图,第一张图的宽w1、高h1,第二张图的宽w2,高h2,屏幕的宽为sw(screen width)。 假设算出来第一张图为w,第二张图宽度为sw-w,而且两张图高度一致,所以有以下公式:

 w*h1/w1 = h2/w2*(sw-w)

一个公式只有一个未知变量,所以可以求解出w的值

接下来就是代码实现过程,我们考虑用RecyclerView 去实现,一开始打算用瀑布流去实现。但是考虑用瀑布流去实现,但是后来发觉瀑布流支持 item 占据总列数的宽度,不支持只占据其中几列。假如图片一张宽度很小,一张很大如何去实现呢(如果有大神知道如何实现,请不吝指导!)

后来去星球提问,有网友推荐用Google的flex布局管理器,咋一看,这不是前端的flex布局吗?还真别说,就是就是前端的流式布局,连用法都很像。这里就不介绍用法了,想学习的话推荐其他网友的博客!Android 流式布局-FlexboxLayout与RecyclerView

一、FlexboxLayout实现 错落有致布局

效果如下:源代码地址

  1. 首先引入流式布局
 implementation 'com.google.android:flexbox:0.3.0-alpha2'//流式布局
  1. 初始化布局
  private void initView() {
        gallleryRecyclerView = findViewById(R.id.gallery_recyclerview);
        mFlexboxLayoutManager = new FlexboxLayoutManager();
        mFlexboxLayoutManager.setFlexDirection(FlexDirection.ROW);
        mFlexboxLayoutManager.setJustifyContent(JustifyContent.SPACE_BETWEEN);
        mFlexboxLayoutManager.setAlignItems(AlignItems.FLEX_START);
        gallleryRecyclerView.setLayoutManager(mFlexboxLayoutManager);

    }

gallleryRecyclerView 定义Recyclew,mFlexboxLayoutManager为流式布局管理类,mFlexboxLayoutManager.setFlexDirection(FlexDirection.ROW) 设置主轴为水平方向,起点在左端,从左到右; mFlexboxLayoutManager.setAlignItems(AlignItems.FLEX_START)顶端对齐;gallleryRecyclerView.setLayoutManager(mFlexboxLayoutManager);把管理类赋值给Recyclerview.

  1. 定义一组美女图片
 private void initData() {
        imageList = new ArrayList<>(Arrays.asList(R.mipmap.girl1,R.mipmap.girl2,R.mipmap.girl3,R.mipmap.girl4,R.mipmap.girl5
                ,R.mipmap.girl6,R.mipmap.girl7,R.mipmap.girl8,R.mipmap.girl9));
        mImageBeanList = new ArrayList<>();
        for (int i=0;i<imageList.size();i++){
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(getResources(),imageList.get(i),options);
            int width = options.outWidth;
            int height = options.outHeight;
            ImageBean imageBean = new ImageBean();
            imageBean.setResourceId(imageList.get(i));
            imageBean.setWidth(width);
            imageBean.setHeight(height);
            mImageBeanList.add(imageBean);
        }
        mFlexBoxAdapter = new FlexBoxAdapter(FlexboxActivity.this,mImageBeanList);
        gallleryRecyclerView.setAdapter(mFlexBoxAdapter);
        gallleryRecyclerView.setHasFixedSize(false);
    }

赋值给FlexBoxAdapter

  1. 定义Adaper
public class FlexBoxAdapter extends RecyclerView.Adapter<FlexBoxAdapter.ViewHolder> {
    private List<ImageBean> mPicturesBeanList;
    private Context mContext;
    private float screenWidth;

    public FlexBoxAdapter(Context context, List<ImageBean>  imageBeans) {
        this.mPicturesBeanList = imageBeans;
        this.mContext = context;
        filterData();
    }

    private void filterData() {
        screenWidth = (float) ScreenUtils.getScreenWidth(mContext);
        int i = 0;
        while (i < this.mPicturesBeanList.size()) {
            if (i % 2 == 0 && i + 1 < this.mPicturesBeanList.size()) {
                long divisor = (long) (screenWidth * (this.mPicturesBeanList.get(i + 1).getHeight()) * (this.mPicturesBeanList.get(i).getWidth()));
                long beDivisor = this.mPicturesBeanList.get(i + 1).getWidth() * this.mPicturesBeanList.get(i).getHeight() + this.mPicturesBeanList.get(i).getWidth() * this.mPicturesBeanList.get(i + 1).getHeight();
                int w = (int) (divisor / beDivisor);
                int h = w * this.mPicturesBeanList.get(i).getHeight() / this.mPicturesBeanList.get(i).getWidth();
                this.mPicturesBeanList.get(i).setChangeWidth(w);
                this.mPicturesBeanList.get(i).setChangeHeight(h);

                int tW = (int) (screenWidth - screenWidth * this.mPicturesBeanList.get(i + 1).getHeight() * this.mPicturesBeanList.get(i).getWidth() / (this.mPicturesBeanList.get(i + 1).getWidth() * this.mPicturesBeanList.get(i).getHeight() + this.mPicturesBeanList.get(i).getWidth() * this.mPicturesBeanList.get(i + 1).getHeight()));
                int tH = tW * this.mPicturesBeanList.get(i + 1).getHeight() / this.mPicturesBeanList.get(i + 1).getWidth();
                this.mPicturesBeanList.get(i + 1).setChangeWidth(tW);
                this.mPicturesBeanList.get(i + 1).setChangeHeight(tH);
            }

            if (i == mPicturesBeanList.size() - 1 && mPicturesBeanList.size() % 2 == 1) {
                int w = (int) screenWidth;
                int h = w * mPicturesBeanList.get(i).getHeight() / mPicturesBeanList.get(i).getWidth();
                this.mPicturesBeanList.get(i).setChangeWidth(w);
                this.mPicturesBeanList.get(i).setChangeHeight(h);
            }
            i++;
        }
    }


    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
     ......
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
    ......
    }


    @Override
    public int getItemCount() {
        return this.mPicturesBeanList.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        private ImageView mImageView;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            mImageView = itemView.findViewById(R.id.mine_photo_iv);
        }
    }
}

filterData()这个方法动态计算图片适合的宽高,使两张图片刚好铺满一行。

想看完整代码可以到源代码地址

完美的实现出来了,打开wegame,准备召唤师峡谷见。但是产品一个电话突然打过来,要加头部和底部布局,这。。。flex布局如何自定义头部和底部呢,而且头部要占一行,底部也要占一行,苦思冥想,而且flex布局用的也不多。要是是网格布局多好啊,直接自定义头部和底部,轻松搞定召唤师峡谷搞起。

网格布局 GridLayoutManager 有个setSpanSizeLookup()方法,可以指定item占几行,同一行的高度也是一样。和我现在的需求就是宽度要如何设置。

GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3, LinearLayoutManager.HORIZONTAL, false);
//自定义item占据的小格大小时需要重写 getSpanSize(),返回值就是占据的小格数量
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {    
        //以下代码仅为上图示例为写,具体场景中应该根据需求具体编写
        if (position == 1) {
            return 2;
        }
        if (position == 2) {
            return 3;
        }
         return 1;
    }

像这样就能指定一行3个Item,根据position来指定item所占宽度。

二、GirdLayoutt实现 错落有致布局

不得不说,灵感这种东西,不止诗人、歌手需要,有时候代码世界也需要灵光一现。那我为何不设置屏幕宽度为总的个数呢?图片宽度为多少,就是多少格呢?

 mGirdLayoutManager = new GridLayoutManager(this, ScreenUtils.getScreenWidth(GridLayoutActivity.this));
 
gallleryRecyclerView.setLayoutManager(mGirdLayoutManager);
  1. 看下GridLayoutManager的定义
  /**
     * Creates a vertical GridLayoutManager
     *
     * @param context Current context, will be used to access resources.
     * @param spanCount The number of columns in the grid
     */
    public GridLayoutManager(Context context, int spanCount) {
        super(context);
        setSpanCount(spanCount);
    }

spanCount就是个数多少,现在定义为ScreenUtils.getScreenWidth(GridLayoutActivity.this),即屏幕的宽度。

  1. 网格布局的adater
package com.lqg.Image;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;


import java.util.List;

public class GalleryAdapter extends RecyclerView.Adapter<GalleryAdapter.ViewHolder> {
    private List<ImageBean> mPicturesBeanList;
    private Context mContext;
    public static final int TYPE_HEADER = 0;
    public static final int TYPE_NORMAL = 1;
    public static final int TYPE_BOTTOM = 2;
    private View mHeaderView;
    private View mBottomView;
    private float screenWidth;


    public GalleryAdapter(Context context, List<ImageBean> imageBeans) {
        this.mContext = context;
        this.mPicturesBeanList = imageBeans;
        filterData();
    }


    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    return getItemViewType(position) == TYPE_HEADER || getItemViewType(position) == TYPE_BOTTOM
                            ? gridManager.getSpanCount() : mPicturesBeanList.get(position - 1).getChangeWidth();
                }
            });
        }
    }



    @Override
    public int getItemViewType(int position) {
        if (mHeaderView == null) return TYPE_NORMAL;
        if (mBottomView == null) return TYPE_NORMAL;
        if (position == 0) return TYPE_HEADER;
        if (position == this.getItemCount() - 1) return TYPE_BOTTOM;
        return TYPE_NORMAL;
    }


    private void filterData() {
      ......
    }


    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        if (mHeaderView != null && i == TYPE_HEADER) return new ViewHolder(mHeaderView);
        if (mBottomView != null && i == TYPE_BOTTOM) return new ViewHolder(mBottomView);
        View view = LayoutInflater.from(mContext).inflate(R.layout.adater_details_works_item, null, false);
        ViewHolder viewHolder = new ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, final int i) {
    ......
    }

    public int getRealPosition(RecyclerView.ViewHolder holder) {
        int position = holder.getLayoutPosition();
        return mHeaderView == null ? position : position - 1;
    }


    @Override
    public int getItemCount() {
        return mPicturesBeanList.size() + 2;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
       ......
    }

}

关键看

gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    return getItemViewType(position) == TYPE_HEADER || getItemViewType(position) == TYPE_BOTTOM
                            ? gridManager.getSpanCount() : mPicturesBeanList.get(position - 1).getChangeWidth();
                }
            });

根据item类型,获取item的格数,头部和底部各占一行。正常图片的item根据计算好的图片宽度获取个数。 最终效果如下:

想看完整代码可以到源代码地址

做的过程中,学习了别人的知识点,果然站在巨人的肩膀才能看的远!

关于RecyclerView你知道的不知道的都在这了(上)

Android 流式布局-FlexboxLayout与RecyclerView