类似微信群聊九宫格头像的算法实现

在工作中遇到了一个开发的需求是将多选的图片聚合起来,类似于微信群聊那种九宫格的头像的那种。当然遇到这个需求首先肯定会从网上查找一些资料,发现大部分的实现类似于通过定义九宫格的ImageView控件来实现,这样一来,实际上图片没有被压缩,而且还生成了一大堆的控件,对内存性能效果是很大的。 既然网上没有合适好的算法方案,那不如自己来实现一个吧,通过分析头像的的形成的原理主要有如下的几个规律: 

1、当图片只有一个的时候,就直接显示这样大图

2、当图片在2-4个的时候,图片就会被分成2列,图片的尺寸大致是控件宽度的一半

3、当图片数量 >4 个是收,图片主要分为三列显示

这样一来就可以根据这样的规律来进行计算了。

我的算法原理是将这些图片通过算法进行组合到一起,形成一个新的Bitmap,这样你需要用的时候直接拿到这个Bitmap使用就可以了。

主要的核心算法如下:

//计算九宫格的图片
public Bitmap formatNineCellBitmap(List<Bitmap> bitmapList) {
    if (bitmapList == null || bitmapList.size() == 0) {
        return null;
    }

    int length = bitmapList.size();
    //最多显示9张
    if (length > 9) {
        length = 9;
    }

    int bitmapSize = builder.bitmapSize;
    //图片画板的内间距
    int paddingSize = builder.paddingSize;
    //每张图片之间的间距
    int itemMargin = builder.itemMargin;

    //每张需要绘制图片的宽高
    int cellSize;
    switch (length) {
        case 1:
            cellSize = bitmapSize - paddingSize * 2;
            break;
        case 2:
        case 3:
        case 4:
            cellSize = (bitmapSize - paddingSize * 2 - itemMargin) / 2;
            break;
        default: //默认是三列的图标展示
            cellSize = (bitmapSize - paddingSize * 2 - itemMargin * 2) / 3;
    }

    //画布
    Bitmap outBitmap = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(outBitmap);
    //先画合成之后的背景颜色,默认是白色
    canvas.drawColor(builder.backgroundColor);
    //这个主要是用来计算绘制图片的起始位置
    int left = paddingSize, top = paddingSize;
    
    int moveSize = cellSize + itemMargin;

    for (int i = 0; i < length; i++) {
        Bitmap dealBitmap = scaleAndCenterInsideBitmap(bitmapList.get(i), cellSize);
        if (dealBitmap != null) {
            switch (length) {
                case 1:
                    left = paddingSize;
                    top = paddingSize;
                    break;
                case 2:
                    left = paddingSize + moveSize * i;
                    top = (bitmapSize - cellSize) / 2;
                    break;
                case 3:
                    if (i == 0) {
                        left = (bitmapSize - cellSize) / 2;
                    } else {
                        left = paddingSize + moveSize * (i % 2);
                    }
                    top = paddingSize + moveSize * ((i + 1) / 2);
                    break;
                case 4:
                    left = paddingSize + moveSize * (i % 2);
                    top = paddingSize + moveSize * (i / 2);
                    break;
                case 5:

                    if (i <= 1) {
                        left = (bitmapSize - cellSize * 2 - paddingSize * 2) / 2 + moveSize * (i % 2);
                    } else {
                        left = paddingSize + moveSize * (i % 3);
                    }

                    top = paddingSize + (bitmapSize - cellSize * 2) / 2 + moveSize * ((i + 1) / 3);
                    break;
                case 6:
                    left = paddingSize + moveSize * (i % 3);
                    top = paddingSize + (bitmapSize - cellSize * 2) / 2 + moveSize * (i / 3);

                    break;
                case 7:
                    if (i == 0) {
                        left = (bitmapSize - cellSize - paddingSize * 2) / 2;
                    } else if (i <= 3) {
                        left = paddingSize + moveSize * ((i - 1) % 3);
                    } else {
                        left = paddingSize + moveSize * ((i - 1) % 3);
                    }

                    top = paddingSize + moveSize * ((i + 2) / 3);

                    break;
                case 8:
                    if (i <= 1) {
                        left = (bitmapSize - cellSize * 2 - paddingSize * 2) / 2 + moveSize * (i % 3);
                    } else if (i <= 4) {
                        left = paddingSize + moveSize * ((i - 2) % 3);
                    } else {
                        left = paddingSize + moveSize * ((i - 2) % 3);
                    }
                    top = paddingSize + moveSize * ((i + 1) / 3);
                    break;
                case 9:
                    left = paddingSize + moveSize * (i % 3);
                    top = paddingSize + moveSize * (i / 3);
                    break;
            }
            canvas.drawBitmap(dealBitmap, left, top, null);
        }
    }
    return outBitmap;
}



复制代码


//将图片缩放换成指定宽高,并且CenterInside模式
private Bitmap scaleAndCenterInsideBitmap(Bitmap sourceBitmap, int size) {
    float sourceWidth = sourceBitmap.getWidth();
    float sourceHeight = sourceBitmap.getHeight();

    float rate = sourceWidth / sourceHeight;
    float destRate = 1;

    Bitmap outBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(outBitmap);
    Bitmap resizeBitmap;
    Matrix matrix = new Matrix();
    if (rate < destRate) {
        //图片过高,需要裁掉部分高度
        float scale = size / sourceWidth;
        matrix.setScale(scale, scale);
        resizeBitmap = Bitmap.createBitmap(sourceBitmap, 0, 0, (int) sourceWidth, (int) sourceHeight, matrix, true);

        float cropHeight = (sourceHeight - sourceWidth) * scale;
        canvas.drawBitmap(resizeBitmap, 0, -cropHeight / 2, null);
    } else {
        //图片过宽,需要裁掉部分宽度
        float scale = size / sourceHeight;
        matrix.setScale(scale, scale);
        resizeBitmap = Bitmap.createBitmap(sourceBitmap, 0, 0, (int) sourceWidth, (int) sourceHeight, matrix, true);
        float cropWidth = (sourceWidth - sourceHeight) * scale;
        canvas.drawBitmap(resizeBitmap, -cropWidth / 2, 0, null);
    }
    return outBitmap;
}复制代码

上述是主要的核心算法,主要根据传入图片的数量进行计算来确定每张图片显示和摆放的位置,这个使用起来简单高效。


下面是算法的封装,将这个类直接复制到你的项目中就可以使用了。

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;

import java.util.ArrayList;
import java.util.List;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;

/***
 * @date 创建时间 2018/9/18 14:40
 * @author 作者: W.YuLong
 * @description 将图片聚合成9宫格
 */
public class NineCellBitmapUtil {
    private Builder builder;
    private NineCellBitmapUtil(Builder builder) {
        this.builder = builder;
    }

    public int getBitmapSize() {
        return builder.bitmapSize;
    }

    public static Builder with() {
        return new Builder();
    }

    /*这里用到了RXJava来将网络传入的图片URL或者Path转换层Bitmap,如果你还没有导入RXJava的话,可以将这个方法删除
    * 或者用你自己的方式将URL转换为Bitmap*/
    public <T> void collectBitmap(List<T> dataList, BitmapCallBack callBack) {
        Observable.create(new ObservableOnSubscribe() {
            @Override
            public void subscribe(ObservableEmitter e) throws Exception {
                for (T t : dataList) {
                    e.onNext(t);
                }
                e.onComplete();

            }
        }).map(new Function<T, Bitmap>() {
            @Override
            public Bitmap apply(T ts) throws Exception {
                return ImageLoadTool.transferBitmap(ts);
            }
        }).observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .subscribe(new Observer<Bitmap>() {
                    private List<Bitmap> resultList;

                    @Override
                    public void onSubscribe(Disposable d) {
                        if (resultList == null) {
                            resultList = new ArrayList<>();
                        }
                    }

                    @Override
                    public void onNext(Bitmap bitmap) {
                        resultList.add(bitmap);
                    }

                    @Override
                    public void onComplete() {
                        callBack.onLoadingFinish(formatNineCellBitmap(resultList));
                    }

                    @Override
                    public void onError(Throwable e) {
                    }
                });
    }


