Android長圖加載

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 最後看下內存佔用情況

最後奉上代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章