/**
* A helper function to return the byte usage per pixel of a bitmap based on its configuration.
*/
static int getBytesPerPixel(Config config) {
if (config == Config.ARGB_8888) {
return 4;
} else if (config == Config.RGB_565) {
return 2;
} else if (config == Config.ARGB_4444) {
return 2;
} else if (config == Config.ALPHA_8) {
return 1;
}
return 1;
}
requeLayout()、invalidate()
- requeLayout() : 控件会重新执行 onMesure() onLayout() ,比如 ScrollView中有LinearLaout ,LinearLayout里面有纵向排列的ImageView和TextView,那么假如ImageView的长宽发生了变化,而要立即在手机上显示这个变化的话,就可调用 imageView.requestLayout();这样的话ScrollView 会重新执行onMesure()这个方法会确定控件的大小然后在确定出自己的宽高,最后在执行onLayout(),这个方法是对所有的子控件进行定位的。他只调用measure()和layout()过程,不会调用draw()。
- invalidate() :是自定义View 的时候,重新执行onDraw()方法,当view只在内容和可见度方面发生变化时调用。
按照原图尺寸加载,那么屏幕肯定是不够大的,并且考虑到内存的情况,不可能一次性整图加载到内存中,所以肯定是局部加载,那么就需要用到一个类
BitmapRegionDecoder,还要实现手势检测,然后根据手势计算加载矩形的上下左右的值,在onDrow中进行绘制。
这个例子实现的功能:如果图片的宽度比手机屏幕的宽度窄,计算一个缩放比例,在绘制的时候,对图片进行放大,是图片宽度与手机屏幕宽度一致。如果图片的宽度比手机屏幕的宽度大,缩放系数就为1f。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BigView bigView = findViewById(R.id.bigView);
InputStream is = null;
try {
is = getAssets().open("world.jpg");
} catch (IOException e) {
e.printStackTrace();
}
bigView.setImage(is);
}
}
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
public class BigView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {
private final Rect mRect;
private final BitmapFactory.Options mOptions;
private final GestureDetector mGestureDetetor;
private final Scroller mScroller;
private int mImageWidth;
private int mImageHeight;
private BitmapRegionDecoder mDecoder;
private int mViewWidth;
private int mViewHeight;
private float mScale = 1f;
private Bitmap mBitmap;
public BigView(Context context) {
this(context, null);
}
public BigView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 第一步,设置BigView所需要的一些成员变量
mRect = new Rect();
// 内存复用
mOptions = new BitmapFactory.Options();
// 手势识别
mGestureDetetor = new GestureDetector(context, this);
// 滚动类
mScroller = new Scroller(context);
setOnTouchListener(this);
}
// 第2步,设置图片,得到图片的信息
public void setImage(InputStream is) {
// 获取图片宽和高, 注意:不能将图片整个加载进内存
mOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, mOptions);
mImageWidth = mOptions.outWidth;
mImageHeight = mOptions.outHeight;
Log.e("TAG", " mImageWidth = " + mImageWidth);
Log.e("TAG", " mImageHeight = " + mImageHeight);
// 开启复用
mOptions.inMutable = true;
// 设置格式为RGB565
mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
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);
Log.e("TAG", " onMeasure()");
// 得到view的宽高
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredHeight();
// 确定加载图片的区域
mRect.left = 0;
mRect.top = 0;
if (mImageWidth > mViewWidth) {
mRect.right = mViewWidth;
} else {
mRect.right = mImageWidth;
mScale = mViewWidth / (float) mImageWidth;
}
// 计算缩放因子
//mScale = mViewWidth / (float) mImageWidth;
//mRect.bottom = (int) (mViewHeight / mScale);
if (mImageHeight > mViewHeight) {
mRect.bottom = (int) (mViewHeight / mScale);
} else {
mRect.bottom = (int) (mImageHeight / mScale);
}
Log.e("TAG", " mViewWidth = " + mViewWidth);
Log.e("TAG", " mViewHeight = " + mViewHeight);
Log.e("TAG", " mScale = " + mScale);
Log.e("TAG", " mRect.bottom = " + mRect.bottom);
}
// 第4步,画出具体的内容
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e("TAG", " onDraw()");
// 判断解码器是不是为null,如果解码器没拿到,表示没有设置过图片
if (mDecoder == null) {
return;
}
// 真正内存复用 // 复用的bitmap必须跟即将解码的bitmap尺寸一样
mOptions.inBitmap = mBitmap;
// 指定解码区域
mBitmap = mDecoder.decodeRegion(mRect, mOptions);
// 得到一个矩阵进行缩放,相当于得到view的大小
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);
canvas.drawBitmap(mBitmap, matrix, null);
}
// 第5步,处理点击事件
/*
* 在onTouch()方法中,我们调用GestureDetector的onTouchEvent()方法,将捕捉到的MotionEvent交给GestureDetector
* 来分析是否有合适的callback函数来处理用户的手势
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
// 直接将事件交给手势事件处理
return mGestureDetetor.onTouchEvent(event);
}
// 第6步, 手按下去
// 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发
@Override
public boolean onDown(MotionEvent e) {
Log.e("TAG", "onDown");
// 如果移动没有停止,强行停止
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
// 继续接收后续事件
return true;
}
// 第7步,处理滑动事件
// 用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发
// e1:开始事件,手指按下去,开始获取坐标
// e2: 获取当前事件坐标
// xy : xy轴移动的距离
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.e("TAG", "onScroll() distanceX = " + distanceX + ", distanceY = " + distanceY);
// 上下移动的时候,mRect需要改变显示的区域
mRect.offset((int) distanceX, (int) distanceY);
// 移动时,处理到达顶部和底部的情况
if (mRect.right > mImageWidth) {
mRect.right = mImageWidth;
mRect.left = mImageWidth - (int) (mViewWidth / mScale);
}
if (mRect.left < 0) {
mRect.left = 0;
mRect.right = (int) (mViewWidth / mScale);
}
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;
}
// 第8步,处理惯性问题
// 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// 参数解释:
// e1:第1个ACTION_DOWN MotionEvent
// e2:最后一个ACTION_MOVE MotionEvent
// velocityX:X轴上的移动速度,像素/秒
// velocityY:Y轴上的移动速度,像素/秒
//惯性滑动。 给定一个初始速度(velocityX,velocityY),该方法内部会根据这个速度去计算需要滑动的距离以及需要耗费的时间。通常用于:界面的惯性滑动等。
Log.e("TAG", " onFling()");
mScroller.fling(mRect.left,
mRect.top,
(int) -velocityX,
(int) -velocityY,
0,
mImageWidth - (int) (mViewWidth / mScale),
0,
mImageHeight - (int) (mViewHeight / mScale));
Log.e("TAG", " velocityY = " + velocityY);
Log.e("TAG", " velocityX = " + velocityY);
return false;
}
// 第9步,处理计算结果
@Override
public void computeScroll() {
Log.e("TAG", "computeScroll() ");
if (mScroller.isFinished()) {
return;
}
if (mScroller.computeScrollOffset()) {
mRect.left = mScroller.getCurrX();
mRect.right = mRect.left + (int) (mViewWidth / mScale);
mRect.top = mScroller.getCurrY();
mRect.bottom = mRect.top + (int) (mViewHeight / mScale);
invalidate();
}
}
/*
* 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发
* 注意和onDown()的区别,强调的是没有松开或者拖动的状态
*/
@Override
public void onShowPress(MotionEvent e) {
Log.e("TAG", "onShowPress");
}
// 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.e("TAG", "onSingleTapUp");
return false;
}
// 用户长按触摸屏,由多个MotionEvent ACTION_DOWN触发
@Override
public void onLongPress(MotionEvent e) {
Log.e("TAG", "onLongPress");
}
}