Android长图加载

android-studio 同时被 2 个专栏收录
2 篇文章 0 订阅
1 篇文章 0 订阅

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 最后看下内存占用情况

最后奉上代码

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值