1. 前言
本文單純是想記錄一下自己學習長圖加載的知識點,幫自己記憶
2. 長圖加載所用到的知識點
- 自定義View
- 手勢控制 GestureDetector
- Bitmap內存複用BitmapFactory.Options
3. 開始
3.1 準備圖片
首先準備一張超長的圖片,由於圖片太長了,我就不放到這裏來了,如果有需要後面可以去github自己下載
大概是這麼大的一張圖片,540X14894
如果全部加載到內存的話 用RGB_565的格式完全加載,預計消耗內存:30.68MB
3.2 效果圖
3.3 自定義控件 LongImageView
/**
* 加載長圖,這裏是橫圖加載,暫未實現雙機放大,手勢放大縮小
*/
public class LongImageView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {
private static final String TAG = LongImageView.class.getSimpleName();
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 mBitmap;
public LongImageView(Context context) {
super(context);
initView(context);
}
public LongImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public LongImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
//1.設置所需要的一些成員變量
mRect = new Rect();
//內存複用所需要的
mOptions = new BitmapFactory.Options();
//手勢識別
mGestureDetector = new GestureDetector(context, this);
mScroller = new Scroller(context);
setOnTouchListener(this);
}
//2.
public void setImage(InputStream is) {
//設置讀取圖片不加載到內存,只讀取圖片的屬性
mOptions.inJustDecodeBounds = true;
//這裏就不需要保存返回值bitmap,只是拿到options屬性
BitmapFactory.decodeStream(is, null, mOptions);
mImageWidth = mOptions.outWidth;
mImageHeight = mOptions.outHeight;
Log.d(TAG, "setImage: image.width:" + mImageWidth + ",height:" + mImageHeight);
//設置可變,內存複用
mOptions.inMutable = true;
mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
//設置爲false,加載圖片到內存中
mOptions.inJustDecodeBounds = false;
//區域解碼器
try {
mDecoder = BitmapRegionDecoder.newInstance(is, false);
} catch (IOException e) {
e.printStackTrace();
}
//開始渲染
requestLayout();
}
//3.讀取view的寬高,測量要加載的圖片要縮放成什麼樣子
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredHeight();
Log.d(TAG, "onMeasure: view.width:" + mViewWidth + ",height:" + mViewHeight);
//確定加載圖片的區域
mRect.left = 0;
mRect.top = 0;
mRect.right = mImageWidth;
//計算縮放比例
mScale = mViewWidth / (float) mImageWidth;
//初始,加載區域的底部就是加載控件的高度/縮放比例
mRect.bottom = (int) (mViewHeight / mScale);
Log.d(TAG, "onMeasure: scale:" + mScale);
Log.d(TAG, "onMeasure: bottom:" + mRect.bottom + ",right:" + mRect.right);
}
//4. 畫出具體的內容
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDecoder == null) {
return;
}
//真正的內存複用
//複用的bitmap必須跟即將解碼的bitmap尺寸大小一樣
mOptions.inBitmap = mBitmap;
//指定解碼區域
mBitmap = mDecoder.decodeRegion(mRect, mOptions);
//因爲圖片的尺寸比例未必跟控件的寬高一致,所以治理我們需要將圖片等比縮放到適應控件的寬高
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);
canvas.drawBitmap(mBitmap, matrix, null);
}
//5.處理觸摸事件
@Override
public boolean onTouch(View v, MotionEvent event) {
//完全交給手勢處理類
return mGestureDetector.onTouchEvent(event);
}
//6.手勢處理 按下事件
@Override
public boolean onDown(MotionEvent e) {
//如果正在滑動或者移動,強行停止
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
return true;
}
//7.處理滑動事件
/**
* @param e1 開始事件,手指按下去,獲取座標
* @param e2 獲取當前事件
* @param distanceX x移動距離
* @param distanceY y移動距離
* @return
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//上下移動的時候,需要改變現實區域
mRect.offset(0, (int) distanceY);
//移動時處理到底的處理邏輯
if (mRect.bottom > mImageHeight) {
mRect.bottom = mImageHeight;
mRect.top = (int) (mImageWidth - mViewHeight / mScale);
}
//到頂的處理邏輯
if (mRect.top < 0) {
mRect.top = 0;
mRect.bottom = (int) (mViewHeight / mScale);
}
Log.d(TAG, "onScroll: top:" + mRect.top + ",bottom:" + mRect.bottom);
//重繪
invalidate();
return false;
}
//8.處理慣性問題
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//這裏將velocityY取反纔是我們正常的向上向下
mScroller.fling(0, mRect.top, 0, (int) -velocityY, 0, 0, 0, (int) (mImageHeight - mViewHeight / mScale));
return false;
}
//9.處理計算結果
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.isFinished()) {
return;
}
if (mScroller.computeScrollOffset()) {
mRect.top = mScroller.getCurrY();
mRect.bottom = (int) (mRect.top + mViewHeight / mScale);
invalidate();
}
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
}
3.4 佈局加載
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.yuri.longimagedemo.LongImageView
android:id="@+id/bigView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>
3.5 加載assets中的長圖
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val inputStream = assets.open("test.jpg")
bigView?.setImage(inputStream)
}
3.6 最後看下內存佔用情況
最後奉上代碼