Android大图加载优化方案

news2024/11/22 14:00:53

我们在编写Android程序的时候经常要用到许多图片,不同图片总是会有不同的形状、不同的大小,但在大多数情况下,这些图片都会大于我们程序所需要的大小。比如微博长图,海报等等。所以我们就要对图片进行局部显示。

大图加载基本需求和原理分析

在这里插入图片描述
基本需求:当我们有一张绿色大小的大图,我们需要让其展示成蓝色部分的大小,一般在我们滑动的过程中我们就只能看到蓝色部分的图片,蓝色部分的下面部分通过向下滑动才能看到。
原理分析:这里涉及到区域加载,由于我们人眼就只能看到占满手机屏幕大小的图片,蓝色部分下面部分是看不到的,这就意味着我们每次加载图片只需要加载到我们能看到的区域即可,看不到的区域就不加载。
在这里插入图片描述

假设我们的图片高度是手机的5倍,那我们首次加载其实就是图片的1/5,而我们不管继续往下滑,每次都加载图片的1/5,那么我们就能节省4/5的内存。
那么问题来了?我们如何做到区域加载和内存复用
在这里插入图片描述

比如我们讲图片分成了5份,我们每次都加载这1/5的内存,为了确保每次都加载1/5的内存,假设我们滑到了第二块区域,依然也是用我们加载第一块区域时的内存,不然的话就相当于我们把5份的内存都加载进去了,可能会造成OOM。

大图加载基础api解析

        //设置一个矩形区域(可以理解为矩形区域框定)
      Rect  mRect = new Rect();
        //用于内存复用(Google提供的对内存复用设置一些参数,比如设置编码格式)
      BitmapFactory.Options  mOptions = new BitmapFactory.Options();
        //手势支持
      GestureDetector  mGestureDetector = new GestureDetector(context, this);
        //滚动类
      Scroller  mScroller = new Scroller(context);
       //触摸时触发事件,比如触碰就停止屏幕滚动
      setOnTouchListener(this);

在这里插入图片描述
我们要将绿色大小的原图转换成手机屏幕大小的蓝图就需要对图片进行缩放,就需要获取图片大小等相关信息。但问题有来了,我们在获取图片宽高信息的时候不能把整个图片加载进来,不然我们内存复用就没意义,这个时候就用到了mOptions。

//inJustDecodeBounds方法,只加载边缘区域来获取图片宽高
        mOptions.inJustDecodeBounds=true;
            //将is传进去解码就能获取到图片的宽和高
        BitmapFactory.decodeStream(is,null,mOptions);
          //拿到宽和高
        mImageWidth = mOptions.outWidth;
        mImageHeight = mOptions.outHeight;
          //开启内存复用
        mOptions.inMutable=true;
        //设置图片格式:rgb565
        mOptions.inPreferredConfig= Bitmap.Config.RGB_565;
         //用完需要关闭
        mOptions.inJustDecodeBounds=false;

通过这种方式就能获取到图片的宽和高,并且没有将整张图片加载进内存

图片编码格式与占用内存之间关系

在这里插入图片描述

比如Glide使用的是RGB_565,Picasso使用的是ARGB_8888
当我们对一张图片进行无限的放大,你会发现它是由n个像素点组成,每个像素点都有自己的颜色,比如下面的图片就是有黑色,黄色和浅黄色
在这里插入图片描述
而当缩回去的时候就会发现是一张正常的图片,可以发现图片由像素点组成
在这里插入图片描述
像素点由RGB组成,三元色(红绿蓝)
而ARGB_8888表示图片中的像素有A,R,G,B四种颜色通道,每个通道占用内存为8位,共32位,相当于4个字节,也就是说每个像素点占用4个字节。
RGB_565相比于ARGB_8888少了A透明通道,表示的是R通道占5位,G通道占6位,B通道占5位,共16位,相当于2个字节,也就是说每个像素点占用2个字节。这样的话内存就相比于上面减少可一半。

