Android大图加载优化方案

news/2025/1/20 4:32:58/

我们在编写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;//设置图片格式:rgb565mOptions.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 {@Overrideprotected 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;//设置图片格式:rgb565mOptions.inPreferredConfig= Bitmap.Config.RGB_565;//用完需要关闭mOptions.inJustDecodeBounds=false;//区域解码器try {mDecoder = BitmapRegionDecoder.newInstance(is, false);} catch (IOException e) {e.printStackTrace();}//去调用onMeasure方法requestLayout();}//第三步 加载图片@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);mViewWidth = getMeasuredWidth();mViewHeight = getMeasuredHeight();//绑定图片加载区域//上边界为0mRect.top=0;//左边界为0mRect.left=0;//右边界为图片的宽度mRect.right=mImageWidth;//下边界为view的高度,在这里相当于手机的高度mRect.bottom=mViewHeight;}//第四步 画图@Overrideprotected 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);}//第五步 处理点击事件@Overridepublic boolean onTouch(View v, MotionEvent event) {//将Touch事件传递给手势return true;}@Overridepublic void onShowPress(MotionEvent e) {}@Overridepublic boolean onSingleTapUp(MotionEvent e) {return false;}@Overridepublic void onLongPress(MotionEvent e) {}
}

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

  //第五步 处理点击事件@Overridepublic boolean onTouch(View v, MotionEvent event) {//将Touch事件传递给手势return mGestureDetector.onTouchEvent(event);}//第六步 处理手势按下事件@Overridepublic boolean onDown(MotionEvent e) {//如果滑动没有停止就 强制停止if(!mScroller.isFinished()){mScroller.forceFinished(true);}//将事件进行传递,接收后续事件//因为在GestureDetector中,onDown方法是用于监听手指按下事件的,如果不返回true消费该事件,// GestureDetector就不会将后续的事件传递给其他的方法进行处理,// 包括滑动事件。因此,如果要实现按下手指后进行滑动图片的效果,需要在onDown方法中返回true进行消费。return true;}//第七步 处理滑动事件(手势)指手势的拖动//e1 开始事件//e2 即时事件也就是滑动时@Overridepublic 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;}

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

    //第八步 处理惯性问题(手势)指手势的滑动@Overridepublic 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;}//该方法可以获取当前的滚动值@Overridepublic void computeScroll() {super.computeScroll();//如果没有滚动,直接返回即可if(mScroller.isFinished()){return;}//如果已经滚动到新位置返回trueif(mScroller.computeScrollOffset()){mRect.top=mScroller.getCurrY();mRect.bottom=mRect.top+mViewHeight;//底部边框等于更新的top位置加上}invalidate();}

请添加图片描述

项目地址

github点击查看


http://www.ppmy.cn/news/488587.html

相关文章

MLB棒球发展中心人才培养计划·棒球1号位

MLB棒球发展中心是为了挖掘和培养有潜力的棒球人才而设立的&#xff0c;其人才培养计划主要包括以下几个方面&#xff1a; 一、选秀和培训 MLB棒球发展中心通过各种途径寻找有潜力的球员&#xff0c;包括高中、大学、独立联盟和国际市场。这些球员将接受专业的培训和指导&…

[大数据 Sqoop,hive,HDFS数据操作]

目录 &#x1f957;前言: &#x1f957;实现Sqoop集成Hive,HDFS实现数据导出 &#x1f957;依赖: &#x1f957;配置文件: &#x1f957;代码实现: &#x1f957;控制器调用: &#x1f957;Linux指令导入导出: &#x1f957;使用Sqoop将数据导入到Hive表中。例如&#…

今天给大家介绍一下苹果XR是多大尺寸的

作为当年的旗舰机型之一&#xff0c;苹果XR在硬件配置上有着相当不错的表现。除了上文提到的屏幕和尺寸以外&#xff0c;其它几个值得注意的方面包括&#xff1a; 1、处理器&#xff1a;苹果XR采用的是A12仿生芯片&#xff0c;这是苹果公司在2018年推出的一款高性能移动处理器。…

苹果xr配置_定了!苹果发布会9月11日

财经961 听财经&#xff0c;懂生活 即将进入九月&#xff0c;又要到苹果发布新iPhone的时候。今日&#xff0c;苹果正式发出秋季发布会邀请函&#xff1a;致创新。苹果2019秋季新品发布会将于当地时间9月10日上午10:00(北京时间9月11日凌晨1点)在美国加州举办。 苹果表示&#…

一路对标顶级产品,奇遇XR为何仍不见起色?

临近6月&#xff0c;再度遇冷的XR行业&#xff0c;又让很多人充满期待。外界普遍认为&#xff0c;基于苹果酝酿多年的MR头显产品&#xff0c;将于6月举行的WWDC 2023全球开发者大会正式亮相&#xff0c;XR行业或将迎来“iPhone时刻”。 在一派期待中&#xff0c;一家国内XR企业…

【编程语言 · C语言 · 指针和函数】

指针与函数 指针大大扩展了功能的可能性。我们不再局限于返回一个值。使用指针参数&#xff0c;你的函数可以更改实际数据&#xff0c;而不是数据副本。 要更改变量的实际值&#xff0c;调用语句将地址传递给函数中的指针参数。 例如&#xff0c;以下程序交换两个值&#xf…

传小米第一款车售价超30万元;苹果操作系统首次打败安卓,市场份额超过50%;TensorFlow 2.8.3 发布|极客头条

「极客头条」—— 技术人员的新闻圈&#xff01; CSDN 的读者朋友们早上好哇&#xff0c;「极客头条」来啦&#xff0c;快来看今天都有哪些值得我们技术人关注的重要新闻吧。 整理 | 梦依丹 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 一分钟速览新闻点&…

大数据相关常用软件下载地址集锦

由于大数据开发中经常需要用到Zookeeper、Hadoop、Spark、HBase、Kafka、Flume、Redis、Hive等软件&#xff0c;安装的时候需要它们的下载地址&#xff0c;这里就汇总一下&#xff0c;方便同学们查找。 一、软件下载地址如下 VMware下载地址&#xff1a;https://www.vmware.c…