项目中总会遇到加载长图的需求,图片的长度可能是手机长度的很多倍,也就是需要通过滑动来查看图片。比较简单的实现方式就是使用ScrollView来加载长图,但是这样做有一个很严重的问题,就是内存消耗严重。我这里有一张长图,宽高为440*10260,大小为477KB,使用ScrollView加载的话,总内存消耗为97M,是相当恐怖的。而使用优化后的自定View加载长图,内存消耗为46M,极大的减少了内存的优化,效果非常明显。我简单说一下实现的过程
对BigImageView的构造器进行简单的修改,并且初始化相关的属性
-
public
class BigImageView extends View implements
-
View.
OnTouchListener,
GestureDetector.
OnGestureListener{
-
private Rect mRect;
//长图中要加载的区域
-
private BitmapFactory.Options mOptions;
-
private GestureDetector mGestureDetector;
//手势的检测器
-
private Scroller mScroller;
//滚动的帮助类
-
-
public BigImageView(Context context) {
-
this(context,
null);
-
}
-
-
public BigImageView(Context context, @Nullable AttributeSet attrs) {
-
this(context, attrs,-
1);
-
}
-
-
public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-
super(context, attrs, defStyleAttr);
-
mRect =
new Rect();
//指定加载的区域
-
mOptions =
new BitmapFactory.Options();
-
mGestureDetector =
new GestureDetector(context,
this);
-
setOnTouchListener(
this);
//设置监听
-
mScroller =
new Scroller(context);
-
}
-
-
@Override
-
public boolean onTouch(View v, MotionEvent event) {
-
//交给手势处理
-
return mGestureDetector.onTouchEvent(event);
-
}
-
-
@Override
-
public boolean onDown(MotionEvent e) {
-
return
false;
-
}
-
-
@Override
-
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
-
return
false;
-
}
-
-
@Override
-
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-
return
false;
-
}
-
-
@Override
-
public void onShowPress(MotionEvent e) {}
-
@Override
-
public void onLongPress(MotionEvent e) {}
-
@Override
-
public boolean onSingleTapUp(MotionEvent e) {
return
false; }
-
}
虽然实现的方法比较多,但是有很多都方法都用不到,比如onShowPress,onSingleTapUp,onLongPress。
上面的代码基本上都很好理解,而mRect这个属性我简单画图说明一下。
mRect就是图中蓝色框的部分。mRect和手机的尺寸(黑色框)比例是一样;mRect的宽度和长图的宽度是一样的。展示的时候,需要把mRect区域根据缩放比例,展示到手机的屏幕既可以。
创建了一个属性mDecoder,用来进行图片的解码
-
/**
-
* 输入一张图片
-
*/
-
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();
-
}
获取了BigImageView的宽高,可以和上一步获取的图片的宽高算出缩放比例。并且设置mRect的上下左右值,用来展示长图的哪部分
-
/**
-
* 在测量的时候把我们需要的内存区域获取到 存入到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);
-
}
主要是两个功能:(1)每次滑动,mRect展示的区域都会改变(2)滑动到顶点和底部的处理
-
/**
-
*
-
* @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;
-
}
在画布上画出展示的图片
-
/**
-
* 画出内容
-
*/
-
@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);
-
}
onFling方法里的内容主要就是计算惯性滑动的值
computeScroll主要就是惯性滑动行为
需要注意的是mScroller.fling中的第四的参数,是负值
-
/**
-
* 处理惯性问题
-
* @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();
-
}
-
}
-
/**
-
* 手按下的回调
-
* @param e
-
* @return
-
*/
-
@Override
-
public boolean onDown(MotionEvent e) {
-
//如果移动还没有停止,强制停止
-
if(!mScroller.isFinished()){
-
mScroller.forceFinished(
true);
//强制停止
-
}
-
//继续接收后续事件
-
return
true;
-
}
-
BigImageView1 bigView=findViewById(R.id.bigView);
-
InputStream
is=
null;
-
try{
-
//加载图片
-
is=getAssets().
open(
"big.png");
-
bigView.setImage(
is);
-
}
catch(Exception e){
-
e.printStackTrace();
-
}
finally {
-
if(
is!=
null){
-
try {
-
is.close();
-
}
catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
}
长图加载的内容主要就是这些,注意细节处理,其实很容易理解。我这里也只是介绍了简单的长图加载,关于横向长图,双指缩放等功能可以自己完善~
demo地址
有问题欢迎留言,欢迎提问,欢迎纠错!