下面我们来手撸一个加载长图的控件
使用BitmapRegionDecoder
BitmapRegionDecoder可用于从图像解码矩形区域。BitmapRegionDecoder在原始图像较大且只需要部分图像时特别有用。
要创建BitmapRegionDecoder,请调用NewInstance(…)。给定BitmapRegionDecoder,用户可以重复调用decodeRegion()以获取指定区域的解码位图。
具体代码
private BitmapRegionDecoder mDecoder;
...
//创建一个区域解码器
try {
mDecoder = BitmapRegionDecoder.newInstance(is, false);
} catch (IOException e) {
e.printStackTrace();
}
...
//解码指定的区域
bitmap = mDecoder.decodeRegion(mRect, mOptions);
开启复用
- Bitmap一定要是可变的,即inmutable设置一定为ture;
- Android4.4以下的平台,需要保证inBitmap和即将要得到decode的Bitmap的尺寸规格一致;
- Android4.4及其以上的平台,只需要满足inBitmap的尺寸大于要decode得到的Bitmap的尺寸规格即可;
开启bitmap的复用需要使用
options.inBitmap
而使用这个需要的开启
//将inMutable设置true,inBitmap生效的条件之一
options.inMutable = true;
具体代码如下
private BitmapFactory.Options mOptions;
private int mImageWidth;
private int mImageHeight;
...
mOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, mOptions);
mImageWidth = mOptions.outWidth;
mImageHeight = mOptions.outHeight;
//开启复用
mOptions.inMutable = true;
//设置格式成RGB_565
mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
mOptions.inJustDecodeBounds = false;
计算显示的大小
重写onMeasure方法
private Rect mRect;
private int mViewWidth;
private int mViewHeight;
private float mScale;
...
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取测量的view的大小
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredHeight();
//确定要加载的图片的区域
mRect.left = 0;
mRect.top = 0;
mRect.right = mImageWidth;
//获取一个缩放因子
mScale = mViewWidth / (float) mImageWidth;
//高度就根据缩放比进行获取
mRect.bottom = (int) (mViewHeight / mScale);
}
显示到画布上
重写onDraw方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//如果解码器拿不到,表示没有设置过要显示的图片
if (null == mDecoder) {
return;
}
//复用上一张bitmap
mOptions.inBitmap = bitmap;
//解码指定的区域
bitmap = mDecoder.decodeRegion(mRect, mOptions);
//把得到的矩阵大小的内存进行缩放 得到view的大小
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);
//画出来
canvas.drawBitmap(bitmap, matrix, null);
}
手势滑动
-
设置onTouch交给GestureDetector
private GestureDetector mGestureDetector; private Scroller mScroller; public BigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //指定要加载的区域 mRect = new Rect(); //需要复用 mOptions = new BitmapFactory.Options(); //手势识别类 mGestureDetector = new GestureDetector(context, this); //设置onTouchListener setOnTouchListener(this); //滑动帮助 mScroller = new Scroller(context); } @Override public boolean onTouch(View v, MotionEvent event) { //交给手势处理 return mGestureDetector.onTouchEvent(event); }
-
处理滑动和快速滑动事件
/** * 手按下的回调 */ @Override public boolean onDown(MotionEvent e) { //如果移动还没有停止,强制停止 if (!mScroller.isFinished()) { mScroller.forceFinished(true); } //继续接收后续事件 return true; } /** * @param e1 接下 * @param e2 移动 * @param distanceX 左右移动时的距离 * @param distanceY 上下移动时的距离 * @return */ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { //上下移动的时候,需要改变显示区域 改mRect mRect.offset(0, (int) distanceY); //处理移动时已经移到了两个顶端的问题 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); } invalidate(); return false; } /** * 处理惯性问题 * * @param e1 * @param e2 * @param velocityX 每秒移动的x点 * @param velocityY * @return */ @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { //做计算 mScroller.fling(0, mRect.top, 0, (int) -velocityY, 0, 0, 0, mImageHeight - (int) (mViewHeight / mScale)); return false; } @Override public void computeScroll() { if (mScroller.isFinished()) { return; } //true 表示当前滑动还没有结束 if (mScroller.computeScrollOffset()) { mRect.top = mScroller.getCurrY(); mRect.bottom = mRect.top + (int) (mViewHeight / mScale); invalidate(); } }
如果想要增加更多的手势,我们下期再见