前言
讲解流程:
1.业务需求
2.完整显示,精度要求不高处理办法
3.精度要求高,不需要完成显示处理
4.总结
1.业务需求
1.1完整显示,对精度要求不高,图片本身就很大
1.2对精度需求比较高,不需要完整显示
2.完整显示,精度要求不高处理办法
1.根据显示设备本身大小进行缩放
2.降低精度加载(改变图片模式RGB565)
3.修改图片格式(png改成webp,jpg)
3.精度要求高,不需要完成显示处理(区域加载)
3.1BitmapRegionDecoder
其实从名字我们也可以看出:指定Bitmap区域进行解码,没错它主要用于显示图片的某一块矩形区域,如果需要显示某个图片的指定区域,那么这个类非常合适。
它的用法也非常简单,既然是显示图片的某一块区域,那么至少需要两个方法:1、设置图片,2、设置显示区域。
接下来通过自定义一个可以加载巨图的View展开说明:
3.2、设置图片
/**
* 由使用者输入一张图片
*/
public void setImage(InputStream is){
//先读取原图片的信息 高,宽
mOptions.inJustDecodeBounds=true;
BitmapFactory.decodeStream(is,null,mOptions);
mImageWidth=mOptions.outWidth;
mImageHeight=mOptions.outHeight;
//开启复用
mOptions.inMutable=true;
//设置格式成RGB_565
mOptions.inPreferredConfig=Bitmap.Config.RGB_565;
mOptions.inJustDecodeBounds=false;
//创建一个区域解码器
try {
mDecoder=BitmapRegionDecoder.newInstance(is,false);
} catch (IOException e) {
e.printStackTrace();
}
requestLayout();
}
设置inJustDecodeBounds为ture(只解码图片的尺寸)。然后得到图片宽高,并且设置Bitmap是可以被复用的。然后创建BitmapRegionDecoder的实例对象。最后调用requestLayout()方法,reqeustLayout会重新测量我们的布局也就是会执行View的onMeasure()。
3.3指定显示区域
/**
* 在测量的时候把我们需要的内存区域获取到 存入到mRect中
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取测量的view的大小
mViewWidth=getMeasuredWidth();
mViewHeight=getMeasuredHeight();
//确定要加载的图片的区域
mRect.left=0;
mRect.top=0;
mRect.right=mImageWidth;
//获取一个缩放因子
mScale=mViewWidth/(float)mImageWidth;
//高度就根据缩放比进行获取
mRect.bottom=(int)(mViewHeight/mScale);
}
/**
* 画出内容
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//如果解码器拿不到,表示没有设置过要显示的图片
if(null==mDecoder){
return;
}
//复用上一张bitmap
mOptions.inBitmap=bitmap;
//解码指定的区域
bitmap=mDecoder.decodeRegion(mRect,mOptions);
//把得到的矩阵大小的内存进行缩放 得到view的大小
Matrix matrix=new Matrix();
matrix.setScale(mScale,mScale);
//画出来
canvas.drawBitmap(bitmap,matrix,null);
}
3.4改变区域完成巨图加载
Scroller + GestureDetector借助手势GestureDetector与Scroller(滑动帮助)来完成这一功能。
附上完整代码
package com.example.administrator.lsn_8_demo;
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;
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 float mScale;
private Bitmap bitmap;
public BigView(Context context) {
this(context,null,0);
}
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();
//需要复用
mOptions=new BitmapFactory.Options();
//手势识别类
mGestureDetector=new GestureDetector(context,this);
//设置onTouchListener
setOnTouchListener(this);
//滑动帮助
mScroller=new Scroller(context);
}
/**
* 由使用者输入一张图片
*/
public void setImage(InputStream is){
//先读取原图片的信息 高,宽
mOptions.inJustDecodeBounds=true;
BitmapFactory.decodeStream(is,null,mOptions);
mImageWidth=mOptions.outWidth;
mImageHeight=mOptions.outHeight;
//开启复用
mOptions.inMutable=true;
//设置格式成RGB_565
mOptions.inPreferredConfig=Bitmap.Config.RGB_565;
mOptions.inJustDecodeBounds=false;
//创建一个区域解码器
try {
mDecoder=BitmapRegionDecoder.newInstance(is,false);
} catch (IOException e) {
e.printStackTrace();
}
requestLayout();
}
/**
* 在测量的时候把我们需要的内存区域获取到 存入到mRect中
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取测量的view的大小
mViewWidth=getMeasuredWidth();
mViewHeight=getMeasuredHeight();
//确定要加载的图片的区域
mRect.left=0;
mRect.top=0;
mRect.right=mImageWidth;
//获取一个缩放因子
mScale=mViewWidth/(float)mImageWidth;
//高度就根据缩放比进行获取
mRect.bottom=(int)(mViewHeight/mScale);
}
/**
* 画出内容
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//如果解码器拿不到,表示没有设置过要显示的图片
if(null==mDecoder){
return;
}
//复用上一张bitmap
mOptions.inBitmap=bitmap;
//解码指定的区域
bitmap=mDecoder.decodeRegion(mRect,mOptions);
//把得到的矩阵大小的内存进行缩放 得到view的大小
Matrix matrix=new Matrix();
matrix.setScale(mScale,mScale);
//画出来
canvas.drawBitmap(bitmap,matrix,null);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//交给手势处理
return mGestureDetector.onTouchEvent(event);
}
/**
* 手按下的回调
* @param e
* @return
*/
@Override
public boolean onDown(MotionEvent e) {
//如果移动还没有停止,强制停止
if(!mScroller.isFinished()){
mScroller.forceFinished(true);
}
//继续接收后续事件
return true;
}
/**
*
* @param e1 接下
* @param e2 移动
* @param distanceX 左右移动时的距离
* @param distanceY 上下移动时的距离
* @return
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//上下移动的时候,需要改变显示区域 改mRect
mRect.offset(0,(int)distanceY);
//处理移动时已经移到了两个顶端的问题
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
* @param e2
* @param velocityX 每秒移动的x点
* @param velocityY
* @return
*/
@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;
}
/*
使用上一个接口的计算结果
*/
@Override
public void 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) {
}
}
4.总结
1、首先确定大图的用途,精度需求:
a)完整显示,对精度要求不高,图片本身就很大
b)对精度需求比较高,不需要完整显示
2、解决方案
a)针对第一种的处理图片本身,按需加载(根据显示设备本身大小进行缩放),降低精度加载(改变图片模式,如将ARGB8888改成RGB565,ARGB4444),修改图片格式(png改成webp,jpg)
b)第二种的一般采用局部加载,主要要用到的是BitmapRegionDecoder这个类decodeRegion的方法,读取图片指定大小的数据,然后通过移动来动态改变显示区域的图片