    //计算九宫格的图片
    public Bitmap formatNineCellBitmap(List<Bitmap> bitmapList) {
        if (bitmapList == null || bitmapList.size() == 0) {
            return null;
        }

        int length = bitmapList.size();
        //最多显示9张
        if (length > 9) {
            length = 9;
        }

        int bitmapSize = builder.bitmapSize;
        //图片画板的内间距
        int paddingSize = builder.paddingSize;
        //每张图片之间的间距
        int itemMargin = builder.itemMargin;

        //每张需要绘制图片的宽高
        int cellSize;
        switch (length) {
            case 1:
                cellSize = bitmapSize - paddingSize * 2;
                break;
            case 2:
            case 3:
            case 4:
                cellSize = (bitmapSize - paddingSize * 2 - itemMargin) / 2;
                break;
            default: //默认是三列的图标展示
                cellSize = (bitmapSize - paddingSize * 2 - itemMargin * 2) / 3;
        }

        //画布
        Bitmap outBitmap = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(outBitmap);
        //先画合成之后的背景颜色,默认是白色
        canvas.drawColor(builder.backgroundColor);
        //这个主要是用来计算绘制图片的起始位置
        int left = paddingSize, top = paddingSize;

        int moveSize = cellSize + itemMargin;

        for (int i = 0; i < length; i++) {
            Bitmap dealBitmap = scaleAndCenterInsideBitmap(bitmapList.get(i), cellSize);
            if (dealBitmap != null) {
                switch (length) {
                    case 1:
                        left = paddingSize;
                        top = paddingSize;
                        break;
                    case 2:
                        left = paddingSize + moveSize * i;
                        top = (bitmapSize - cellSize) / 2;
                        break;
                    case 3:
                        if (i == 0) {
                            left = (bitmapSize - cellSize) / 2;
                        } else {
                            left = paddingSize + moveSize * (i % 2);
                        }
                        top = paddingSize + moveSize * ((i + 1) / 2);
                        break;
                    case 4:
                        left = paddingSize + moveSize * (i % 2);
                        top = paddingSize + moveSize * (i / 2);
                        break;
                    case 5:

                        if (i <= 1) {
                            left = (bitmapSize - cellSize * 2 - paddingSize * 2) / 2 + moveSize * (i % 2);
                        } else {
                            left = paddingSize + moveSize * (i % 3);
                        }

                        top = paddingSize + (bitmapSize - cellSize * 2) / 2 + moveSize * ((i + 1) / 3);
                        break;
                    case 6:
                        left = paddingSize + moveSize * (i % 3);
                        top = paddingSize + (bitmapSize - cellSize * 2) / 2 + moveSize * (i / 3);

                        break;
                    case 7:
                        if (i == 0) {
                            left = (bitmapSize - cellSize - paddingSize * 2) / 2;
                        } else if (i <= 3) {
                            left = paddingSize + moveSize * ((i - 1) % 3);
                        } else {
                            left = paddingSize + moveSize * ((i - 1) % 3);
                        }

                        top = paddingSize + moveSize * ((i + 2) / 3);

                        break;
                    case 8:
                        if (i <= 1) {
                            left = (bitmapSize - cellSize * 2 - paddingSize * 2) / 2 + moveSize * (i % 3);
                        } else if (i <= 4) {
                            left = paddingSize + moveSize * ((i - 2) % 3);
                        } else {
                            left = paddingSize + moveSize * ((i - 2) % 3);
                        }
                        top = paddingSize + moveSize * ((i + 1) / 3);
                        break;
                    case 9:
                        left = paddingSize + moveSize * (i % 3);
                        top = paddingSize + moveSize * (i / 3);
                        break;
                }
                canvas.drawBitmap(dealBitmap, left, top, null);
            }
        }
        return outBitmap;
    }


    //将图片缩放换成指定宽高,并且CenterInside模式
    private Bitmap scaleAndCenterInsideBitmap(Bitmap sourceBitmap, int size) {
        float sourceWidth = sourceBitmap.getWidth();
        float sourceHeight = sourceBitmap.getHeight();

        float rate = sourceWidth / sourceHeight;
        float destRate = 1;

        Bitmap outBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(outBitmap);
        Bitmap resizeBitmap;
        Matrix matrix = new Matrix();
        if (rate < destRate) {
            //图片过高,需要裁掉部分高度
            float scale = size / sourceWidth;
            matrix.setScale(scale, scale);
            resizeBitmap = Bitmap.createBitmap(sourceBitmap, 0, 0, (int) sourceWidth, (int) sourceHeight, matrix, true);

            float cropHeight = (sourceHeight - sourceWidth) * scale;
            canvas.drawBitmap(resizeBitmap, 0, -cropHeight / 2, null);
        } else {
            //图片过宽,需要裁掉部分宽度
            float scale = size / sourceHeight;
            matrix.setScale(scale, scale);
            resizeBitmap = Bitmap.createBitmap(sourceBitmap, 0, 0, (int) sourceWidth, (int) sourceHeight, matrix, true);
            float cropWidth = (sourceWidth - sourceHeight) * scale;
            canvas.drawBitmap(resizeBitmap, -cropWidth / 2, 0, null);
        }
        return outBitmap;
    }


    public static class Builder {
        //画布宽度和高度
        private int bitmapSize = 300;
        //聚合后的图片内间距
        private int paddingSize = 10;
        //每张图片的间距
        private int itemMargin = 15;
        //聚合后图片的背景色
        private int backgroundColor = Color.WHITE;

        public Builder() {
        }

        public NineCellBitmapUtil build() {
            return new NineCellBitmapUtil(this);
        }

        public int getBackgroundColor() {
            return backgroundColor;
        }

        public Builder setBackgroundColor(int backgroundColor) {
            this.backgroundColor = backgroundColor;
            return this;
        }

        public int getBitmapSize() {
            return bitmapSize;
        }

        public Builder setBitmapSize(int bitmapSize) {
            this.bitmapSize = bitmapSize;
            return this;
        }

        public int getPaddingSize() {
            return paddingSize;
        }

