要优雅!Android中这样加载大图片和长图片

我们在做开发的时候总是会不可避免的遇到加载图片的情况,当图片的尺寸小于ImageView的尺寸的时候,我们当然可以很happy的去直接加载展示。

让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:国际域名空间、虚拟主机、营销软件、网站建设、噶尔网站维护、网站推广。

但是如果我们要加载的图片远远大于ImageView的大小,直接用ImageView去展示的话,就会带来不好的视觉效果,也会占用太多的内存和性能开销。

甚至这张图片足够大到导致程序oom崩溃。这个时候我们就需要对图片进行特殊的处理了:

要优雅!Android中这样加载大图片和长图片

一、图片压缩

图片太大,那我就想办法把它压缩变小呗。老铁,这思路完全没毛病。

BitmapFactory这个类就提供了多个解析方法(decodeResource、decodeStream、decodeFile等)用于创建Bitmap。

我们可以根据图片的来源来选择解析方法。

  • 比如如果图片来源于网络,就可以使用decodeStream方法;
  • 如果是sd卡里面的图片,就可以选择decodeFile方法;
  • 如果是资源文件里面的图片,就可以使用decodeResource方法等。
    这些方法会为创建的Bitmap分配内存,如果图片过大的话就会导致 oom。

BitmapFactory为这些方法都提供了一个可选的参数BitmapFactory.Options,用来辅助我们解析图片。这个参数有一个属性inSampleSize,这个属性可以帮助我们来进行图片的压缩。

为了解释inSampleSize的效果,我们可以举个栗子。
比如我们有一张2048 1536的图片,设置inSampleSize的值为4,就可以把这张图片压缩为512384,长短各缩小了4倍,所占内存就缩小了16倍。
这就明了了,inSampleSize的作用就是可以把图片的长短缩小inSampleSize倍,所占内存缩小inSampleSize的平方。
官方文档对于inSampleSize的值也做了一些要求,那就是inSampleSize的值必须大于等于1,如果给定的值小于1,那就默认为1。
而且inSampleSize的值需要是2的倍数,如果不是的话,就会自动变为离这个值向下最近的2的倍数的值,比如给定的值是3,那么最终 inSampleSize的值会是2。

当然了,这个inSampleSize的值我们也不可能随便就给,最好使我们能获取到照片的原始大小,再根据需要进行压缩。别急,谷歌都帮我们想好了!BitmapFactory.Options有一个属性inJustDecodeBounds,这个属性当为true的时候,表明我们当前只是为了获取当前图片的边界的大小,此时BitmapFactory的解析图片方法的返回值为 null,该方法是一个十分轻量级的方法。这样我们就可以很愉快的拿到图片大小了,代码如下:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 当前只为获取图片的边界大小BitmapFactory.decodeResource(getResources(), R.drawable.bigpic, options);int outHeight = options.outHeight;int outWidth = options.outWidth;
String outMimeType = options.outMimeType;

拿到了图片的大小,我们就可以根据需要计算出所需要压缩的大小了:

private int caculateSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {        int sampleSize = 1;        int picWidth = options.outWidth;        int picHeight = options.outHeight;        if (picWidth > reqWidth || picHeight > reqHeight) {            int halfPicWidth = picWidth / 2;            int halfPicHeight = picHeight / 2;            while (halfPicWidth / sampleSize > reqWidth || halfPicHeight / sampleSize > reqHeight) {
                sampleSize *= 2;
            }
        }        return sampleSize;
}

下面就是完整的代码:

        mIvBigPic = findViewById(R.id.iv_big_pic);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true; // 当前只为获取图片的边界大小
        BitmapFactory.decodeResource(getResources(), R.drawable.bigpic, options);        int outHeight = options.outHeight;        int outWidth = options.outWidth;
        String outMimeType = options.outMimeType;
        System.out.println("outHeight = " + outHeight + " outWidth = " + outWidth + " outMimeType = " + outMimeType);
        options.inJustDecodeBounds = false;
        options.inSampleSize = caculateSampleSize(options, getScreenWidth(), getScreenHeight());
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bigpic, options);
        mIvBigPic.setImageBitmap(bitmap);

这样图片压缩到这里就差不多结束了。

二、局部展示

有时候我们通过压缩可以取得很好的效果,但有时候效果就不那么美好了,例如长图像清明上河图,像这类的长图,如果我们直接压缩展示的话,这张图完全看不清,很影响体验。这时我们就可以采用局部展示,然后滑动查看的方式去展示图片。

Android里面是利用BitmapRegionDecoder来局部展示图片的,展示的是一块矩形区域。为了完成这个功能那么就需要一个方法设置图片,另一个方法设置展示的区域。

初始化
BitmapRegionDecoder提供了一系列的newInstance来进行初始化,支持传入文件路径,文件描述符和文件流InputStream等

例如:

mRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
上面这个方法解决了传入图片,接下来就要去设置展示区域了。

Bitmap bitmap = mRegionDecoder.decodeRegion(mRect, sOptions);
参数一是一个Rect,参数二是BitmapFactory.Options,可以用来控制inSampleSize,inPreferredConfig等。下面是一个简单的例子,展示图片最前面屏幕大的部分:

try {
        BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
        BitmapFactory.Options options1 = new BitmapFactory.Options();
        options1.inPreferredConfig = Bitmap.Config.ARGB_8888;
        Bitmap bitmap = regionDecoder.decodeRegion(new Rect(0, 0, getScreenWidth(), getScreenHeight()), options1);
        mIvBigPic.setImageBitmap(bitmap);
    } catch (IOException e) {
        e.printStackTrace();
    }

当然了,这只是最简单的用法,对于我们想要完全展示图片并没什么用!客官,稍安勿躁,前途已经明了!既然我们可以实现区域展示,那我们可不可以自定义一个View,可以随着我们的手指滑动展示图片的不同区域。yes! of course。那么我们就继续吧!

根据上面的分析,我们自定义控件的思路就很明白了:

提供一个设置图片的路口;
重写onTouchEvent,根据用户移动的手势,修改图片显示的区域;
每次更新区域参数后,调用invalidate,onDraw里面去regionDecoder.decodeRegion拿到bitmap,去draw
废话不多说,直接上代码:

public class BigImageView extends View {    private static final String TAG = "BigImageView";    private BitmapRegionDecoder mRegionDecoder;    private int mImageWidth, mImageHeight;    private Rect mRect = new Rect();    private static BitmapFactory.Options sOptions = new BitmapFactory.Options();
    {
        sOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
    }    public BigImageView(Context context) {        this(context, null);
    }    public BigImageView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);
    }    public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);
    }    public void setInputStream(InputStream inputStream) {        try {
            mRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = false;
            BitmapFactory.decodeStream(inputStream, null, options);
            mImageHeight = options.outHeight;
            mImageWidth = options.outWidth;
            requestLayout();
            invalidate();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }    int downX = 0;    int downY = 0;    @Override
    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getX();
                downY = (int) event.getY();                break;            case MotionEvent.ACTION_MOVE:                int curX = (int) event.getX();                int curY = (int) event.getY();                int moveX = curX - downX;                int moveY = curY - downY;
                onMove(moveX, moveY);
                System.out.println(TAG + " moveX = " + moveX + " curX = " + curX + " downX = " + downX);
                downX = curX;
                downY = curY;                break;            case MotionEvent.ACTION_UP:                break;
        }        return true;
    }    private void onMove(int moveX, int moveY) {        if (mImageWidth > getWidth()) {
            mRect.offset(-moveX, 0);
            checkWidth();
            invalidate();
        }        if (mImageHeight > getHeight()) {
            mRect.offset(0, -moveY);
            checkHeight();
            invalidate();
        }
    }    private void checkWidth() {
        Rect rect = mRect;        if (rect.right > mImageWidth) {
            rect.right = mImageWidth;
            rect.left = mImageWidth - getWidth();
        }        if (rect.left < 0) {
            rect.left = 0;
            rect.right = getWidth();
        }
    }    private void checkHeight() {
        Rect rect = mRect;        if (rect.bottom > mImageHeight) {
            rect.bottom = mImageHeight;
            rect.top = mImageHeight - getHeight();
        }        if (rect.top < 0) {
            rect.top = 0;
            rect.bottom = getWidth();
        }
    }    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int width = getMeasuredWidth();        int height = getMeasuredHeight();
        mRect.left = 0;
        mRect.top = 0;
        mRect.right = width;
        mRect.bottom = height;
    }    @Override
    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);
        Bitmap bitmap = mRegionDecoder.decodeRegion(mRect, sOptions);
        canvas.drawBitmap(bitmap, 0, 0, null);
    }
}

根据上述源码:

在setInputStream方法里面初始BitmapRegionDecoder,获取图片的实际宽高;

onMeasure方法里面给Rect赋初始化值,控制开始显示的图片区域;

onTouchEvent监听用户手势,修改Rect参数来修改图片展示区域,并且进行边界检测,最后invalidate;
在onDraw里面根据Rect获取Bitmap并且绘制。

最后

学习不是件简单的事,分享一下我们阿里p7架构师的学习路线

要优雅!Android中这样加载大图片和长图片

作为一个Android程序员,要学的东西也很多。

放出来自己整理好的Android学习内容帮助大家学习提升进阶

  • 面试题合集
  • 入门级书籍PDF:Java、c、c++
  • Android进阶精选书籍PDF
  • 阿里规范文档
  • Android开发技巧
  • 进阶PDF大全
  • 高级进阶视频
  • 源码
  • 算法学习视频
  • 未完待续

还有现在的学习趋势 flutter,kotin等的资料,都已经整理好,节省搜索的时间来学习

如果你有需要的话,可以 点赞+评论, 关注我,然后点击 了解详情

要优雅!Android中这样加载大图片和长图片
要优雅!Android中这样加载大图片和长图片
要优雅!Android中这样加载大图片和长图片

当前名称:要优雅!Android中这样加载大图片和长图片
当前链接:http://myzitong.com/article/iiphcs.html