大图加载之图片初始化展示实现

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        BigView bigView= findViewById(R.id.bigView);
        InputStream is=null;
        try {
            is= getResources().getAssets().open("test.jpg");
            bigView.setImage(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class BigView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {

    private Rect mRect;
    private BitmapFactory.Options mOptions;
    private GestureDetector mGestureDetector;
    private Scroller mScroller;
    private int mImageWidth;
    private int mImageHeight;
    private BitmapRegionDecoder mDecoder;
    private int mViewWidth;
    private int mViewHeight;
    private Bitmap mBitmap;
    private float mScaleX;
    private float mScaleY;

    public BigView(Context context) {
        this(context,null);
    }

    public BigView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public BigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);
    }

    public BigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        //第一步 设置BigView需要的成员变量
        //设置一个矩形区域(矩形区域框定)
        mRect = new Rect();
        //用于内存复用(设置编码格式)
        mOptions = new BitmapFactory.Options();
        //手势支持
        mGestureDetector = new GestureDetector(context, this);
        //滚动类
        mScroller = new Scroller(context);
        //触摸时触发事件
        setOnTouchListener(this);
    }


    //第二步设置图片
    public void setImage(InputStream is){
        //获取图片的宽和高
        //此时不能将整张图片加载进来,这样内存复用无意义,需要使用inJustDecodeBounds方法,只加载部分区域来获取图片宽高
        mOptions.inJustDecodeBounds=true;
        //将is传进去解码就能获取到图片的宽和高
        BitmapFactory.decodeStream(is,null,mOptions);

        mImageWidth = mOptions.outWidth;
        mImageHeight = mOptions.outHeight;

        //开启内存复用
        mOptions.inMutable=true;

        //设置图片格式:rgb565
        mOptions.inPreferredConfig= Bitmap.Config.RGB_565;

        //用完需要关闭
        mOptions.inJustDecodeBounds=false;

        //区域解码器
        try {
            mDecoder = BitmapRegionDecoder.newInstance(is, false);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //去调用onMeasure方法
        requestLayout();
    }

    //第三步 加载图片
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mViewWidth = getMeasuredWidth();
        mViewHeight = getMeasuredHeight();
        //绑定图片加载区域
        //上边界为0
        mRect.top=0;
        //左边界为0
        mRect.left=0;
        //右边界为图片的宽度
        mRect.right=mImageWidth;
        //下边界为view的高度,在这里相当于手机的高度
        mRect.bottom=mViewHeight;
    }

    //第四步 画图
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mDecoder==null){
            return;
        }
        //内存复用
        //复用inBitmap这块的内存(每次滚动重新绘制都会复用这块内存,达到内存复用)
        mOptions.inBitmap=mBitmap;
       
        mBitmap=mDecoder.decodeRegion(mRect,mOptions);
     
        //计算缩放因子
        mScaleX = mViewWidth / (float) mImageWidth;
   
        mScaleY = mViewHeight / (float) mImageHeight;
      
        //得到矩阵缩放
        Matrix matrix = new Matrix();
        matrix.setScale(mScaleX, mScaleX);//如果matrix.setScale(mScaleX, mScaleY)则图片会在充满在当前的view的x和y轴
        canvas.drawBitmap(mBitmap,matrix,null);
    }

    //第五步 处理点击事件
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //将Touch事件传递给手势
        return true;
    }
  
    @Override
    public void onShowPress(MotionEvent e) {

    }

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

    @Override
    public void onLongPress(MotionEvent e) {

    }
}

大图加载之图片滚动功能实现

  //第五步 处理点击事件
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //将Touch事件传递给手势
        return mGestureDetector.onTouchEvent(event);
    }

    //第六步 处理手势按下事件

    @Override
    public boolean onDown(MotionEvent e) {
        //如果滑动没有停止就 强制停止
        if(!mScroller.isFinished()){
            mScroller.forceFinished(true);
        }
        //将事件进行传递,接收后续事件
        //因为在GestureDetector中,onDown方法是用于监听手指按下事件的,如果不返回true消费该事件,
        // GestureDetector就不会将后续的事件传递给其他的方法进行处理,
        // 包括滑动事件。因此,如果要实现按下手指后进行滑动图片的效果,需要在onDown方法中返回true进行消费。
        return true;
    }

    //第七步 处理滑动事件(手势)指手势的拖动
    //e1 开始事件
    //e2 即时事件也就是滑动时
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        //上下滑动时,直接改变Rect的显示区域
        mRect.offset(0,(int) distanceY);//上下滑动只需要改变Y轴
        //判断到顶和到底的情况
        if(mRect.bottom>mImageHeight){//滑到最底
            mRect.bottom=mImageHeight;
            mRect.top=mImageHeight-mViewHeight;
        }
        if(mRect.top<0){//滑到最顶
            mRect.top=0;
            mRect.bottom=mViewHeight;
        }
        invalidate();
        return true;
    }

