Android自定义无压缩加载超清大图
自定义无压缩加载超清大图
前言
已经很久没有写博客了,前段时间做项目就遇到加载超大图时系统内存溢出,我们一般处理加载图片时OOM的方法都是对图片进行压缩。但是发现手机系统相册是可以打开大图的,今天就分享一波自定义无压缩加载超清大图。
BitmapRegionDecoder
BitmapRegionDecoder
用来解码一张图片的某个矩形区域,通常用于加载某个图片的指定区域。通过调用该类提供的一系列newInstance(...)
方法可获得BitmapRegionDecoder
对象,该类提供的主要构造方法如下:
获取该对象后我们可以通过decodeRegion(rect,mOptions)
方法传入需要显示的指定区域,就可以得到指定区域的Bitmap
。这个方法的第一个参数就是要显示的矩形区域,第二个参数是BitmapFactory.Options
(这个
类是BitmapFactory对图片进行解码时使用的一个配置参数类,其中定义了一系列的public成员变量,每个成员变量代表一个配置参数。)
自定义控件
要自定义这个控件,我们主要分以下几个步骤:
1. 提供一个图片入口
2. 自定义手势监听,通过手势上下左右滑动,更新显示图片的区域
3. 自定义显示指定区域图片,即通过手势滑动传入的区域显示大图在该区域的内容
自定义加载大图控件(LargeImageView)
package com.example.bthvi.bigpictureloading; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Canvas; import android.graphics.Rect; import android.media.ExifInterface; import android.os.Build; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import java.io.IOException; import java.io.InputStream; /** * 自定义加载超大图片的View * * create by bthvi on 2018/06/29 */ public class LargeImageView extends View { /** *用来解码一张图片的某个矩形区域 */ private BitmapRegionDecoder mDecoder; /** * 图片的宽度和高度 */ private int mImageWidth,mImageHeight; /** *绘制图片区域 */ private volatile Rect rect = new Rect(); /** * 手势监听器 */ private MoveGestureDetector moveGestureDetector; /** * 图片解码时的参数配置类 */ private static final BitmapFactory.Options mOptions = new BitmapFactory.Options(); /** * 图片解码时所用的颜色模式 */ static { mOptions.inPreferredConfig = Bitmap.Config.RGB_565; } /** * 构造方法 * @param context */ public LargeImageView(Context context) { super(context); initView(); } public LargeImageView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(); } public LargeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } @Override public boolean onTouchEvent(MotionEvent event) { moveGestureDetector.onToucEvent(event); return true; } @Override protected void onDraw(Canvas canvas) { Bitmap bm = mDecoder.decodeRegion(rect, mOptions); canvas.drawBitmap(bm, 0, 0, null); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMeasuredWidth(); int height = getMeasuredHeight(); int imageWidth = mImageWidth; int imageHeight = mImageHeight; //默认直接显示图片的中心区域,可以自己去调节 rect.left = imageWidth / 2 - width / 2; rect.top = imageHeight / 2 - height / 2; rect.right = rect.left + width; rect.bottom = rect.top + height; } /** * 初始化 */ private void initView() { moveGestureDetector = new MoveGestureDetector(getContext()); SimpleMoveGestureDetector simpleMoveGestureDetector = new SimpleMoveGestureDetector(){ @Override public boolean onMove(MoveGestureDetector detector){ int moveX = (int) detector.getMoveX(); int moveY = (int) detector.getMoveY(); if (mImageWidth > getWidth()){ rect.offset(-moveX,0); checkWidth(); invalidate(); } if (mImageHeight > getHeight()) { rect.offset(0, -moveY); checkHeight(); invalidate(); } return true; } }; moveGestureDetector.setOnMoveGestureListener(simpleMoveGestureDetector); } public void setInputStream(InputStream is) { try { mDecoder = BitmapRegionDecoder.newInstance(is, false); BitmapFactory.Options tmpOptions = new BitmapFactory.Options(); // Grab the bounds for the scene dimensions tmpOptions.inJustDecodeBounds = true; BitmapFactory.decodeStream(is, null, tmpOptions); /** * 获取图片的宽高 */ mImageWidth = mDecoder.getWidth(); mImageHeight = mDecoder.getHeight(); requestLayout(); invalidate(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (is != null) is.close(); } catch (Exception e) { } } } private void checkWidth() { Rect rect2 = rect; int imageWidth = mImageWidth; if (rect2.right > imageWidth){ rect2.right = imageWidth; rect2.left = imageWidth - getWidth(); } if (rect2.left < 0){ rect2.left = 0; rect2.right = getWidth(); } } private void checkHeight() { Rect rect3 = rect; int imageWidth = mImageWidth; int imageHeight = mImageHeight; if (rect3.bottom > imageHeight) { rect3.bottom = imageHeight; rect3.top = imageHeight - getHeight(); } if (rect3.top < 0) { rect3.top = 0; rect3.bottom = getHeight(); } } }
上述源码的几个主要方法是setInputStream(InputStream)
,onTouchEvent(MotionEvent)
,onMeasure(int,int)
,onDraw(Canvas)
,下面我们看下这几个方法的主要逻辑:
-
setInputStream
的主要作用是通过传入的图片输入流来获取图片真实的宽和高。 -
onTouchEvent
这个方法主要监听我们的手势,通过手势监听回调,在滑动时改变图片显示的区域。 -
onMeasure
这个方法是给显示区域的上下左右边届赋值,图片的大小就是显示的大小。 -
onDraw(Canvas)
就是根据上面的指定区域拿到bitmap并绘制在自定义控件上。
自定义手势监听(MoveGestureDetector)
package com.example.bthvi.bigpictureloading; import android.content.Context; import android.graphics.PointF; import android.view.MotionEvent; /** * 手势处理 * create by bthvi on 2018/06/29 */ public class MoveGestureDetector extends BaseGestureDetector{ /** * 当前点 */ private PointF mCurrentPointer; /** * 上次触摸点 */ private PointF mPrePointer; //仅仅为了减少创建内存 private PointF mDeltaPointer = new PointF(); //用于记录最终结果,并返回 private PointF mExtenalPointer = new PointF(); private OnMoveGestureListener mListenter; public MoveGestureDetector(Context context) { super(context); } public void setOnMoveGestureListener(OnMoveGestureListener listener){ this.mListenter = listener; } public OnMoveGestureListener getmListenter(){ return this.mListenter; } @Override protected void handleInProgressEvent(MotionEvent event) { /** * 这里就是处理多点触控,MotionEvent.ACTION_MASK(0x000000ff)与触摸事件进行逻辑运算and。 * 无论多少手指在屏幕上操作,进过逻辑and运算后 都是一个手指 */ int actionCode = event.getAction() & MotionEvent.ACTION_MASK; switch (actionCode){ case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: mListenter.onMoveEnd(this); resetState(); break; case MotionEvent.ACTION_MOVE: updateStateByEvent(event); boolean update = mListenter.onMove(this); if (update) { mPreMotionEvent.recycle(); mPreMotionEvent = MotionEvent.obtain(event); } break; } } @Override protected void handleStartProgressEvent(MotionEvent event) { int actionCode = event.getAction() & MotionEvent.ACTION_MASK; switch (actionCode){ case MotionEvent.ACTION_DOWN: /**防止没收到 UP 或 CANCEL*/ resetState(); mPreMotionEvent = MotionEvent.obtain(event); updateStateByEvent(event); break; case MotionEvent.ACTION_MOVE: mGestureInProgress = mListenter.onMove(this); break; } } @Override protected void updateStateByEvent(MotionEvent event) { final MotionEvent prev = mPreMotionEvent; mPrePointer = calculateCenterPointer(prev); mCurrentPointer = calculateCenterPointer(event); boolean mSkipThisEvent = prev.getPointerCount() != event.getPointerCount(); mExtenalPointer.x = mSkipThisEvent ? 0 : mCurrentPointer.x - mPrePointer.x; mExtenalPointer.y = mSkipThisEvent ? 0 : mCurrentPointer.y - mPrePointer.y; } /** * 计算多指触控中心点 * @param event * @return */ private PointF calculateCenterPointer(MotionEvent event) { /** * 触摸点数 */ final int count = event.getPointerCount(); float x=0,y=0; for (int i = 0; i< count;i++){ x += event.getX(i); y += event.getY(i); } x /= count; y /= count; return new PointF(x,y); } public float getMoveX() { return mExtenalPointer.x; } public float getMoveY() { return mExtenalPointer.y; } }
这个类主要有四个方法,他们的作用主要是:
-
handleInProgressEvent
,handleStartProgressEvent
处理手势触摸事件,这里我们注意到int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
很多同学可能跟我一样会想为啥要这样写呢?首先,我们知道MotionEvent.ACTION_MASK
的值为(0x000000ff),我们的触摸事件跟它做逻辑与运算的结果一定会小于它,这里就是将多个手指的触摸事件转为一个手指触摸。 -
calculateCenterPointer
这个方法就是计算多个手指在屏幕上的中心位置。 -
updateStateByEvent
这个方法主要是更新当前手指的中心位置。
BaseGestureDetector
package com.example.bthvi.bigpictureloading; import android.content.Context; import android.view.MotionEvent; /** * 手势处理抽象类 * create by bthvi on 2018/06/29 */ public abstract class BaseGestureDetector { protected boolean mGestureInProgress; protected MotionEvent mPreMotionEvent; protected MotionEvent mCurrentMotionEvent; protected Context mContext; public BaseGestureDetector(Context context) { mContext = context; } public boolean onToucEvent(MotionEvent event) { if (!mGestureInProgress) { handleStartProgressEvent(event); } else { handleInProgressEvent(event); } return true; } protected abstract void handleInProgressEvent(MotionEvent event); protected abstract void handleStartProgressEvent(MotionEvent event); protected abstract void updateStateByEvent(MotionEvent event); protected void resetState() { if (mPreMotionEvent != null) { mPreMotionEvent.recycle(); mPreMotionEvent = null; } if (mCurrentMotionEvent != null) { mCurrentMotionEvent.recycle(); mCurrentMotionEvent = null; } mGestureInProgress = false; } }
OnMoveGestureListener
package com.example.bthvi.bigpictureloading; /** * 手势监听接口 * create by bthvi on 2018/06/29 */ public interface OnMoveGestureListener { public boolean onMoveBegin(MoveGestureDetector detector); public boolean onMove(MoveGestureDetector detector); public void onMoveEnd(MoveGestureDetector detector); }
SimpleMoveGestureDetector
package com.example.bthvi.bigpictureloading; /** * 手势监听接口实现 * create by bthvi on 2018/06/29 */ public class SimpleMoveGestureDetector implements OnMoveGestureListener { @Override public boolean onMoveBegin(MoveGestureDetector detector) { return false; } @Override public boolean onMove(MoveGestureDetector detector) { return false; } @Override public void onMoveEnd(MoveGestureDetector detector) { } }
调用
首先在xml中调用LargeImageView
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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.example.bthvi.bigpictureloading.LargeImageView android:id="@+id/largeImageView" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
然后在Activity里面去将图片的输入流设置给LargeImageView
package com.example.bthvi.bigpictureloading; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import java.io.IOException; import java.io.InputStream; /** * * create by bthvi on 2018/06/29 */ public class MainActivity extends AppCompatActivity { LargeImageView largeImageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); largeImageView = findViewById(R.id.largeImageView); try { InputStream stream = getAssets().open("world.jpg"); largeImageView.setInputStream(stream); } catch (IOException e) { e.printStackTrace(); } } }
最后附上源码地址点击查看源码
特别感谢
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
MVC vs. MVP vs. MVVM on Android
在过去的几年里,将Android应用程序转变成逻辑组件的方法已经逐渐成熟。很大程度上摆脱了MVC模式,转而采用更模块化、可测试的模式。 Model View Presenter (MVP) & Model View ViewModel (MVVM)是最广泛被采用的两种替代方案。本文不去讨论哪种方式更适合于Android应用开发,只是通过案例来看到每种模式是如何编写的。 本文通过实现一个井字游戏,分别通过MVC、MVP、MVVM三种模式实现游戏效果。源代码已经上传到Github仓库中。 井字游戏效果图.png MVC Model, View, Controller将应用程序在宏观层面分为3中职责。 Model Model模型是应用程序中的数据+状态+业务逻辑。可以说是应用程序的大脑,不受View视图和Controller控制器的束缚,因此很多情况下是可以复用的。 View View视图是Model的展现,负责呈现UI并在用户与应用程序交互时与Controller通信。在MVC架构中,视图通常很“愚蠢”,因为他们不了解底层模型,也没有对状态的理解,或者当用户通过单击按钮,键入值等进...
- 下一篇
工欲善其事,必先利其器,分享31个Android开发者工具
FlowUp 这是一个帮助你跟踪app整体性能的工具,深入分析关键的性能数据如FPS, 内存, CPU, 磁盘, 等等。FlowUp根据用户数量收费。 由Facebook开发的一个强大的开源Android debug平台,Stetho让原生 Android app的debug跟使用Chrome的开发者工具debug web页面一样简单。它能让你轻松检查整个view树结构,观察SQLite数据库,管理网络操作以及其它的一些操作。 每当等待永远也编译不出来的Gradle的时候,我们都会疯掉。JRebel的这个工具可以显著的提高编译速度。价格取决于开发者数量以及协议时长。 如果你的 Android Studio 编译系统使用的是Gradle,那么用一行代码就可以把第三方库添加到项目中了。但是如何才能快速的得到依赖的那行代码呢?这就是这个网站的方便之处了,它可以帮助你快速找到自己想要的库的依赖。 注:比如我们要使用glide,只需在一个输入框中输入glide,下面就会显示glide的完整依赖。有时候搞不清楚拼写或者版本号这些细节的话很有用。 一个可以生成各种类型图标(launcher, not...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合Redis,开启缓存,提高访问速度