Android 长图大图加载
长图大图加载
需求:在项目开发中需要长图显示以及大图、巨图显示。
1.长图加载
长图加载显示
由于长图大小比较大,占用的内存比较多,所以需要优化加载。
理论:我们只加载需要显示的大小,其他部分不加载。
1.1画出图形
创建一个自定义控件继承View,它需要的参数:
//需要显示的区域
private Rect mRect;
//由于需要复用,所有需要option
private BitmapFactory.Options mOption;
//长图需要通过手势滑动来操作
private GestureDetector mGestureDetector;
//滑动帮助类
private Scroller mScroller;
//图片的宽度
private int mImageWidth;
//图片的高度
private int mImageHeight;
//控件的宽度
private int mViewWidth;
//控件的高度
private int mViewHeight;
//图片缩放因子
private float mScale;
//区域解码器
private BitmapRegionDecoder mDecode;
//需要展示的图片,是被复用的
private Bitmap mBitmap;
构造函数中初始化需要显示的矩形区域、手势识别类、滑动帮助类等
public BigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mRect = new Rect();
mOption = new BitmapFactory.Options();
mGestureDetector = new GestureDetector(context, this);
setOnTouchListener(this);
mScroller = new Scroller(context);
}
为使用者提供一个输入图片的方法,参数使用输入流,方便使用。
/**
* 由使用者输入一张图片
*
* @param is 图片输入流
*/
public void setImage(InputStream is) {
mOption.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, mOption);
mImageWidth = mOption.outWidth;
mImageHeight = mOption.outHeight;
//开启复用内存
mOption.inMutable = true;
//设置格式,减少内存
mOption.inPreferredConfig = Bitmap.Config.RGB_565;
mOption.inJustDecodeBounds = false;
//创建一个区域解码器
try {
mDecode = BitmapRegionDecoder.newInstance(is, false);
} catch (IOException e) {
e.printStackTrace();
}
//刷新
requestLayout();
}
在自定义控件测量方法中,得到需要展示区域的大小,保存在Rect中。
/**
* 在控件测量中把需要内存区域获取,保存在Rect中
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取测量的view的大小
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredHeight();
mRect.top = 0;
mRect.left = 0;
mRect.right = mImageWidth;
mScale = mViewWidth / (float) mImageWidth;
mRect.bottom = (int) (mViewHeight / mScale);
}
在onDraw中画出需要展示的内容
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//如果没有解码器 说明还没有图片,不需要绘制
if (null == mDecode) {
return;
}
mOption.inBitmap = mBitmap;
//通过解码器把图解码出来,只加载矩形区域的内容
mBitmap = mDecode.decodeRegion(mRect, mOption);
//把得到的矩形局域大小的图片通过缩放因子,缩放成控件大小
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);
canvas.drawBitmap(mBitmap, matrix, null);
}
1.2滑动图形
将控件的onTouch事件交给手势识别来处理。
@Override
public boolean onTouch(View v, MotionEvent event) {
//将onTouch事件交给手势处理
return mGestureDetector.onTouchEvent(event);
}
如果控件由于惯性正在滑动,需要用户在点击的时候,立刻停止滑动,需要使用滑动帮助类强制停止滑动。
/**
* 手指按下的回调
*/
@Override
public boolean onDown(MotionEvent e) {
//如果移动还没有停止,强制停止
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
return true;
}
在控件滑动过程中,需要根据用户滑动的距离来偏移需要显示的矩形区域,使得屏幕上显示图片根据用户的手势移动来移动。
在这个过程中需要处理矩形上下边界值,不能移出长图以外。
/**
* @param e1 手指按下去的事件 开始的坐标
* @param e2 当前手势事件
* @param distanceX x方向移动的距离
* @param distanceY y方向移动的距离
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//上下移动的时候,需要改变显示的区域 改mRect
mRect.offset(0, (int) distanceY);
//处理上下边界问题
if (mRect.top < 0) {
mRect.top = 0;
mRect.bottom = (int) (mViewHeight / mScale);
}
if (mRect.bottom > mImageHeight) {
mRect.top = mImageHeight - (int) (mViewHeight / mScale);
mRect.bottom = mImageHeight;
}
invalidate();
return false;
}
1.3惯性滑动图形
使用滑动帮助类来处理滑动的惯性问题。
注意点:手势滑动方法onFling中的velocityY参数与Scroller.fling方法的参数velocityY方向是反的。
/**
* @param e1 手指按下去的事件 开始的坐标
* @param e2 当前手势事件
* @param velocityX 每秒移动的x像素点
* @param velocityY 每秒移动的y像素点
*/
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mScroller.fling(0, mRect.top,
0, (int) -velocityY,
0, 0,
0, mImageHeight - (int) (mViewHeight / mScale));
return false;
}
最后在computeScroll方法中通过滑动帮助类来告诉控件惯性滑动的距离,让控件重新绘制。
ps:computeScroll不是来让ViewGroup滑动的,真正让ViewGroup滑动的是scrollTo,scrollBy。computeScroll的作用是计算ViewGroup如何滑动。而computeScroll是通过draw来调用的。
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.isFinished()) {
return;
}
//true 当前滑动还没有结束
if (mScroller.computeScrollOffset()) {
mRect.top = mScroller.getCurrY();
mRect.bottom = mRect.top + (int) (mViewHeight / mScale);
invalidate();
}
}
1.4 长图加载所有代码,以及展示
调用方式,可以根据需要调整Inpustream,演示是从assets中获取。
BigView mView = findViewById(R.id.big_view);
InputStream is = null;
try {
is = getAssets().open("big.png");
mView.setImage(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
长图加载的类
/**
* Author:JR
* Time: 2019/5/17
* Description: 长图加载
*/
public class BigView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {
//需要显示的区域
private Rect mRect;
//由于需要复用,所有需要option
private BitmapFactory.Options mOption;
//长图需要通过手势滑动来操作
private GestureDetector mGestureDetector;
//滑动帮助类
private Scroller mScroller;
//图片的宽度
private int mImageWidth;
//图片的高度
private int mImageHeight;
//控件的宽度
private int mViewWidth;
//控件的高度
private int mViewHeight;
//图片缩放因子
private float mScale;
//区域解码器
private BitmapRegionDecoder mDecode;
//需要展示的图片,是被复用的
private Bitmap mBitmap;
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) {
super(context, attrs, defStyleAttr);
mRect = new Rect();
mOption = new BitmapFactory.Options();
mGestureDetector = new GestureDetector(context, this);
setOnTouchListener(this);
mScroller = new Scroller(context);
}
/**
* 由使用者输入一张图片
*
* @param is 图片输入流
*/
public void setImage(InputStream is) {
mOption.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, mOption);
mImageWidth = mOption.outWidth;
mImageHeight = mOption.outHeight;
//开启复用内存
mOption.inMutable = true;
//设置格式,减少内存
mOption.inPreferredConfig = Bitmap.Config.RGB_565;
mOption.inJustDecodeBounds = false;
//创建一个区域解码器
try {
mDecode = BitmapRegionDecoder.newInstance(is, false);
} catch (IOException e) {
e.printStackTrace();
}
//刷新
requestLayout();
}
/**
* 在控件测量中把需要内存区域获取,保存在Rect中
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取测量的view的大小
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredHeight();
mRect.top = 0;
mRect.left = 0;
mRect.right = mImageWidth;
mScale = mViewWidth / (float) mImageWidth;
mRect.bottom = (int) (mViewHeight / mScale);
}
/**
* 画出内容
*
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//如果没有解码器 说明还没有图片,不需要绘制
if (null == mDecode) {
return;
}
mOption.inBitmap = mBitmap;
//通过解码器把图解码出来,只加载矩形区域的内容
mBitmap = mDecode.decodeRegion(mRect, mOption);
//把得到的矩形局域大小的图片通过缩放因子,缩放成控件大小
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);
canvas.drawBitmap(mBitmap, matrix, null);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//将onTouch事件交给手势处理
return mGestureDetector.onTouchEvent(event);
}
/**
* 手指按下的回调
*/
@Override
public boolean onDown(MotionEvent e) {
//如果移动还没有停止,强制停止
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
return true;
}
/**
* @param e1 手指按下去的事件 开始的坐标
* @param e2 当前手势事件
* @param distanceX x方向移动的距离
* @param distanceY y方向移动的距离
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//上下移动的时候,需要改变显示的区域 改mRect
mRect.offset(0, (int) distanceY);
//处理上下边界问题
if (mRect.top < 0) {
mRect.top = 0;
mRect.bottom = (int) (mViewHeight / mScale);
}
if (mRect.bottom > mImageHeight) {
mRect.top = mImageHeight - (int) (mViewHeight / mScale);
mRect.bottom = mImageHeight;
}
invalidate();
return false;
}
/**
* 处理滑动的惯性的问题
*
* @param e1 手指按下去的事件 开始的坐标
* @param e2 当前手势事件
* @param velocityX 每秒移动的x像素点
* @param velocityY 每秒移动的y像素点
*/
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mScroller.fling(0, mRect.top,
0, (int) -velocityY,
0, 0,
0, mImageHeight - (int) (mViewHeight / mScale));
return false;
}
/**
* 利用onFling的计算结果 在惯性滑动过程中重新计算滑动过程中Rect的top以及bottom的值
*/
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.isFinished()) {
return;
}
//true 当前滑动还没有结束
if (mScroller.computeScrollOffset()) {
mRect.top = mScroller.getCurrY();
mRect.bottom = mRect.top + (int) (mViewHeight / mScale);
invalidate();
}
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
}
2.大图加载
在长图加载的基础上,把缩放因子去掉,长图加载只是操作了Y轴上的变化,大图加载需要操作X和Y轴上的变化。
代码如下:
/**
* Author:JR
* Time: 2019/5/17
* Description: 大图加载
*/
public class BigView2 extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {
//需要显示的区域
private Rect mRect;
//由于需要复用,所有需要option
private BitmapFactory.Options mOption;
//长图需要通过手势滑动来操作
private GestureDetector mGestureDetector;
//滑动帮助类
private Scroller mScroller;
//图片的宽度
private int mImageWidth;
//图片的高度
private int mImageHeight;
//控件的宽度
private int mViewWidth;
//控件的高度
private int mViewHeight;
//区域解码器
private BitmapRegionDecoder mDecode;
//需要展示的图片,是被复用的
private Bitmap mBitmap;
public BigView2(Context context) {
this(context, null);
}
public BigView2(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BigView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mRect = new Rect();
mOption = new BitmapFactory.Options();
mGestureDetector = new GestureDetector(context, this);
setOnTouchListener(this);
mScroller = new Scroller(context);
}
/**
* 由使用者输入一张图片
*
* @param is 图片输入流
*/
public void setImage(InputStream is) {
mOption.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, mOption);
mImageWidth = mOption.outWidth;
mImageHeight = mOption.outHeight;
//开启复用内存
mOption.inMutable = true;
//设置格式,减少内存
mOption.inPreferredConfig = Bitmap.Config.RGB_565;
mOption.inJustDecodeBounds = false;
//创建一个区域解码器
try {
mDecode = BitmapRegionDecoder.newInstance(is, false);
} catch (IOException e) {
e.printStackTrace();
}
//刷新
requestLayout();
}
/**
* 在控件测量中把需要内存区域获取,保存在Rect中
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取测量的view的大小
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredHeight();
mRect.top = 0;
mRect.left = 0;
mRect.right = mViewWidth;
mRect.bottom = mViewHeight;
}
/**
* 画出内容
*
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//如果没有解码器 说明还没有图片,不需要绘制
if (null == mDecode) {
return;
}
mOption.inBitmap = mBitmap;
//通过解码器把图解码出来,只加载矩形区域的内容
mBitmap = mDecode.decodeRegion(mRect, mOption);
//把得到的矩形局域大小的图片通过缩放因子,缩放成控件大小
Matrix matrix = new Matrix();
canvas.drawBitmap(mBitmap, matrix, null);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//将onTouch事件交给手势处理
return mGestureDetector.onTouchEvent(event);
}
/**
* 手指按下的回调
*/
@Override
public boolean onDown(MotionEvent e) {
//如果移动还没有停止,强制停止
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
return true;
}
/**
* @param e1 手指按下去的事件 开始的坐标
* @param e2 当前手势事件
* @param distanceX x方向移动的距离
* @param distanceY y方向移动的距离
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//上下移动的时候,需要改变显示的区域 改mRect
mRect.offset((int) distanceX, (int) distanceY);
//处理上下边界问题
if (mRect.left < 0) {
mRect.left = 0;
mRect.right = mViewWidth;
}
if (mRect.top < 0) {
mRect.top = 0;
mRect.bottom = mViewHeight;
}
if (mRect.right > mImageWidth) {
mRect.left = mImageWidth - mViewWidth;
mRect.right = mImageWidth;
}
if (mRect.bottom > mImageHeight) {
mRect.top = mImageHeight - mViewHeight;
mRect.bottom = mImageHeight;
}
invalidate();
return false;
}
/**
* 处理滑动的惯性的问题
*
* @param e1 手指按下去的事件 开始的坐标
* @param e2 当前手势事件
* @param velocityX 每秒移动的x像素点
* @param velocityY 每秒移动的y像素点
*/
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mScroller.fling(mRect.left, mRect.top,
(int)-velocityX, (int) -velocityY,
0, mImageWidth - mViewWidth,
0, mImageHeight - mViewHeight);
return false;
}
/**
* 利用onFling的计算结果 在惯性滑动过程中重新计算滑动过程中Rect的top以及bottom的值
*/
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.isFinished()) {
return;
}
//true 当前滑动还没有结束
if (mScroller.computeScrollOffset()) {
mRect.left = mScroller.getCurrX();
mRect.top = mScroller.getCurrY();
mRect.right = mRect.left + mViewHeight;
mRect.bottom = mRect.top + mViewHeight;
invalidate();
}
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
}
3.大图缩放
不想写了 下次再写吧。