大图加载之图片惯性滚动功能实现

    //第八步 处理惯性问题(手势)指手势的滑动
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        //velocityY表示Y轴的惯性值,startX和startY为滑动的开始位置,minY和maxY为滑动距离的最小值和最大值
        mScroller.fling(0,mRect.top,0,(int) -velocityY,0,0,0,mImageHeight-mViewHeight);
        return false;
    }

    //该方法可以获取当前的滚动值
    @Override
    public void computeScroll() {
        super.computeScroll();
        //如果没有滚动,直接返回即可
        if(mScroller.isFinished()){
            return;
        }
        //如果已经滚动到新位置返回true
        if(mScroller.computeScrollOffset()){
            mRect.top=mScroller.getCurrY();
            mRect.bottom=mRect.top+mViewHeight;//底部边框等于更新的top位置加上
        }
        invalidate();
    }

请添加图片描述

项目地址

github点击查看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/667806.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【QQ界面展示-监听键盘事件 Objective-C语言】

一、关于这个通知,我们就说到这里, 1.接下来,就看一下, 我们说了这么一堆,目的是为了什么, 目的是为了监听我们那个键盘的点击事件吧, 我们说了一堆,目的是为了监听我们这个键盘的弹出事件、不是点击事件, 当键盘弹出以后,我们是不是要做一件事儿, 那么,我们知道…

虚拟机网卡/网络配置,静态IP配置

文章目录 1. Vmvare设置 “编辑->虚拟机网络编辑”2. 新建一个虚拟机并给它设置网卡3. 配置eth0网卡为静态IP vim /etc/sysconfig/network-scripts/ifcfg-eth04、测试 1. Vmvare设置 “编辑->虚拟机网络编辑” 这里设置了3个虚拟网络(两个主机模式&#xff0c;这两个网络…

2023.6.20 GPIO子系统编写LED驱动

作业&#xff1a;通过GPIO子系统编写LED驱动&#xff0c;应用程序控制LED灯亮灭 &#xff08;1&#xff09;led.h #ifndef __LED_H__ #define __LED_H__ // typedef struct{ // unsigned int MODER; // unsigned int OTYPER; // unsigned int OSPEEDR; // un…

FreeRTOS实时操作系统(五)临界区及任务调度器

系列文章目录 文章目录 系列文章目录临界区代码保护任务调度器的挂起与保护 临界区代码保护 临界区&#xff1a;是指那些必须要完整运行的&#xff0c;不能被打断的代码 适用于&#xff1a; 1.外设初始化 2.操作系统的代码有很多不能被打断 3.用户自己的需求 一般在中断、任…

014 - STM32学习笔记 - I2C访问存储器(一)

014 - STM32学习笔记 - I2C访问存储器 1、存储器分类 存储器主要分为两类&#xff1a;易失性存储器和非易失性存储器&#xff0c;从字面上理解&#xff0c;判断易失/非易失主要取决于设备掉电后&#xff0c;存储的数据是否会丢失。常规的来说&#xff0c;易失性存储器存取速度…

Django基础入门⑥:Django过滤器和标签讲解

Django基础入门⑥&#xff1a;Django过滤器和标签讲解 Django过滤器过滤器语法过滤器应用获取变量的长度截取指定个数的词返回指定键的排序列表add给变量值加“n” Django url标签url标签动态url Django自定义标签如何自定义标签定义之前的准备工作模块变量register自定义标签赋…

Java 对接google WIFI定位API

1.创建Http请求工具类 1.1.引入httpclient <dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.10</version></dependency> 1.2.封装Http工具类 /*** Http请求* a…

MySQL 高级语句 一

目录 一、MySQL高级&#xff08;进阶&#xff09;SQL语句1. select2. distinct3. where4. and or5. in6. between7. 通配符8. order by9. 函数9.1 数学函数9.2 聚合函数9.3 字符串函数 二、高级查询语句2.1 group by &#xff08;用于分组和汇总&#xff09;2.2 having2.3 别名…

如何在 XMind 中绘制流程图

XMind 是专业强大的思维导图软件,由于其结构没有任何限制,很多朋友特别喜欢用它来绘制流程图。禁不住大家的多次询问,今天 XMind 酱就将这简单的流程图绘图方法分享给大家。 在 XMind 中,绘制流程图的主角是「自由主题」和「联系」。它们可以打破思维导图的限制,让你自由…

《异常检测——从经典算法到深度学习》21 Anomaly Transformer:具有关联差异的时间序列异常检测

We # 《异常检测——从经典算法到深度学习》 0 概论1 基于隔离森林的异常检测算法 2 基于LOF的异常检测算法3 基于One-Class SVM的异常检测算法4 基于高斯概率密度异常检测算法5 Opprentice——异常检测经典算法最终篇6 基于重构概率的 VAE 异常检测7 基于条件VAE异常检测8 Do…

[NX亲测有效]Ubuntu,Jetson nano,NX板开机设置开机自起,Jetson nano,NX设置x11vnc开机自起

&#xff01;&#xff01;Ubuntu,Jetson nano,NX板开机设置开机自起&#xff0c;nano NX设置x11vnc开机自起&#xff01;&#xff01; 1.创建一个rc-local自启服务 2.创建运行脚本 3.启动服务 4.NX&#xff0c;nano设置x11vnc并设置开机自启 大功告成&#xff01;编写不易…

【中级软考】软件设计-考试介绍

一、软考好处 通过软考认证可以抵扣当年的 3600 元的个税,并且有些城市可以积分落户,同时获得证书可以获得同等级别的职称。计算机方向的职称是以考代评,所以获得中级软考证书就相当于获得同等的中级计算机工程师职称,获得高级软考证书就相当于获得同等的高级计算机工程师…

如何使用@umijs/plugin-qiankun搭建微前端项目

umijs/plugin-qiankun是一个基于UmiJS框架的插件&#xff0c;用于实现乾坤微前端架构。乾坤微前端是一种前端架构模式&#xff0c;可以将一个大型的前端应用拆分成多个小型的子应用&#xff0c;每个子应用可以独立开发、独立部署、独立运行&#xff0c;同时可以通过乾坤框架进行…

ArduPilot开源代码之AP_InertialSensor

ArduPilot开源代码之AP_InertialSensor 1. 源由2. AP_InertialSensor类2.1 init2.2 periodic2.3 update 3. 重要应用方法3.1 BatchSampler::push_data_to_log3.2 wait_for_sample3.2 calibrate_gyros 4. 总结5. 参考资料 1. 源由 前面研读了IMU如何通过front-end/back-end获取…

基于SpringBoot+kaptcha的验证码生成

教程 1.添加 Kaptcha 依赖 在 pom.xml 文件中添加 Kaptcha 依赖&#xff1a; <dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version> </dependency> <!--或者 都…

【深度学习 | 机器学习】干货满满 | 近万字总结了 12 个令人惊艳的神经网络可视化工具!

文章目录 一、神经网络可视化难点在哪里&#xff1f;二、神经网络可视化方法三、神经网络可视化解释的工具3.1 draw_convnet3.2 NNSVG3.3 PlotNeuralNet3.4 Tensorboard3.5 Caffe3.6 Matlab3.7 Keras.js3.8 DotNet3.9 Graphviz3.10 ConX3.11 ENNUI3.12 Neataptic 神经网络可视化…

linux下Python的学习(五)用matplotlib画iostat的折线图

之前写测试脚本的时候习惯用shell来写&#xff0c;只不过用AWK(个人觉得awk是shell的精髓)处理表格的时候比Python复杂的多&#xff0c;因为Python有很多现成的库&#xff0c;直接调用就可以。下面用Python自带的matplotlib函数实现一下对iostat log的采样 比如有一个记录iosta…

第一章 数据可视化简介(复习)

第一章 数据可视化简介 什么是可视化 定义&#xff1a;通过可视表达增强人们完成某些 任务的效率 The American Heritage Dictionary&#xff1a; The act or process of interpreting in visual terms or of putting into visible form&#xff08;用可视形式进行解释的 动作…

做性能测试必须掌握的基本概念和套路

目录 性能优化的常见概念 性能调优的思路 1.性能摸底测试 2.定义性能优化的目标 3.分析 针对内存&#xff1a; OOM&#xff1a; 针对IO&#xff1a; 文件IO&#xff1a; 总结&#xff1a; 经常听到人说&#xff0c;做个性能优化&#xff0c;吞吐量越高越好&#xff1…

2023 年开发者必须知道的 6 个 AI 工具

自Chat GPT发布以来&#xff0c;AI在各个领域都出现了令人惊艳的产品&#xff0c;在编程方面也是如此。这些由 AI 驱动的工具使用算法快速准确地生成代码&#xff0c;从而节省程序员的时间和精力。虽然目前AI写出来的代码还不能完全替代人类&#xff0c;但开发人员完全可以作为…