        public Builder setPaddingSize(int paddingSize) {
            this.paddingSize = paddingSize;
            return this;
        }

        public int getItemMargin() {
            return itemMargin;
        }

        public Builder setItemMargin(int itemMargin) {
            this.itemMargin = itemMargin;
            return this;
        }
    }

    /***
     *@date 创建时间 2018/9/18 11:50
     *@author 作者: W.YuLong
     *@description 图片聚合完成之后的回调
     */
    public interface BitmapCallBack {
        /*处理完成*/
        void onLoadingFinish(Bitmap bitmap);
    }
}
复制代码


具体使用如下:

import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import com.hwariot.android.R;
import com.hwariot.android.tools.util.NineCellBitmapUtil;
import com.hwariot.lib.base.BaseActivity;

import java.util.ArrayList;
import java.util.List;

import butterknife.BindView;
import butterknife.ButterKnife;

/***
 * @date 创建时间 2018/9/19 11:30
 * @author 作者: W.YuLong
 * @description
 */
public class TestActivity extends BaseActivity implements View.OnClickListener {

    @BindView(R.id.test_ImageView) ImageView imageView;
    @BindView(R.id.test_add_Button) Button addButton;
    @BindView(R.id.test_remove_Button) Button removeButton;


    NineCellBitmapUtil nineCellBitmapUtil;

    String[] urlArray = {
            "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1538451017&di=dd252d5e4594f786d34891fb6be826ff&imgtype=jpg&er=1&src=http%3A%2F%2Fimg5.duitang.com%2Fuploads%2Fitem%2F201311%2F28%2F20131128101128_JZUaM.jpeg",
            "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1537856300317&di=e4aebfba49e34aa5bd8de8346b268229&imgtype=0&src=http%3A%2F%2Fs9.knowsky.com%2Fbizhi%2Fl%2F35001-45000%2F20095294542896291195.jpg",
            "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1537856300315&di=8def4a51ac362ffa7602ca768d76c982&imgtype=0&src=http%3A%2F%2Fg.hiphotos.baidu.com%2Fzhidao%2Fpic%2Fitem%2Ff9198618367adab44ce126ab8bd4b31c8701e420.jpg",
            "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1537345745067&di=d51264f2b354863c865a1da4b6672d90&imgtype=0&src=http%3A%2F%2Fpic40.nipic.com%2F20140426%2F6608733_175243397000_2.jpg",
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3394638573,2701566035&fm=26&gp=0.jpg",
            "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2758635669,3034136689&fm=26&gp=0.jpg",
            "http://t.cn/EvHONPF",
            "http://t.cn/EvHOTa9",
            "http://t.cn/EvHO38y",
    };

    private List<String> imgList = new ArrayList<>();
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_layout);
        ButterKnife.bind(this);
        // 实例化这个这个工具类,默认聚合的图片尺寸是1000像素,每张图的间距是20像素
        nineCellBitmapUtil = NineCellBitmapUtil.with().setBitmapSize(1000)
                .setItemMargin(20).setPaddingSize(20).build();

        addButton.setOnClickListener(this);
        removeButton.setOnClickListener(this);


    }

    private int i = 0;
    @Override
    public void onClick(View v) {
        if (v == addButton) {
            if (i < urlArray.length){
                imgList.add(urlArray[i]);
                i++;
                //调用这个方法进行聚合
                nineCellBitmapUtil.collectBitmap(imgList, new NineCellBitmapUtil.BitmapCallBack() {
                    @Override
                    public void onLoadingFinish(Bitmap bitmap) {
                        imageView.setImageBitmap(bitmap);
                    }
                });
            }

        } else if (v == removeButton) {
            if (i > 0){
                i--;
                imgList.remove(i);
                nineCellBitmapUtil.collectBitmap(imgList, new NineCellBitmapUtil.BitmapCallBack() {
                    @Override
                    public void onLoadingFinish(Bitmap bitmap) {
                        imageView.setImageBitmap(bitmap);

                    }
                });
            }
        }
    }
}复制代码


使用就是先实例化这个工具类

// 实例化这个这个工具类,默认聚合的图片尺寸是1000像素,每张图的间距是20像素
        nineCellBitmapUtil = NineCellBitmapUtil.with().setBitmapSize(1000)
                .setItemMargin(20).setPaddingSize(20).build();复制代码

然后调用这个聚合后的算法

//调用这个方法进行聚合
nineCellBitmapUtil.collectBitmap(imgList, new NineCellBitmapUtil.BitmapCallBack() {
    @Override
    public void onLoadingFinish(Bitmap bitmap) {
        imageView.setImageBitmap(bitmap);
    }
});           复制代码

这样就完成了,是不是使用起来非常简单方便高效!

下图就是展示的效果



转载于:https://juejin.im/post/5ba9ae2fe51d450e462840e5

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值