自定义大图加载控件(内存复用,分块加载)

如果自己没有大图,那请自己手机截长屏,这就是大图!!!
[项目github地址](https://github.com/fenchen31/bigImageView)

**效果:**话不多说,先上效果图(上视频太麻烦,干脆截两张图好了)注意第二张是滑动之后的效果哈:
在这里插入图片描述
在这里插入图片描述

**概念:**大图无外乎两个概念,1.图片体积大,2.图片像素点多,如果将大图直接加载进内存,可能会直接引起oom,这是对大图加载进行优化的直接原因。而优化方案无外乎两个方面:内存复用和分块加载。
分块加载:假设一张大图有100M是我们在屏幕上看到的只有10M,那对于我们来说只要这10M就可以,这时候我们只需要截取原图中的一部分进行加载即可
内存复用:开启内存复用是指在该图片的加载过程中,不管我们怎么滑动图片,使用的始终是内存中的同一块地方。
大致流程:
1.用options将图片按照安卓屏幕比例进行压缩(其中包括确定图片加载格式以及开启内存复用)
2.用rect确定解码区域,然后传入decoder进行解码
3.用canvas进行绘制
优化: 1.在格式方面采用RGB_565而不适用ARGB_8888本身就是一种优化
2.应该避免在onDraw方法里面创建临时变量,因为ondraw是一个会被频繁调用的方法,创建的临时变量会迅速占据内存,引起频繁gc,甚至是内存抖动(内存抖动:内存频繁回收与分配)
**最后贴上全部代码:
MyBigImageView文件

package com.test.bigimageview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;

public class MyBigImageView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {

    private Scroller mScroll;
    private Rect mRect;
    private int pictureWidth,pictureHeight;//图片宽高
    private int viewWidth,viewHeight;//控件宽高
    private BitmapFactory.Options mOptions;
    private GestureDetector mGestureDetector;
    private BitmapRegionDecoder mDecoder;//解码器
    private float mScale;//压缩比例
    private Bitmap mBitmap;
    private Matrix matrix;//图片压缩类
    public MyBigImageView(Context context) {
        this(context,null);
        if (isInEditMode())
            return;
    }
    /*
    *   注意:因为在xml文件中使用自定义控件的时候调用的是第二个构造方法,所以该构造方法
    *   必须被重写,否则会直接报错
    * */
    public MyBigImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyBigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mRect = new Rect();
        mOptions = new BitmapFactory.Options();
        mScroll = new Scroller(context);
        matrix = new Matrix();
        mGestureDetector = new GestureDetector(this);
        setOnTouchListener(this);
    }
    public void setPicture(InputStream inputStream){
        //  在不把图片加载进内存的情况下获取图片的宽和高
        mOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(inputStream,null,mOptions);
        pictureWidth = mOptions.outWidth;
        pictureHeight = mOptions.outHeight;
        //  开启内存复用
        mOptions.inMutable = true;
        /*
        *注:在计算机中,图片是由像素点组成,而像素点又是由三原色(红绿蓝,即RGB)组成,
        *   565代表的是每个颜色的像素点占多少位,而565格式表示每个像素点占据16位,即两个字节。
        *   ARGB中的A表示透明度。
        *内存优化:从内存优化的角度上来说,采用RGB_565替代ARGB_8888节省了一半的存储空间,
        *   从一定角度上来说本身就是一种内存优化,同时也提高了性能
        * */
        //  设置图片格式
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        //  注意:这句话必须是和mOptions.inJustDecodeBounds=true代码成对出现,可以理解为将其改为true之后在使用完又复原
        mOptions.inJustDecodeBounds = false;
        //  创建解码区域
        try {
            mDecoder = BitmapRegionDecoder.newInstance(inputStream,false);
        } catch (IOException e) {
            e.printStackTrace();
        }
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //  1.获取控件宽高
        viewHeight = getMeasuredHeight();
        viewWidth = getMeasuredWidth();
        //  2.确定加载区域
        mRect.top = 0;
        mRect.left = 0;
        mRect.right = viewWidth;
        mScale = viewWidth/(float)pictureWidth;
        mRect.bottom = (int) (viewHeight/mScale);
        //  3.按照图片和屏幕比例,对图片开始压缩
        matrix.setScale(mScale,mScale);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mDecoder == null)
            return;
        //  解析加载区域
        mBitmap = mDecoder.decodeRegion(mRect,mOptions);
        mOptions.inBitmap = mBitmap;
        // 绘制
        canvas.drawBitmap(mBitmap,matrix,null);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return mGestureDetector.onTouchEvent(event);
    }

    @Override
    public boolean onDown(MotionEvent e) {
        //  如果图片由于惯性仍然在滑动,当手指又放到屏幕上时,上一次的滑动应该被停止
        if (!mScroll.isFinished())
            mScroll.forceFinished(true);
        //  在由于惯性任然在滑动,我们又将手指放了上去,这时又摆动手指滑动,所以应该返回true
        return true;
    }

    @Override
    public void onShowPress(MotionEvent e) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        mRect.offset(0, (int) distanceY);
        if (mRect.bottom > pictureHeight) {
            mRect.bottom = pictureHeight;
            mRect.top = (int) (pictureHeight - viewHeight/mScale);
        }
        if (mRect.top < 0){
            mRect.top = 0;
            mRect.bottom = (int) (viewHeight/mScale);
        }
        invalidate();//通过调用该方法去触发onDraw,对区域进行重新绘制
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {

    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return false;
    }
}

xml文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.test.bigimageview.MyBigImageView
        android:id="@+id/bigView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <ImageView
        android:id="@+id/image"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity文件

package com.test.bigimageview;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;

public class MainActivity extends AppCompatActivity {
    private MyBigImageView bigView;
    private ImageView image;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bigView = findViewById(R.id.bigView);
        image = findViewById(R.id.image);
        //  内存复用模式加载大图
        try {
            InputStream inputStream = getAssets().open("bigpicture.png");
            bigView.setPicture(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        /*//  传统方式加载大图,注意:因为本项目中图片尺寸较大,可能会引起oom,闪退请查看log日志
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bigpicture, null);
        image.setImageBitmap(bitmap);*/
    }


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值