Android 加载超大长图及原理
我们在开发中偶尔会遇到加载超大长图,类似于微信n多页聊天记录截图加载。但这类图片一般都会很大,几兆、十几兆、甚至几十兆,很容易造成内存溢出,今天笔者给大家提供一个可以满足此类需求的简单的自定义view。具体原理就是图片缩放,只加载屏幕显示区域图片,内存复用。代码很简单,注视也很相信,可以拿来直接用。
public class BigView extends View implements GestureDetector.OnGestureListener,View.OnTouchListener {
private Scroller mScroll;
//手势
private GestureDetector mGestureDetector;
private BitmapFactory.Options mOptions;
private int mImageHeight;
private int mImageWidht;
//手机屏幕宽高
private int mViewHeight;
private int mViewWidth;
//图片缩放因子
private float mScale;
private Rect mRect;
private Bitmap mBitmap;
//bitmap解码器
private BitmapRegionDecoder mDecoder;
public BigView(Context context) {
this(context,null);
}
public BigView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public BigView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mOptions=new BitmapFactory.Options();//内存复用
mGestureDetector=new GestureDetector(context,this);
mScroll =new Scroller(context);
mRect=new Rect();
setOnTouchListener(this);
}
public void setImage(InputStream inputStream){
//只将大图的边框加载到内存
mOptions.inJustDecodeBounds=true;
//将图片边宽加载到options
BitmapFactory.decodeStream(inputStream,null,mOptions);
mImageHeight=mOptions.outHeight;
mImageWidht=mOptions.outWidth;
//设置内存可复用
mOptions.inMutable=true;
//设置图片编码格式
mOptions.inPreferredConfig=Bitmap.Config.RGB_565;
mOptions.inJustDecodeBounds=false;
try {
//解码图片
mDecoder= BitmapRegionDecoder.newInstance(inputStream,false);
} catch (IOException e) {
e.printStackTrace();
}
//requestLayout方法会导致View的onMeasure、onLayout、onDraw方法被调用;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mViewHeight=getMeasuredHeight();
mViewWidth=getMeasuredWidth();
mScale=mViewWidth/(float)mImageWidht;
//等比例缩放后图片所占区域
mRect.top=0;
mRect.left=0;
mRect.right= mImageWidht;
mRect.bottom= (int) (mViewHeight/mScale);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mDecoder==null)return;
//内存复用,分配空间
mOptions.inBitmap=mBitmap;
//指定解码区域
mBitmap=mDecoder.decodeRegion(mRect,mOptions);
Matrix matrix=new Matrix();
//将图片缩放到手机屏幕内
matrix.preScale(mScale,mScale);
canvas.drawBitmap(mBitmap,matrix,null);
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
//将触摸时间分发给手势
return mGestureDetector.onTouchEvent(motionEvent);
}
@Override
public boolean onDown(MotionEvent motionEvent) {
//当按下屏幕时,如果正在滑动,则强制停止滑动
if(!mScroll.isFinished()){
mScroll.forceFinished(true);
}
return true;
}
@Override
public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
//滑动时rect偏移量计算
mRect.offset(0, (int) v1);
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);
}
//调用ondraw方法
invalidate();
return false;
}
@Override
public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
mScroll.fling(0,mRect.top,0,(int)-v1,0,0,0,mImageHeight-(int)(mViewHeight/mScale));
return false;
}
//处理滚动结果
@Override
public void computeScroll() {
if(mScroll.isFinished())return;
if(mScroll.computeScrollOffset()){
mRect.top=mScroll.getCurrY();
mRect.bottom=mScroll.getCurrY()+(int)(mViewHeight/mScale);
//调用ondraw方法
invalidate();
}
super.computeScroll();
}
@Override
public void onShowPress(MotionEvent motionEvent) {
}
@Override
public boolean onSingleTapUp(MotionEvent motionEvent) {
return false;
}
@Override
public void onLongPress(MotionEvent motionEvent) {
}
}
外部只需要简单调用即可使用 bigView.setImage(inputStream);
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BigView bigView=findViewById(R.id.bigView);
InputStream inputStream= null;
try {
inputStream = getAssets().open("big.jpeg");
bigView.setImage(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
}