版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、BitmapRegionDecoder
当一张图片的分辨率特别大的时候,我们把这张图完整的加载进来,在屏幕上并没有办法显示这张图片的全部内容。但是,全部加载进来的话,还会占用较大的内存,严重时候会产生 OOM。
这时候我们可以使用 BitmapRegionDecoder,进行加载图片的一部分内容进行显示。
基本用法:
private void load() {
InputStream inputStream = null;
try {
inputStream = getAssets().open("big.png");
//isShareable: 输入流是否共享,false 内部拷贝输入流
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(inputStream, false);
Rect rect = new Rect();
rect.left = 0;
rect.top = 0;
rect.right = 100;
rect.bottom = 100;
decoder.decodeRegion(rect, null);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
BitmapRegionDecoder 的 newInstance 有一个参数是 isShareable,表示输入流是否是同一个,true 表示的是同一个,我们如果对代码中的输入流进行关闭,BitmapRegionDecoder 中的输入流也将关闭。所以一般设为 false。
BitmapRegionDecoder 的 decodeRegion 方法,需要两个参数,分别是 Rect 和 BitmapFactory.Options。Rect 表示要加载的巨图区域,起始点在图片的左上角。
注:红色区域表示巨图,黑色区域表示 Rect 截取的区域。
二、自定义控件加载巨图
这边实现自定义控件来加载巨图,这边只针对长长图,不正对宽长图进行处理,思路一样,有需要的可以自己扩展。
BigImageView :
package com.xiaoyue.bigimage;
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.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;
import java.io.IOException;
import java.io.InputStream;
/**
* 加载长度的 View
*/
public class BigImageView extends View {
//指定要加载区域的矩形
private Rect mRect;
//区域解码器
private BitmapRegionDecoder mDecoder;
//解码图片的配置
private BitmapFactory.Options mOptions;
//图片的宽高
private int mImageWidth;
private int mImageHeight;
//图片缩放系数
private float mScale;
//控件的宽高
private int mViewWidth;
private int mViewHeight;
//控件显示的图片
private Bitmap mBitmap;
//滑动
private final Scroller mScroller;
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);
init();
// 滑动帮助
mScroller = new Scroller(context);
}
/**
* 初始化
*/
private void init() {
mRect = new Rect();
mOptions = new BitmapFactory.Options();
}
/**
* 设置要显示的图片的输入流
* @param inputStream 图片文件的输入流
*/
public void setImage(InputStream inputStream) {
//先获取图片的宽高
mOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, mOptions);
mImageWidth = mOptions.outWidth;
mImageHeight = mOptions.outHeight;
//设置允许复用
mOptions.inMutable = true;
//设置格式,减小内存
mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
//需设置回来,否则无法解析图片
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);
//获取测量宽高
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredHeight();
//还没设置 Image
if(mDecoder == null){
return;
}
//确定要加载的图片的区域
mRect.left = 0;
mRect.top = 0;
mRect.right = mImageWidth;
//获得缩放因子
mScale = mViewWidth / (float) mImageWidth;
// 需要加载的高 * 缩放因子 = 视图view的高
// x * mScale = mViewHeight
mRect.bottom = (int) (mViewHeight / mScale);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//还没设置 Image
if(mDecoder == null){
return;
}
//复用上一张bitmap
mOptions.inBitmap = mBitmap;
//解码指定区域
mBitmap = mDecoder.decodeRegion(mRect, mOptions);
//使用矩阵 对图片进行 缩放
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);
//画出来
canvas.drawBitmap(mBitmap, matrix, null);
}
}
定义一个 BigImageView 继承 View,添加基本的构造函数,初始化方法。采用了巨图加载的 BitmapRegionDecoder 进行部分区域的加载,还对图片的宽进行了缩放。
另外,比较重要的一点是,通过 BitmapFactory.Options 设置了Bitmap 内存的复用,减小了 Bitmap 内存申请次数。
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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.xiaoyue.bigimage.BigImageView
android:id="@+id/big_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>
效果:
注:背后文字是一张长图片。
这样就简单的吧图片截取出来并显示,我们还需要为他加上滑动,这边使用的是手势处理 GestureDetector 进行处理。
BigImageView:
public class BigImageView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {
//指定要加载区域的矩形
private Rect mRect;
//区域解码器
private BitmapRegionDecoder mDecoder;
//解码图片的配置
private BitmapFactory.Options mOptions;
//图片的宽高
private int mImageWidth;
private int mImageHeight;
//图片缩放系数
private float mScale;
//控件的宽高
private int mViewWidth;
private int mViewHeight;
//控件显示的图片
private Bitmap mBitmap;
//手势处理
private GestureDetector mGestureDetector;
//滑动
private final Scroller mScroller;
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);
init();
//手势
mGestureDetector = new GestureDetector(context, this);
setOnTouchListener(this);
// 滑动帮助
mScroller = new Scroller(context);
}
/**
* 初始化
*/
private void init() {
mRect = new Rect();
mOptions = new BitmapFactory.Options();
}
/**
* 设置要显示的图片的输入流
* @param inputStream 图片文件的输入流
*/
public void setImage(InputStream inputStream) {
//先获取图片的宽高
mOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, mOptions);
mImageWidth = mOptions.outWidth;
mImageHeight = mOptions.outHeight;
//设置允许复用
mOptions.inMutable = true;
//设置格式,减小内存
mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
//需设置回来,否则无法解析图片
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);
//获取测量宽高
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredHeight();
//还没设置 Image
if(mDecoder == null){
return;
}
//确定要加载的图片的区域
mRect.left = 0;
mRect.top = 0;
mRect.right = mImageWidth;
//获得缩放因子
mScale = mViewWidth / (float) mImageWidth;
// 需要加载的高 * 缩放因子 = 视图view的高
// x * mScale = mViewHeight
mRect.bottom = (int) (mViewHeight / mScale);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//还没设置 Image
if(mDecoder == null){
return;
}
//复用上一张bitmap
mOptions.inBitmap = mBitmap;
//解码指定区域
mBitmap = mDecoder.decodeRegion(mRect, mOptions);
//使用矩阵 对图片进行 缩放
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);
//画出来
canvas.drawBitmap(mBitmap, matrix, null);
}
@Override
public boolean onDown(MotionEvent e) {
//如果滑动还没有停止 强制停止
if (!mScroller.isFinished()){
mScroller.forceFinished(true);
}
//继续接收后续事件
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
/**
* 手指在屏幕上拖动
* @param e1 手指按下去的事件 -- 获取开始的坐标
* @param e2 当前手势事件 -- 获取当前的坐标
* @param distanceX x方向移动的距离
* @param distanceY y方向移动的距离
* @return
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// 手指从下往上 图片也要往上 distanceY是负数, top 和 bottom 在减
// 手指从上往下 图片也要往下 distanceY是正数, top 和 bottom 在加
//改变加载图片的区域
mRect.offset(0, (int) distanceY);
//bottom大于图片高了, 或者 top小于0了
if (mRect.bottom > mImageHeight){
mRect.bottom = mImageHeight;
mRect.top = mImageHeight-(int) (mViewHeight / mScale);
}
if (mRect.top < 0){
mRect.top = 0;
mRect.bottom = (int) (mViewHeight / mScale);
}
//重绘
invalidate();
return false;
}
/**
* 手指离开屏幕滑动(惯性)
* @param e1 启动 flin 的第一个向下运动事件
* @param e2 触发当前 onFlin 的移动运动事件
* @param velocityX 速度,每秒 x 方向移动的像素
* @param velocityY 速度,每秒 y 方向移动的像素
* @return
*/
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
/**
* startX: 滑动开始的x坐标
* startY: 滑动开始的y坐标
* velocityX:两个速度
* velocityY:
* minX: x方向的最小值
* max 最大
* y
*/
//计算器
mScroller.fling(0, mRect.top,
0, (int)-velocityY,
0,0,0,
mImageHeight - (int) (mViewHeight / mScale));
return false;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//触摸事件转交给 mGestureDetector 进行处理
return mGestureDetector.onTouchEvent(event);
}
//获取计算结果并且重绘
@Override
public void computeScroll() {
//已经计算结束 return
if (mScroller.isFinished()){
return;
}
//true 表示当前动画未结束
if (mScroller.computeScrollOffset()){
//
mRect.top = mScroller.getCurrY();
mRect.bottom = mRect.top+ (int) (mViewHeight / mScale);
invalidate();
}
}
}
效果: