Android多點(diǎn)觸控實(shí)現(xiàn)對(duì)圖片放大縮小平移,慣性滑動(dòng)等功能
文章將在原有基礎(chǔ)之上做了一些擴(kuò)展功能:
1.圖片的慣性滑動(dòng)
2.圖片縮放小于正常比例時(shí),松手會(huì)自動(dòng)回彈成正常比例
3.圖片縮放大于最大比例時(shí),松手會(huì)自動(dòng)回彈成最大比例

實(shí)現(xiàn)圖片的縮放,平移,雙擊縮放等基本功能的代碼如下,每一行代碼我都做了詳細(xì)的注釋
public class ZoomImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener,
View.OnTouchListener , ViewTreeObserver.OnGlobalLayoutListener{
/**
* 縮放手勢(shì)的監(jiān)測(cè)
*/
private ScaleGestureDetector mScaleGestureDetector;
/**
* 監(jiān)聽(tīng)手勢(shì)
*/
private GestureDetector mGestureDetector;
/**
* 對(duì)圖片進(jìn)行縮放平移的Matrix
*/
private Matrix mScaleMatrix;
/**
* 第一次加載圖片時(shí)調(diào)整圖片縮放比例,使圖片的寬或者高充滿(mǎn)屏幕
*/
private boolean mFirst;
/**
* 圖片的初始化比例
*/
private float mInitScale;
/**
* 圖片的最大比例
*/
private float mMaxScale;
/**
* 雙擊圖片放大的比例
*/
private float mMidScale;
/**
* 是否正在自動(dòng)放大或者縮小
*/
private boolean isAutoScale;
//-----------------------------------------------
/**
* 上一次觸控點(diǎn)的數(shù)量
*/
private int mLastPointerCount;
/**
* 是否可以拖動(dòng)
*/
private boolean isCanDrag;
/**
* 上一次滑動(dòng)的x和y坐標(biāo)
*/
private float mLastX;
private float mLastY;
/**
* 可滑動(dòng)的臨界值
*/
private int mTouchSlop;
/**
* 是否用檢查左右邊界
*/
private boolean isCheckLeftAndRight;
/**
* 是否用檢查上下邊界
*/
private boolean isCheckTopAndBottom;
public ZoomImageView(Context context) {
this(context, null, 0);
}
public ZoomImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//一定要將圖片的ScaleType設(shè)置成Matrix類(lèi)型的
setScaleType(ScaleType.MATRIX);
//初始化縮放手勢(shì)監(jiān)聽(tīng)器
mScaleGestureDetector = new ScaleGestureDetector(context,this);
//初始化矩陣
mScaleMatrix = new Matrix();
setOnTouchListener(this);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
//初始化手勢(shì)檢測(cè)器,監(jiān)聽(tīng)雙擊事件
mGestureDetector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onDoubleTap(MotionEvent e) {
//如果是正在自動(dòng)縮放,則直接返回,不進(jìn)行處理
if (isAutoScale) return true;
//得到點(diǎn)擊的坐標(biāo)
float x = e.getX();
float y = e.getY();
//如果當(dāng)前圖片的縮放值小于指定的雙擊縮放值
if (getScale() < mMidScale){
//進(jìn)行自動(dòng)放大
post(new AutoScaleRunnable(mMidScale,x,y));
}else{
//當(dāng)前圖片的縮放值大于初試縮放值,則自動(dòng)縮小
post(new AutoScaleRunnable(mInitScale,x,y));
}
return true;
}
});
}
/**
* 當(dāng)view添加到window時(shí)調(diào)用,早于onGlobalLayout,因此可以在這里注冊(cè)監(jiān)聽(tīng)器
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
/**
* 當(dāng)view從window上移除時(shí)調(diào)用,因此可以在這里移除監(jiān)聽(tīng)器
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
/**
* 當(dāng)布局樹(shù)發(fā)生變化時(shí)會(huì)調(diào)用此方法,我們可以在此方法中獲得控件的寬和高
*/
@Override
public void onGlobalLayout() {
//只有當(dāng)?shù)谝淮渭虞d圖片的時(shí)候才會(huì)進(jìn)行初始化,用一個(gè)變量mFirst控制
if (!mFirst){
mFirst = true;
//得到控件的寬和高
int width = getWidth();
int height = getHeight();
//得到當(dāng)前ImageView中加載的圖片
Drawable d = getDrawable();
if(d == null){//如果沒(méi)有圖片,則直接返回
return;
}
//得到當(dāng)前圖片的寬和高,圖片的寬和高不一定等于控件的寬和高
//因此我們需要將圖片的寬和高與控件寬和高進(jìn)行判斷
//將圖片完整的顯示在屏幕中
int dw = d.getIntrinsicWidth();
int dh = d.getIntrinsicHeight();
//我們定義一個(gè)臨時(shí)變量,根據(jù)圖片與控件的寬高比例,來(lái)確定這個(gè)最終縮放值
float scale = 1.0f;
//如果圖片寬度大于控件寬度,圖片高度小于控件高度
if (dw>width && dh<height){
//我們需要將圖片寬度縮小,縮小至控件的寬度
//至于為什么要這樣計(jì)算,我們可以這樣想
//我們調(diào)用matrix.postScale(scale,scale)時(shí),寬和高都要乘以scale的
//當(dāng)前我們的圖片寬度是dw,dw*scale=dw*(width/dw)=width,這樣就等于控件寬度了
//我們的高度同時(shí)也乘以scale,這樣能夠保證圖片的寬高比不改變,圖片不變形
scale = width * 1.0f / dw;
}
//如果圖片的寬度小于控件寬度,圖片高度大于控件高度
if (dw<width && dh>height){
//我們就應(yīng)該將圖片的高度縮小,縮小至控件的高度,計(jì)算方法同上
scale = height * 1.0f / dh;
}
//如果圖片的寬度小于控件寬度,高度小于控件高度時(shí),我們應(yīng)該將圖片放大
//比如圖片寬度是控件寬度的1/2 ,圖片高度是控件高度的1/4
//如果我們將圖片放大4倍,則圖片的高度是和控件高度一樣了,但是圖片寬度就超出控件寬度了
//因此我們應(yīng)該選擇一個(gè)最小值,那就是將圖片放大2倍,此時(shí)圖片寬度等于控件寬度
//同理,如果圖片寬度大于控件寬度,圖片高度大于控件高度,我們應(yīng)該將圖片縮小
//縮小的倍數(shù)也應(yīng)該為那個(gè)最小值
if ((dw < width && dh < height) || (dw > width && dh > height)){
scale = Math.min(width * 1.0f / dw , height * 1.0f / dh);
}
//我們還應(yīng)該對(duì)圖片進(jìn)行平移操作,將圖片移動(dòng)到屏幕的居中位置
//控件寬度的一半減去圖片寬度的一半即為圖片需要水平移動(dòng)的距離
//高度同理,大家可以畫(huà)個(gè)圖看一看
int dx = width/2 - dw/2;
int dy = height/2 - dh/2;
//對(duì)圖片進(jìn)行平移,dx和dy分別表示水平和豎直移動(dòng)的距離
mScaleMatrix.postTranslate(dx, dy);
//對(duì)圖片進(jìn)行縮放,scale為縮放的比例,后兩個(gè)參數(shù)為縮放的中心點(diǎn)
mScaleMatrix.postScale(scale, scale, width / 2, height / 2);
//將矩陣作用于我們的圖片上,圖片真正得到了平移和縮放
setImageMatrix(mScaleMatrix);
//初始化一下我們的幾個(gè)縮放的邊界值
mInitScale = scale;
//最大比例為初始比例的4倍
mMaxScale = mInitScale * 4;
//雙擊放大比例為初始化比例的2倍
mMidScale = mInitScale * 2;
}
}
/**
* 獲得圖片當(dāng)前的縮放比例值
*/
private float getScale(){
//Matrix為一個(gè)3*3的矩陣,一共9個(gè)值
float[] values = new float[9];
//將Matrix的9個(gè)值映射到values數(shù)組中
mScaleMatrix.getValues(values);
//拿到Matrix中的MSCALE_X的值,這個(gè)值為圖片寬度的縮放比例,因?yàn)閳D片高度
//的縮放比例和寬度的縮放比例一致,我們?nèi)∫粋€(gè)就可以了
//我們還可以 return values[Matrix.MSCALE_Y];
return values[Matrix.MSCALE_X];
}
/**
* 獲得縮放后圖片的上下左右坐標(biāo)以及寬高
*/
private RectF getMatrixRectF(){
//獲得當(dāng)錢(qián)圖片的矩陣
Matrix matrix = mScaleMatrix;
//創(chuàng)建一個(gè)浮點(diǎn)類(lèi)型的矩形
RectF rectF = new RectF();
//得到當(dāng)前的圖片
Drawable d = getDrawable();
if (d != null){
//使這個(gè)矩形的寬和高同當(dāng)前圖片一致
rectF.set(0,0,d.getIntrinsicWidth(),d.getIntrinsicHeight());
//將矩陣映射到矩形上面,之后我們可以通過(guò)獲取到矩陣的上下左右坐標(biāo)以及寬高
//來(lái)得到縮放后圖片的上下左右坐標(biāo)和寬高
matrix.mapRect(rectF);
}
return rectF;
}
/**
* 當(dāng)縮放時(shí)檢查邊界并且使圖片居中
*/
private void checkBorderAndCenterWhenScale(){
if (getDrawable() == null){
return;
}
//初始化水平和豎直方向的偏移量
float deltaX = 0.0f;
float deltaY = 0.0f;
//得到控件的寬和高
int width = getWidth();
int height = getHeight();
//拿到當(dāng)前圖片對(duì)應(yīng)的矩陣
RectF rectF = getMatrixRectF();
//如果當(dāng)前圖片的寬度大于控件寬度,當(dāng)前圖片處于放大狀態(tài)
if (rectF.width() >= width){
//如果圖片左邊坐標(biāo)是大于0的,說(shuō)明圖片左邊離控件左邊有一定距離,
//左邊會(huì)出現(xiàn)一個(gè)小白邊
if (rectF.left > 0){
//我們將圖片向左邊移動(dòng)
deltaX = -rectF.left;
}
//如果圖片右邊坐標(biāo)小于控件寬度,說(shuō)明圖片右邊離控件右邊有一定距離,
//右邊會(huì)出現(xiàn)一個(gè)小白邊
if (rectF.right <width){
//我們將圖片向右邊移動(dòng)
deltaX = width - rectF.right;
}
}
//上面是調(diào)整寬度,這是調(diào)整高度
if (rectF.height() >= height){
//如果上面出現(xiàn)小白邊,則向上移動(dòng)
if (rectF.top > 0){
deltaY = -rectF.top;
}
//如果下面出現(xiàn)小白邊,則向下移動(dòng)
if (rectF.bottom < height){
deltaY = height - rectF.bottom;
}
}
//如果圖片的寬度小于控件的寬度,我們要對(duì)圖片做一個(gè)水平的居中
if (rectF.width() < width){
deltaX = width/2f - rectF.right + rectF.width()/2f;
}
//如果圖片的高度小于控件的高度,我們要對(duì)圖片做一個(gè)豎直方向的居中
if (rectF.height() < height){
deltaY = height/2f - rectF.bottom + rectF.height()/2f;
}
//將平移的偏移量作用到矩陣上
mScaleMatrix.postTranslate(deltaX, deltaY);
}
/**
* 平移時(shí)檢查上下左右邊界
*/
private void checkBorderWhenTranslate() {
//獲得縮放后圖片的相應(yīng)矩形
RectF rectF = getMatrixRectF();
//初始化水平和豎直方向的偏移量
float deltaX = 0.0f;
float deltaY = 0.0f;
//得到控件的寬度
int width = getWidth();
//得到控件的高度
int height = getHeight();
//如果是需要檢查左和右邊界
if (isCheckLeftAndRight){
//如果左邊出現(xiàn)的白邊
if (rectF.left > 0){
//向左偏移
deltaX = -rectF.left;
}
//如果右邊出現(xiàn)的白邊
if (rectF.right < width){
//向右偏移
deltaX = width - rectF.right;
}
}
//如果是需要檢查上和下邊界
if (isCheckTopAndBottom){
//如果上面出現(xiàn)白邊
if (rectF.top > 0){
//向上偏移
deltaY = -rectF.top;
}
//如果下面出現(xiàn)白邊
if (rectF.bottom < height){
//向下偏移
deltaY = height - rectF.bottom;
}
}
mScaleMatrix.postTranslate(deltaX,deltaY);
}
/**
* 自動(dòng)放大縮小,自動(dòng)縮放的原理是使用View.postDelay()方法,每隔16ms調(diào)用一次
* run方法,給人視覺(jué)上形成一種動(dòng)畫(huà)的效果
*/
private class AutoScaleRunnable implements Runnable{
//放大或者縮小的目標(biāo)比例
private float mTargetScale;
//可能是BIGGER,也可能是SMALLER
private float tempScale;
//放大縮小的中心點(diǎn)
private float x;
private float y;
//比1稍微大一點(diǎn),用于放大
private final float BIGGER = 1.07f;
//比1稍微小一點(diǎn),用于縮小
private final float SMALLER = 0.93f;
//構(gòu)造方法,將目標(biāo)比例,縮放中心點(diǎn)傳入,并且判斷是要放大還是縮小
public AutoScaleRunnable(float targetScale , float x , float y){
this.mTargetScale = targetScale;
this.x = x;
this.y = y;
//如果當(dāng)前縮放比例小于目標(biāo)比例,說(shuō)明要自動(dòng)放大
if (getScale() < mTargetScale){
//設(shè)置為Bigger
tempScale = BIGGER;
}
//如果當(dāng)前縮放比例大于目標(biāo)比例,說(shuō)明要自動(dòng)縮小
if (getScale() > mTargetScale){
//設(shè)置為Smaller
tempScale = SMALLER;
}
}
@Override
public void run() {
//這里縮放的比例非常小,只是稍微比1大一點(diǎn)或者比1小一點(diǎn)的倍數(shù)
//但是當(dāng)每16ms都放大或者縮小一點(diǎn)點(diǎn)的時(shí)候,動(dòng)畫(huà)效果就出來(lái)了
mScaleMatrix.postScale(tempScale, tempScale, x, y);
//每次將矩陣作用到圖片之前,都檢查一下邊界
checkBorderAndCenterWhenScale();
//將矩陣作用到圖片上
setImageMatrix(mScaleMatrix);
//得到當(dāng)前圖片的縮放值
float currentScale = getScale();
//如果當(dāng)前想要放大,并且當(dāng)前縮放值小于目標(biāo)縮放值
//或者 當(dāng)前想要縮小,并且當(dāng)前縮放值大于目標(biāo)縮放值
if ((tempScale > 1.0f) && currentScale < mTargetScale
||(tempScale < 1.0f) && currentScale > mTargetScale){
//每隔16ms就調(diào)用一次run方法
postDelayed(this,16);
}else {
//current*scale=current*(mTargetScale/currentScale)=mTargetScale
//保證圖片最終的縮放值和目標(biāo)縮放值一致
float scale = mTargetScale / currentScale;
mScaleMatrix.postScale(scale, scale, x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(mScaleMatrix);
//自動(dòng)縮放結(jié)束,置為false
isAutoScale = false;
}
}
}
/**
* 這個(gè)是OnScaleGestureListener中的方法,在這個(gè)方法中我們可以對(duì)圖片進(jìn)行放大縮小
*/
@Override
public boolean onScale(ScaleGestureDetector detector) {
//當(dāng)我們兩個(gè)手指進(jìn)行分開(kāi)操作時(shí),說(shuō)明我們想要放大,這個(gè)scaleFactor是一個(gè)稍微大于1的數(shù)值
//當(dāng)我們兩個(gè)手指進(jìn)行閉合操作時(shí),說(shuō)明我們想要縮小,這個(gè)scaleFactor是一個(gè)稍微小于1的數(shù)值
float scaleFactor = detector.getScaleFactor();
//獲得我們圖片當(dāng)前的縮放值
float scale = getScale();
//如果當(dāng)前沒(méi)有圖片,則直接返回
if (getDrawable() == null){
return true;
}
//如果scaleFactor大于1,說(shuō)明想放大,當(dāng)前的縮放比例乘以scaleFactor之后小于
//最大的縮放比例時(shí),允許放大
//如果scaleFactor小于1,說(shuō)明想縮小,當(dāng)前的縮放比例乘以scaleFactor之后大于
//最小的縮放比例時(shí),允許縮小
if ((scaleFactor > 1.0f && scale * scaleFactor < mMaxScale)
|| scaleFactor < 1.0f && scale * scaleFactor > mInitScale){
//邊界控制,如果當(dāng)前縮放比例乘以scaleFactor之后大于了最大的縮放比例
if (scale * scaleFactor > mMaxScale + 0.01f){
//則將scaleFactor設(shè)置成mMaxScale/scale
//當(dāng)再進(jìn)行matrix.postScale時(shí)
//scale*scaleFactor=scale*(mMaxScale/scale)=mMaxScale
//最后圖片就會(huì)放大至mMaxScale縮放比例的大小
scaleFactor = mMaxScale / scale;
}
//邊界控制,如果當(dāng)前縮放比例乘以scaleFactor之后小于了最小的縮放比例
//我們不允許再縮小
if (scale * scaleFactor < mInitScale + 0.01f){
//計(jì)算方法同上
scaleFactor = mInitScale / scale;
}
//前兩個(gè)參數(shù)是縮放的比例,是一個(gè)稍微大于1或者稍微小于1的數(shù),形成一個(gè)隨著手指放大
//或者縮小的效果
//detector.getFocusX()和detector.getFocusY()得到的是多點(diǎn)觸控的中點(diǎn)
//這樣就能實(shí)現(xiàn)我們?cè)趫D片的某一處局部放大的效果
mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
//因?yàn)閳D片的縮放點(diǎn)不是圖片的中心點(diǎn)了,所以圖片會(huì)出現(xiàn)偏移的現(xiàn)象,所以進(jìn)行一次邊界的檢查和居中操作
checkBorderAndCenterWhenScale();
//將矩陣作用到圖片上
setImageMatrix(mScaleMatrix);
}
return true;
}
/**
* 一定要返回true
*/
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//當(dāng)雙擊操作時(shí),不允許移動(dòng)圖片,直接返回true
if (mGestureDetector.onTouchEvent(event)){
return true;
}
//將事件傳遞給ScaleGestureDetector
mScaleGestureDetector.onTouchEvent(event);
//用于存儲(chǔ)多點(diǎn)觸控產(chǎn)生的坐標(biāo)
float x = 0.0f;
float y = 0.0f;
//得到多點(diǎn)觸控的個(gè)數(shù)
int pointerCount = event.getPointerCount();
//將所有觸控點(diǎn)的坐標(biāo)累加起來(lái)
for(int i=0 ; i<pointerCount ; i++){
x += event.getX(i);
y += event.getY(i);
}
//取平均值,得到的就是多點(diǎn)觸控后產(chǎn)生的那個(gè)點(diǎn)的坐標(biāo)
x /= pointerCount;
y /= pointerCount;
//如果觸控點(diǎn)的數(shù)量變了,則置為不可滑動(dòng)
if (mLastPointerCount != pointerCount){
isCanDrag = false;
mLastX = x;
mLastY = y;
}
mLastPointerCount = pointerCount;
RectF rectF = getMatrixRectF();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
isCanDrag = false;
//當(dāng)圖片處于放大狀態(tài)時(shí),禁止ViewPager攔截事件,將事件傳遞給圖片,進(jìn)行拖動(dòng)
if (rectF.width() > getWidth() + 0.01f || rectF.height() > getHeight() + 0.01f){
if (getParent() instanceof ViewPager){
getParent().requestDisallowInterceptTouchEvent(true);
}
}
break;
case MotionEvent.ACTION_MOVE:
//當(dāng)圖片處于放大狀態(tài)時(shí),禁止ViewPager攔截事件,將事件傳遞給圖片,進(jìn)行拖動(dòng)
if (rectF.width() > getWidth() + 0.01f || rectF.height() > getHeight() + 0.01f){
if (getParent() instanceof ViewPager){
getParent().requestDisallowInterceptTouchEvent(true);
}
}
//得到水平和豎直方向的偏移量
float dx = x - mLastX;
float dy = y - mLastY;
//如果當(dāng)前是不可滑動(dòng)的狀態(tài),判斷一下是否是滑動(dòng)的操作
if (!isCanDrag){
isCanDrag = isMoveAction(dx,dy);
}
//如果可滑動(dòng)
if (isCanDrag){
if (getDrawable() != null){
isCheckLeftAndRight = true;
isCheckTopAndBottom = true;
//如果圖片寬度小于控件寬度
if (rectF.width() < getWidth()){
//左右不可滑動(dòng)
dx = 0;
//左右不可滑動(dòng),也就不用檢查左右的邊界了
isCheckLeftAndRight = false;
}
//如果圖片的高度小于控件的高度
if (rectF.height() < getHeight()){
//上下不可滑動(dòng)
dy = 0;
//上下不可滑動(dòng),也就不用檢查上下邊界了
isCheckTopAndBottom = false;
}
}
mScaleMatrix.postTranslate(dx,dy);
//當(dāng)平移時(shí),檢查上下左右邊界
checkBorderWhenTranslate();
setImageMatrix(mScaleMatrix);
}
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
//當(dāng)手指抬起時(shí),將mLastPointerCount置0,停止滑動(dòng)
mLastPointerCount = 0;
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
/**
* 判斷是否是移動(dòng)的操作
*/
private boolean isMoveAction(float dx , float dy){
//勾股定理,判斷斜邊是否大于可滑動(dòng)的一個(gè)臨界值
return Math.sqrt(dx*dx + dy*dy) > mTouchSlop;
}
}
實(shí)現(xiàn)圖片縮小后,松手回彈的效果
實(shí)現(xiàn)這個(gè)功能很簡(jiǎn)單,我們先添加一個(gè)mMinScale作為可縮小到的最小值,我們指定為初試比例的1/4
/**
* 最小縮放比例
*/
private float mMinScale;
//在onGlobalLayout中進(jìn)行初始化
@Override
public void onGlobalLayout() {
...
//最小縮放比例為初試比例的1/4倍
mMinScale = mInitScale / 4;
...
}
//在onScale中,修改如下代碼
@Override
public boolean onScale(ScaleGestureDetector detector) {
...
if ((scaleFactor > 1.0f && scale * scaleFactor < mMaxScale)
|| scaleFactor < 1.0f && scale * scaleFactor > mMinScale){
//邊界控制,如果當(dāng)前縮放比例乘以scaleFactor之后小于了最小的縮放比例
//我們不允許再縮小
if (scale * scaleFactor < mMinScale + 0.01f){
scaleFactor = mMinScale / scale;
}
...
}
這樣我們的圖片最小就可以縮放到初始化比例的1/4大小了,然后我們還需要添加一個(gè)松手后回彈至初試化大小的動(dòng)畫(huà)效果,然后我們需要在onTouch的ACTION_UP中添加如下代碼
@Override
public boolean onTouch(View v, MotionEvent event) {
...
case MotionEvent.ACTION_UP:
//當(dāng)手指抬起時(shí),將mLastPointerCount置0,停止滑動(dòng)
mLastPointerCount = 0;
//如果當(dāng)前圖片大小小于初始化大小
if (getScale() < mInitScale){
//自動(dòng)放大至初始化大小
post(new AutoScaleRunnable(mInitScale,getWidth()/2,getHeight()/2));
}
break;
...
}
現(xiàn)在我們看一下效果

實(shí)現(xiàn)圖片放大后,松手回彈效果
這個(gè)功能實(shí)現(xiàn)起來(lái)和上面那個(gè)功能基本一致,大家可以先試著自己寫(xiě)一下。
同理,我們需要先定義一個(gè)mMaxOverScale作為放大到最大值后,還能繼續(xù)放大到的值。
/**
* 最大溢出值
*/
private float mMaxOverScale;
//在onGlobalLayout中進(jìn)行初始化
@Override
public void onGlobalLayout() {
...
//最大溢出值為最大值的5倍,可以隨意調(diào)
mMaxOverScale = mMaxScale * 5;
...
}
//在onScale中,修改如下代碼
@Override
public boolean onScale(ScaleGestureDetector detector) {
...
if ((scaleFactor > 1.0f && scale * scaleFactor < mMaxOverScale)
|| scaleFactor < 1.0f && scale * scaleFactor > mMinScale){
if (scale * scaleFactor > mMaxOverScale + 0.01f){
scaleFactor = mMaxOverScale / scale;
}
...
}
這樣當(dāng)我們圖片放大至最大比例后還可以繼續(xù)放大,然后我們同樣需要在onTouch中的ACTION_UP中添加自動(dòng)縮小的功能
case MotionEvent.ACTION_UP:
//當(dāng)手指抬起時(shí),將mLastPointerCount置0,停止滑動(dòng)
mLastPointerCount = 0;
//如果當(dāng)前圖片大小小于初始化大小
if (getScale() < mInitScale){
//自動(dòng)放大至初始化大小
post(new AutoScaleRunnable(mInitScale,getWidth()/2,getHeight()/2));
}
//如果當(dāng)前圖片大小大于最大值
if (getScale() > mMaxScale){
//自動(dòng)縮小至最大值
post(new AutoScaleRunnable(mMaxScale,getWidth()/2,getHeight()/2));
}
break;
然后我們看一下效果

實(shí)現(xiàn)圖片的慣性滑動(dòng)
要實(shí)現(xiàn)圖片的慣性滑動(dòng),我們需要借助VelocityTracker來(lái)幫我們檢測(cè)當(dāng)我們手指離開(kāi)圖片時(shí)的一個(gè)速度,然后根據(jù)這個(gè)速度以及圖片的位置來(lái)調(diào)用Scroller的fling方法來(lái)計(jì)算慣性滑動(dòng)過(guò)程中的x和y的坐標(biāo)
@Override
public boolean onTouch(View v, MotionEvent event) {
...
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//初始化速度檢測(cè)器
mVelocityTracker = VelocityTracker.obtain();
if (mVelocityTracker != null){
//將當(dāng)前的事件添加到檢測(cè)器中
mVelocityTracker.addMovement(event);
}
//當(dāng)手指再次點(diǎn)擊到圖片時(shí),停止圖片的慣性滑動(dòng)
if (mFlingRunnable != null){
mFlingRunnable.cancelFling();
mFlingRunnable = null;
}
...
}
...
case MotionEvent.ACTION_MOVE:
...
//如果可滑動(dòng)
if (isCanDrag){
if (getDrawable() != null){
if (mVelocityTracker != null){
//將當(dāng)前事件添加到檢測(cè)器中
mVelocityTracker.addMovement(event);
}
...
}
...
case MotionEvent.ACTION_UP:
//當(dāng)手指抬起時(shí),將mLastPointerCount置0,停止滑動(dòng)
mLastPointerCount = 0;
//如果當(dāng)前圖片大小小于初始化大小
if (getScale() < mInitScale){
//自動(dòng)放大至初始化大小
post(new AutoScaleRunnable(mInitScale,getWidth()/2,getHeight()/2));
}
//如果當(dāng)前圖片大小大于最大值
if (getScale() > mMaxScale){
//自動(dòng)縮小至最大值
post(new AutoScaleRunnable(mMaxScale,getWidth()/2,getHeight()/2));
}
if (isCanDrag){//如果當(dāng)前可以滑動(dòng)
if (mVelocityTracker != null){
//將當(dāng)前事件添加到檢測(cè)器中
mVelocityTracker.addMovement(event);
//計(jì)算當(dāng)前的速度
mVelocityTracker.computeCurrentVelocity(1000);
//得到當(dāng)前x方向速度
final float vX = mVelocityTracker.getXVelocity();
//得到當(dāng)前y方向的速度
final float vY = mVelocityTracker.getYVelocity();
mFlingRunnable = new FlingRunnable(getContext());
//調(diào)用fling方法,傳入控件寬高和當(dāng)前x和y軸方向的速度
//這里得到的vX和vY和scroller需要的velocityX和velocityY的負(fù)號(hào)正好相反
//所以傳入一個(gè)負(fù)值
mFlingRunnable.fling(getWidth(),getHeight(),(int)-vX,(int)-vY);
//執(zhí)行run方法
post(mFlingRunnable);
}
}
break;
case MotionEvent.ACTION_CANCEL:
//釋放速度檢測(cè)器
if (mVelocityTracker != null){
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
/**
* 慣性滑動(dòng)
*/
private class FlingRunnable implements Runnable{
private Scroller mScroller;
private int mCurrentX , mCurrentY;
public FlingRunnable(Context context){
mScroller = new Scroller(context);
}
public void cancelFling(){
mScroller.forceFinished(true);
}
/**
* 這個(gè)方法主要是從onTouch中或得到當(dāng)前滑動(dòng)的水平和豎直方向的速度
* 調(diào)用scroller.fling方法,這個(gè)方法內(nèi)部能夠自動(dòng)計(jì)算慣性滑動(dòng)
* 的x和y的變化率,根據(jù)這個(gè)變化率我們就可以對(duì)圖片進(jìn)行平移了
*/
public void fling(int viewWidth , int viewHeight , int velocityX ,
int velocityY){
RectF rectF = getMatrixRectF();
if (rectF == null){
return;
}
//startX為當(dāng)前圖片左邊界的x坐標(biāo)
final int startX = Math.round(-rectF.left);
final int minX , maxX , minY , maxY;
//如果圖片寬度大于控件寬度
if (rectF.width() > viewWidth){
//這是一個(gè)滑動(dòng)范圍[minX,maxX],詳情見(jiàn)下圖
minX = 0;
maxX = Math.round(rectF.width() - viewWidth);
}else{
//如果圖片寬度小于控件寬度,則不允許滑動(dòng)
minX = maxX = startX;
}
//如果圖片高度大于控件高度,同理
final int startY = Math.round(-rectF.top);
if (rectF.height() > viewHeight){
minY = 0;
maxY = Math.round(rectF.height() - viewHeight);
}else{
minY = maxY = startY;
}
mCurrentX = startX;
mCurrentY = startY;
if (startX != maxX || startY != maxY){
//調(diào)用fling方法,然后我們可以通過(guò)調(diào)用getCurX和getCurY來(lái)獲得當(dāng)前的x和y坐標(biāo)
//這個(gè)坐標(biāo)的計(jì)算是模擬一個(gè)慣性滑動(dòng)來(lái)計(jì)算出來(lái)的,我們根據(jù)這個(gè)x和y的變化可以模擬
//出圖片的慣性滑動(dòng)
mScroller.fling(startX,startY,velocityX,velocityY,minX,maxX,minY,maxY);
}
}
關(guān)于startX,minX,maxX做一個(gè)解釋

我們從圖中可以看出,當(dāng)前圖片可滑動(dòng)的一個(gè)區(qū)間就是左邊多出來(lái)的那塊區(qū)間,所以minX和maxX代表的是區(qū)間的最小值和最大值,startX就是屏幕左邊界的坐標(biāo)值,我們可以想象成是startX在區(qū)間[minX,maxX]的移動(dòng)。Y軸方向同理。
現(xiàn)在我們看一下效果

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家學(xué)習(xí)Android軟件編程有所幫助。
- Android通過(guò)多點(diǎn)觸控的方式對(duì)圖片進(jìn)行縮放的實(shí)例代碼
- Android實(shí)現(xiàn)多點(diǎn)觸控,自由縮放圖片的實(shí)例代碼
- Android多點(diǎn)觸控實(shí)現(xiàn)圖片自由縮放
- Android多點(diǎn)觸控技術(shù)實(shí)戰(zhàn) 針對(duì)圖片自由縮放和移動(dòng)
- Android開(kāi)發(fā)實(shí)例之多點(diǎn)觸控程序
- 解析Android開(kāi)發(fā)中多點(diǎn)觸摸的實(shí)現(xiàn)方法
- android 多點(diǎn)觸摸圖片縮放的具體實(shí)現(xiàn)方法
- Android實(shí)現(xiàn)手勢(shì)滑動(dòng)多點(diǎn)觸摸放大縮小圖片效果
- Android實(shí)現(xiàn)手勢(shì)滑動(dòng)多點(diǎn)觸摸縮放平移圖片效果
- Android實(shí)現(xiàn)多點(diǎn)觸控功能
相關(guān)文章
Android開(kāi)發(fā)之開(kāi)關(guān)按鈕控件ToggleButton簡(jiǎn)單用法示例
這篇文章主要介紹了Android開(kāi)發(fā)之開(kāi)關(guān)按鈕控件ToggleButton簡(jiǎn)單用法,結(jié)合實(shí)例形式分析了Android開(kāi)關(guān)按鈕控件ToggleButton的相關(guān)xml布局與調(diào)用操作技巧,需要的朋友可以參考下2017-12-12
android app判斷是否有系統(tǒng)簽名步驟詳解
這篇文章主要為大家介紹了android app判斷是否有系統(tǒng)簽名步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
android圖像繪制(三)畫(huà)布刷屏問(wèn)題記錄
在canvas中繪制動(dòng)態(tài)圖的時(shí)候,如果使用了一個(gè)固定的背景圖片,只有一個(gè)小小的精靈在移動(dòng)!這樣的情況下卻不得不在沒(méi)幀中重新繪制背景圖片,使得效率降低,本文章只是記錄一下,并不是解決方法感興趣的朋友可以了解下2013-01-01
Android開(kāi)發(fā)中比較耗時(shí)的一些操作小結(jié)
這篇文章主要介紹了Android開(kāi)發(fā)中比較耗時(shí)的一些操作小結(jié),本文根據(jù)實(shí)際開(kāi)發(fā)經(jīng)驗(yàn)總結(jié)了6條比較耗時(shí)的編程操作,請(qǐng)大家注意下,需要的朋友可以參考下2015-06-06
Android實(shí)現(xiàn)點(diǎn)擊獲取驗(yàn)證碼倒計(jì)時(shí)效果
這篇文章主要介紹了Android實(shí)現(xiàn)點(diǎn)擊獲取驗(yàn)證碼倒計(jì)時(shí)效果,這種效果大家經(jīng)常遇到,想知道如何實(shí)現(xiàn)的,請(qǐng)閱讀本文2016-08-08
Android跳轉(zhuǎn)到系統(tǒng)聯(lián)系人及撥號(hào)或短信界面
現(xiàn)在開(kāi)發(fā)中的功能需要直接跳轉(zhuǎn)到撥號(hào)、聯(lián)系人、短信界面等等,查找了很多資料,自己整理了一下特此分享到腳本之家平臺(tái)供大家參考2016-12-12
Android ListView中headerview的動(dòng)態(tài)顯示和隱藏的實(shí)現(xiàn)方法
這篇文章主要介紹了Android ListView中headerview的動(dòng)態(tài)顯示和隱藏的實(shí)現(xiàn)方法的相關(guān)資料,這里提供兩種方法幫助實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下2017-08-08
Android學(xué)習(xí)之Intent中顯示意圖和隱式意圖的用法實(shí)例分析
這篇文章主要介紹了Android學(xué)習(xí)之Intent中顯示意圖和隱式意圖的用法,以實(shí)例形式分析了Intent通訊的相關(guān)技巧與注意事項(xiàng),具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10

