/**
* 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");
}
}