android自定义view-加载长图

android再加载大图,长图时,为了减少内存的占用,可以不必一次性把整张图加载到内存中,而是采用加载部分的办法,随着图片的滑动再加载展现的部分。

加载Bitmap时,通常我们使用BitmapFactory,可以从File文件、InPutStreaml流、Byte数组中加载一张图片。

而BitmapRegionDecoder,可以加载图片的一个Rect区域大小的图。得需配置BitmapFactory.Options()中的一些参数。

另外还得处理滑动事件,来保证图片随着屏幕的滑动,而不断的加载。

1.初始化

 private void init(Context context) {
        //图片decode的矩形大小
        mRect = new Rect();
        //canvas绘制时需要的参数
        matrix = new Matrix();
        //处理手势相关的
        mGestureDetector = new GestureDetector(context, this);
        //滑动
        mScroller = new Scroller(context);
        setOnTouchListener(this);
    }

2.设置一个流

  1) options.inJustDecodeBounds 设置为true,可以获得图片的宽高,而不必把图片加载到内存。通过options.outWidth; options.outHeight;获得图片的宽高。

If set to true, the decoder will return null (no bitmap), 
but the out... fields will still be set, 
allowing the caller to query the bitmap without having to
allocate the memory for its pixels.

 public boolean inJustDecodeBounds;

 options.inMutable = true,返回一个可变的Bitmap。

If set, decode methods will always return a mutable Bitmap
 instead of an immutable one.

 public boolean inMutable;

 创建一个区域解码器BitmapRegionDecoder.newInstance(is, false);

 public void setImage(InputStream is) {
        options = new BitmapFactory.Options();
        //为true则只加载图片的大小,返回一个null值,不会把图片加载到内存
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, options);
        //得到图片的宽高
        imgWidth = options.outWidth;
        imgHeight = options.outHeight;
        //默认是false,正常加载图片
        options.inJustDecodeBounds = false;
        //返回的一个可变的Bitmap
        options.inMutable = true;
        try {
            //区域解码器,可以从一个图片上解码一个Rect大小的区域
            decoder = BitmapRegionDecoder.newInstance(is, false);
            //重新测量、布局、绘制
            requestLayout();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

3. 在onMeasure中得到View的宽高。 设置Rect的大小,告知BitmapRegionDecoder要加载的图片区域。Rect的初始left和top都是0,right是图片的宽度。

通过View的宽度除以图片的宽度,得到一个缩放因子scale。再用View的高度除以这个scale,得到的就是初始的Rect的bottom值,之所以这计算,就是为了在onDraw时,对加载后的Bitmap按照scale进行缩放,以保证显示的屏幕中的图片大小是View的大小。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        viewWidth = getMeasuredWidth();
        viewHeight = getMeasuredHeight();
        //设置要加载图片的矩形left、top、right、bottom值
        mRect.left = 0;
        mRect.top = 0;
        //图片加载的矩形右侧大小
        mRect.right = imgWidth;
        //用屏幕宽度除以图片宽度,得到缩放因子。
        // 这个是为了配合onDraw时对画布进行缩放,使画布宽度正好是View的宽度
        mScale = viewWidth / (float) imgWidth;
        //通过缩放因子,计算出矩形底部,保证图片缩放后的高度正好是View的高度
        mRect.bottom = (int) (viewHeight / mScale);
    }

4. onDraw 

     options.inBitmap = mBitmap; 这个是为了对mBitmap申请内存的复用。

   如果设置了inBitmap,在加载内容时,会尝试复用这个bitmap,如果不能使用,则会抛出异常。

If set, decode methods that take the Options object 
will attempt to reuse this bitmap when loading content. 
If the decode operation cannot use this bitmap, 
the decode method will throw an IllegalArgumentException.

public Bitmap inBitmap;

 options.inPreferredConfig 这个可以设置图片的加载格式。默认是ARGB_8888

 decoder.decodeRegion(mRect, options) 加载一个mRect大小的bitmap

   matrix.setScale(mScale, mScale); 加入缩放因子
    canvas.drawBitmap(mBitmap, matrix, null); 绘制,这样就可以显示到屏幕上了。

  @Override
    protected void onDraw(Canvas canvas) {
        if (decoder == null) {
            return;
        }
        //内存复用,decode的时,会复用mBitmap所占用的内存
        options.inBitmap = mBitmap;
        //设置图片解码格式。默认是ARGB_8888
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        //通过区域解码器,decode出一个mRect大小的Bitmap
        mBitmap = decoder.decodeRegion(mRect, options);
        matrix.setScale(mScale, mScale);
        //把bitmap绘制在canvas上。然后显示在屏幕上
        canvas.drawBitmap(mBitmap, matrix, null);
    }

5.滑动事件处理。

  //按下事件
    @Override
    public boolean onDown(MotionEvent e) {
        if (!mScroller.isFinished()) {
            mScroller.forceFinished(true);
        }
        return true;
    }
 @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        mRect.offset(0, (int) distanceY);
        if (mRect.bottom > imgHeight) {
            mRect.bottom = imgHeight;
            //Img的高度,减去加载一屏的高度,就是此时的top值。
            mRect.top = imgHeight - (int) (viewHeight / mScale);
        }
        if (mRect.top < 0) {
            mRect.top = 0;
            mRect.bottom = (int) (viewHeight / mScale);
        }
        invalidate();
        return false;
    }
 @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        //maxY: Y轴最大的滑动距离 imgHeight - (int) (viewHeight / mScale)
        //用Img的总高度,减去第一次加载的高度的高度,剩下的就是未加载的,也就是可以滑动的最大距离。
        mScroller.fling(0, mRect.top, 0, (int) -velocityY, 0, 0, 0,
                imgHeight - (int) (viewHeight / mScale));
        return false;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.isFinished()) {
            return;
        }
        if (mScroller.computeScrollOffset()) {
            mRect.top = mScroller.getCurrY();
            mRect.bottom = mRect.top + (int) (viewHeight / mScale);
            invalidate();
        }
    }

6.  GestureDetector.OnGestureListener 手势监听源码分析。

public class GestureDetector {
   
    public interface OnGestureListener {
        //按下事件
        boolean onDown(MotionEvent e);
       
        void onShowPress(MotionEvent e);
       //单指点击

        boolean onSingleTapUp(MotionEvent e);
       //滑动事件处理
     
        boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
        //长按事件
        void onLongPress(MotionEvent e);
       //手指抬起后,滑动事件处理
        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
    }

GestureDetector.OnGestureListener 这个就是对事件类型做了一个简单封装,接口中定义的方法都是在onTouchEvent时处理的。

 case MotionEvent.ACTION_DOWN:
   ...
   handled |= mListener.onDown(ev);
      break;
 case MotionEvent.ACTION_MOVE:
   ...
  handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
      break;
 case MotionEvent.ACTION_UP:
 ...
 handled = mListener.onSingleTapUp(ev);
 ...
 handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
      break;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

niuyongzhi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值