Android不同的圖片模式,每個(gè)像素占用的字節(jié)大小
/**
* 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() : 控件會(huì)重新執(zhí)行 onMesure() onLayout() ,比如 ScrollView中有LinearLaout ,LinearLayout里面有縱向排列的ImageView和TextView,那么假如ImageView的長(zhǎng)寬發(fā)生了變化,而要立即在手機(jī)上顯示這個(gè)變化的話,就可調(diào)用 imageView.requestLayout();這樣的話ScrollView 會(huì)重新執(zhí)行onMesure()這個(gè)方法會(huì)確定控件的大小然后在確定出自己的寬高,最后在執(zhí)行onLayout(),這個(gè)方法是對(duì)所有的子控件進(jìn)行定位的。他只調(diào)用measure()和layout()過(guò)程,不會(huì)調(diào)用draw()。
- invalidate() :是自定義View 的時(shí)候,重新執(zhí)行onDraw()方法,當(dāng)view只在內(nèi)容和可見(jiàn)度方面發(fā)生變化時(shí)調(diào)用。
按照原圖尺寸加載,那么屏幕肯定是不夠大的,并且考慮到內(nèi)存的情況,不可能一次性整圖加載到內(nèi)存中,所以肯定是局部加載,那么就需要用到一個(gè)類
BitmapRegionDecoder,還要實(shí)現(xiàn)手勢(shì)檢測(cè),然后根據(jù)手勢(shì)計(jì)算加載矩形的上下左右的值,在onDrow中進(jìn)行繪制。
這個(gè)例子實(shí)現(xiàn)的功能:如果圖片的寬度比手機(jī)屏幕的寬度窄,計(jì)算一個(gè)縮放比例,在繪制的時(shí)候,對(duì)圖片進(jìn)行放大,是圖片寬度與手機(jī)屏幕寬度一致。如果圖片的寬度比手機(jī)屏幕的寬度大,縮放系數(shù)就為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);
// 第一步,設(shè)置BigView所需要的一些成員變量
mRect = new Rect();
// 內(nèi)存復(fù)用
mOptions = new BitmapFactory.Options();
// 手勢(shì)識(shí)別
mGestureDetetor = new GestureDetector(context, this);
// 滾動(dòng)類
mScroller = new Scroller(context);
setOnTouchListener(this);
}
// 第2步,設(shè)置圖片,得到圖片的信息
public void setImage(InputStream is) {
// 獲取圖片寬和高, 注意:不能將圖片整個(gè)加載進(jìn)內(nèi)存
mOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, mOptions);
mImageWidth = mOptions.outWidth;
mImageHeight = mOptions.outHeight;
Log.e("TAG", " mImageWidth = " + mImageWidth);
Log.e("TAG", " mImageHeight = " + mImageHeight);
// 開(kāi)啟復(fù)用
mOptions.inMutable = true;
// 設(shè)置格式為RGB565
mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
mOptions.inJustDecodeBounds = false;
// 區(qū)域解碼器
try {
mDecoder = BitmapRegionDecoder.newInstance(is, false);
} catch (IOException e) {
e.printStackTrace();
}
requestLayout();
}
// 第3步,開(kāi)始測(cè)量,得到view的寬高,測(cè)量加載的圖片到底縮放成什么樣子
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e("TAG", " onMeasure()");
// 得到view的寬高
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredHeight();
// 確定加載圖片的區(qū)域
mRect.left = 0;
mRect.top = 0;
if (mImageWidth > mViewWidth) {
mRect.right = mViewWidth;
} else {
mRect.right = mImageWidth;
mScale = mViewWidth / (float) mImageWidth;
}
// 計(jì)算縮放因子
//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步,畫(huà)出具體的內(nèi)容
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e("TAG", " onDraw()");
// 判斷解碼器是不是為null,如果解碼器沒(méi)拿到,表示沒(méi)有設(shè)置過(guò)圖片
if (mDecoder == null) {
return;
}
// 真正內(nèi)存復(fù)用 // 復(fù)用的bitmap必須跟即將解碼的bitmap尺寸一樣
mOptions.inBitmap = mBitmap;
// 指定解碼區(qū)域
mBitmap = mDecoder.decodeRegion(mRect, mOptions);
// 得到一個(gè)矩陣進(jìn)行縮放,相當(dāng)于得到view的大小
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);
canvas.drawBitmap(mBitmap, matrix, null);
}
// 第5步,處理點(diǎn)擊事件
/*
* 在onTouch()方法中,我們調(diào)用GestureDetector的onTouchEvent()方法,將捕捉到的MotionEvent交給GestureDetector
* 來(lái)分析是否有合適的callback函數(shù)來(lái)處理用戶的手勢(shì)
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
// 直接將事件交給手勢(shì)事件處理
return mGestureDetetor.onTouchEvent(event);
}
// 第6步, 手按下去
// 用戶輕觸觸摸屏,由1個(gè)MotionEvent ACTION_DOWN觸發(fā)
@Override
public boolean onDown(MotionEvent e) {
Log.e("TAG", "onDown");
// 如果移動(dòng)沒(méi)有停止,強(qiáng)行停止
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
// 繼續(xù)接收后續(xù)事件
return true;
}
// 第7步,處理滑動(dòng)事件
// 用戶按下觸摸屏,并拖動(dòng),由1個(gè)MotionEvent ACTION_DOWN, 多個(gè)ACTION_MOVE觸發(fā)
// e1:開(kāi)始事件,手指按下去,開(kāi)始獲取坐標(biāo)
// e2: 獲取當(dāng)前事件坐標(biāo)
// xy : xy軸移動(dòng)的距離
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.e("TAG", "onScroll() distanceX = " + distanceX + ", distanceY = " + distanceY);
// 上下移動(dòng)的時(shí)候,mRect需要改變顯示的區(qū)域
mRect.offset((int) distanceX, (int) distanceY);
// 移動(dòng)時(shí),處理到達(dá)頂部和底部的情況
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步,處理慣性問(wèn)題
// 用戶按下觸摸屏、快速移動(dòng)后松開(kāi),由1個(gè)MotionEvent ACTION_DOWN, 多個(gè)ACTION_MOVE, 1個(gè)ACTION_UP觸發(fā)
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// 參數(shù)解釋:
// e1:第1個(gè)ACTION_DOWN MotionEvent
// e2:最后一個(gè)ACTION_MOVE MotionEvent
// velocityX:X軸上的移動(dòng)速度,像素/秒
// velocityY:Y軸上的移動(dòng)速度,像素/秒
//慣性滑動(dòng)。 給定一個(gè)初始速度(velocityX,velocityY),該方法內(nèi)部會(huì)根據(jù)這個(gè)速度去計(jì)算需要滑動(dòng)的距離以及需要耗費(fèi)的時(shí)間。通常用于:界面的慣性滑動(dòng)等。
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步,處理計(jì)算結(jié)果
@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();
}
}
/*
* 用戶輕觸觸摸屏,尚未松開(kāi)或拖動(dòng),由一個(gè)1個(gè)MotionEvent ACTION_DOWN觸發(fā)
* 注意和onDown()的區(qū)別,強(qiáng)調(diào)的是沒(méi)有松開(kāi)或者拖動(dòng)的狀態(tài)
*/
@Override
public void onShowPress(MotionEvent e) {
Log.e("TAG", "onShowPress");
}
// 用戶(輕觸觸摸屏后)松開(kāi),由一個(gè)1個(gè)MotionEvent ACTION_UP觸發(fā)
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.e("TAG", "onSingleTapUp");
return false;
}
// 用戶長(zhǎng)按觸摸屏,由多個(gè)MotionEvent ACTION_DOWN觸發(fā)
@Override
public void onLongPress(MotionEvent e) {
Log.e("TAG", "onLongPress");
}
}