Android 在viewPager中雙指縮放圖片雙擊縮放圖片單指拖拽圖片的實(shí)現(xiàn)思路
我們就把這個(gè)問題叫做圖片查看器吧,它的主要功能有:
1、雙擊縮放圖片。
2、 雙指縮放圖片。
3、單指拖拽圖片。
為此這個(gè)圖片查看器需要考慮以下的技術(shù)點(diǎn):
一、雙擊縮放圖片:
1、如果圖片高度比屏幕的高度小得多,那么就將圖片放大到高度與屏幕高度相等,否則就放大一個(gè)特定的倍數(shù)。
2、如何判斷是否到達(dá)這個(gè)倍數(shù)來停止縮放。
3、判斷完且停止放大后,圖片可能已經(jīng)超出了這個(gè)倍數(shù)需要的大小,如何回歸到我們的目標(biāo)大小。
4、判斷完且停止縮小后,圖片寬度可能已經(jīng)小于屏幕寬度,在兩邊留下了空白,如何重置為原來的大小。
二、雙指縮放圖片:
1、雙指縮放,放大一個(gè)特定的倍數(shù)停止。
2、如何判斷是否到達(dá)這個(gè)倍數(shù)。
3、放大停止后,圖片可能已經(jīng)超出了這個(gè)倍數(shù)需要的大小,如何回歸到我們的目標(biāo)大小。
4、縮小停止后,圖片寬度可能已經(jīng)小于屏幕寬度,在兩邊留下了空白,如何重置為原來的大小。
三、單指拖拽:
1、當(dāng)圖片寬度小于或等于屏幕寬度的時(shí)候,禁止左右移動(dòng),當(dāng)圖片的高度小于屏幕高度的時(shí)候,禁止上下移動(dòng)。
2、移動(dòng)圖片時(shí),如果圖片的一邊已經(jīng)與屏幕之間有了空白,松手后恢復(fù),讓圖片的這一邊與屏幕邊界重合。
四、
如何判斷是雙擊,還是多指觸控,還是單指。
五、
如何解決與viewPager的滑動(dòng)沖突,當(dāng)圖片已經(jīng)滑動(dòng)到盡頭無法滑動(dòng)時(shí),此時(shí)viewPager應(yīng)該攔截事件。
我們逐一來解決:
public class MyImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,View.OnTouchListener {
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setScaleType(ScaleType.MATRIX);
setOnTouchListener(this);
/**
* 雙擊實(shí)現(xiàn)圖片放大縮小
*/
mGestureDetector = new GestureDetector(context,
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
changeViewSize(e);
return true;
}
});
}
在這里縮放圖片是用matrix,因此首先要設(shè)置scaleType為matrix。
用手勢判斷雙擊行為。不要忘了在onTouch里面加上
if (mGestureDetector.onTouchEvent(event)) return true;
判斷單指與多指觸控,則在onTouch里面判斷,要用 event.getAction() & MotionEvent.ACTION_MASK來判斷。
//多指觸控模式,單指,雙指
private int mode;
private final static int SINGLE_TOUCH = 1; //單指
private final static int DOUBLE_TOUCH = 2; //雙指
@Override
public boolean onTouch(View view, MotionEvent event) {
rectF = getMatrixRectF();
if (mGestureDetector.onTouchEvent(event))
return true;
switch (event.getAction() & event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mode = SINGLE_TOUCH;
break;
case MotionEvent.ACTION_MOVE:
if (mode >= DOUBLE_TOUCH) //雙指縮放
{
}
if (mode == SINGLE_TOUCH) //單指拖拽
{
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode += 1;break;
case MotionEvent.ACTION_POINTER_UP:
mode -= 1;
break;
case MotionEvent.ACTION_UP:
mode = 0;
break;
//在ACTION_MOVE中,事件被攔截了之后,有時(shí)候ACTION_UP無法觸發(fā),所以加上了ACTION_CANCEL
case MotionEvent.ACTION_CANCEL:
mode = 0;
break;
default:
break;
}
return true;
}
有如下事件使我們要用到的:
MotionEvent.ACTION_DOWN:在第一個(gè)點(diǎn)被按下時(shí)觸發(fā)
MotionEvent.ACTION_UP:當(dāng)屏幕上唯一的點(diǎn)被放開時(shí)觸發(fā)
MotionEvent.ACTION_POINTER_DOWN:當(dāng)屏幕上已經(jīng)有一個(gè)點(diǎn)被按住,此時(shí)再按下其他點(diǎn)時(shí)觸發(fā)。
MotionEvent.ACTION_POINTER_UP:當(dāng)屏幕上有多個(gè)點(diǎn)被按住,松開其中一個(gè)點(diǎn)時(shí)觸發(fā)(即非最后一個(gè)點(diǎn)被放開時(shí))。
MotionEvent.ACTION_MOVE:當(dāng)有點(diǎn)在屏幕上移動(dòng)時(shí)觸發(fā)。值得注意的是,由于它的靈敏度很高,而我們的手指又不可能完全靜止(即使我們感覺不到移動(dòng),但其實(shí)我們的手指也在不停地抖動(dòng)),所以實(shí)際的情況是,基本上只要有點(diǎn)在屏幕上,此事件就會(huì)一直不停地被觸發(fā)。
在ACTION_MOVE中通過mode的大小來判斷是單指還是雙指。
不過有一個(gè)令人傷心的事情,Android自己有一個(gè)bug。經(jīng)過測試發(fā)現(xiàn)雙指交換觸碰圖片的時(shí)候,程序會(huì)閃退,出現(xiàn)異常:pointIndex out of range。這是Android自己的bug。個(gè)人覺得最好得解決方法是自定義一個(gè)viewPager,然后在里面重寫:onTouchEvent,onInterceptTouchEvent,然后捕獲異常。
@Override
public boolean onTouchEvent(MotionEvent ev) {
try {
return super.onTouchEvent(ev);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
try {
return super.onInterceptTouchEvent(ev);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
return false;
}
這樣程序就不會(huì)閃退了。
我們來看看雙擊放大的的代碼:
/**
* 雙擊縮放圖片
*/
private void changeViewSize(MotionEvent e) {
//獲取雙擊的坐標(biāo)
final float x = e.getX();
final float y = e.getY();
//如果此時(shí)還在縮放那就直接返回
if (animator != null && animator.isRunning())
return;
//判斷是處于放大還是縮小的狀態(tài)
if (!isZoomChanged()) {
animator = ValueAnimator.ofFloat(1.0f, 2.0f);
} else {
animator = ValueAnimator.ofFloat(1.0f, 0.0f);
}
animator.setTarget(this);
animator.setDuration(500);
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Float value = (Float) animator.getAnimatedValue();
matrix.postScale(value, value, x, y);
checkBorderAndCenterWhenScale(); //在縮放后讓圖片居中
setImageMatrix(matrix);
/**
* 控制縮小的范圍
* 如果已經(jīng)小于初始大小,那么恢復(fù)到初始大小,然后停止
*/
if (checkRestScale()) {
matrix.set(oldMatrix); //oldMatrix為最原始的matrix
setImageMatrix(matrix);
return;
}
/**
* 控制放大的范圍
* 如果已經(jīng)大于目標(biāo)的放大倍數(shù),那么直接置為目標(biāo)的放大倍數(shù)
* 然后停止
*/
if (getMatrixValueX() >= mDoubleClickScale)
{
matrix.postScale(mDoubleClickScale/getMatrixValueX(), mDoubleClickScale/getMatrixValueX(), x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
return;
}
}
});
}
判斷處于放大還是縮小狀態(tài)的代碼:(不是初始值就說明是處于放大狀態(tài))
/**
* 判斷縮放級(jí)別是否是改變過
*
* @return true表示非初始值, false表示初始值
*/
private boolean isZoomChanged() {
float[] values = new float[9];
getImageMatrix().getValues(values);
//獲取當(dāng)前X軸縮放級(jí)別
float scale = values[Matrix.MSCALE_X];
//獲取初始時(shí)候的X軸縮放級(jí)別,兩者做比較
oldMatrix.getValues(values);
return scale != values[Matrix.MSCALE_X];
}
getMatrixValue()的代碼如下,是為了取得當(dāng)前的放大倍數(shù),相對于一開始的圖片來說
private float getMatrixValueX()
{
// TODO Auto-generated method stub
float[] values = new float[9];
getImageMatrix().getValues(values);
//獲取當(dāng)前X軸縮放級(jí)別
float scale = values[Matrix.MSCALE_X];
//獲取原始Matrix的X軸縮放級(jí)別
oldMatrix.getValues(values);
//返回放大的倍數(shù)
return scale / values[Matrix.MSCALE_X];
}
checkRestScale()的代碼如下,主要是為了判斷當(dāng)前的縮放級(jí)別是否小于最初始的縮放級(jí)別。
/**
* 判斷是否需要重置
*
* @return 當(dāng)前縮放級(jí)別小于原始縮放級(jí)別時(shí),重置
*/
private boolean checkRestScale() {
// TODO Auto-generated method stub
float[] values = new float[9];
getImageMatrix().getValues(values);
//獲取當(dāng)前X軸縮放級(jí)別
float scale = values[Matrix.MSCALE_X];
//獲取原始的X軸縮放級(jí)別,兩者做比較
oldMatrix.getValues(values);
return scale < values[Matrix.MSCALE_X];
}
checkBorderAndCenterWhenScale()的代碼如下,否則圖片縮放后位置會(huì)發(fā)生變化。
/**
* 在縮放時(shí),進(jìn)行圖片顯示范圍的控制
*/
private void checkBorderAndCenterWhenScale()
{
RectF rect = getMatrixRectF();
float deltaX = 0;
float deltaY = 0;
int width = getWidth();
int height = getHeight();
// 如果寬或高大于屏幕,則控制范圍
if (rect.width() >= width)
{
if (rect.left > 0)
{
deltaX = -rect.left;
}
if (rect.right < width)
{
deltaX = width - rect.right;
}
}
if (rect.height() >= height)
{
if (rect.top > 0)
{
deltaY = -rect.top;
}
if (rect.bottom < height)
{
deltaY = height - rect.bottom;
}
}
// 如果寬或高小于屏幕,則讓其居中
if (rect.width() < width)
{
deltaX = width * 0.5f - rect.right + 0.5f * rect.width();
}
if (rect.height() < height)
{
deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height();
}
matrix.postTranslate(deltaX, deltaY);
setImageMatrix(matrix);
}
接下看看雙指縮放和單指拖拽:
@Override
public boolean onTouch(View view, MotionEvent event) {
rectF = getMatrixRectF(); //獲取圖片邊界范圍
if (mGestureDetector.onTouchEvent(event))
return true;
switch (event.getAction() & event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
//如果放大后圖片的邊界超出了屏幕,那么就攔截事件,不讓viewPager處理
if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
mode = SINGLE_TOUCH;
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (mode >= DOUBLE_TOUCH) //雙指縮放
{
getParent().requestDisallowInterceptTouchEvent(true);
newDist = calculateDist(event); //計(jì)算距離
Point point = getMiPoint(event); //獲取兩手指間的中點(diǎn)坐標(biāo)
if (newDist > oldDist + 1) //放大(加一是為了防止抖動(dòng))
{
changeViewSize(oldDist, newDist, point); //根據(jù)距離實(shí)現(xiàn)放大縮小
oldDist = newDist;
}
if (oldDist > newDist + 1) //縮小
{
changeViewSize(oldDist, newDist, point);
oldDist = newDist;
}
}
if (mode == SINGLE_TOUCH) //單指拖拽
{
float dx = event.getRawX() - x;
float dy = event.getRawY() - y;
//如果移動(dòng)過程中圖片的邊界超出了屏幕,那么就攔截事件,不讓viewPager處理
if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
//如果向右移動(dòng)圖片到了盡頭,那么就不要攔截事件,讓viewPager處理
if (rectF.left >= 0 && dx > 0)
getParent().requestDisallowInterceptTouchEvent(false);
//如果向左移動(dòng)到了盡頭,那么就不要攔截事件,讓viewPager處理
if (rectF.right <= getWidth() && dx < 0)
getParent().requestDisallowInterceptTouchEvent(false);
if (getDrawable() != null) {
//如果圖片寬度或高度沒有超出屏幕,那么就禁止左右或上下滑動(dòng)
if (rectF.width() <= getWidth())
dx = 0;
if (rectF.height() < getHeight())
dy = 0;
//如果圖片向下移動(dòng)到了盡頭,不讓它繼續(xù)移動(dòng)
if (rectF.top >= 0 && dy > 0)
dy = 0;
//如果圖片向上移動(dòng)到了盡頭,不讓它繼續(xù)移動(dòng)
if (rectF.bottom <= getHeight() && dy < 0)
dy = 0;
//當(dāng)移動(dòng)距離大于1的時(shí)候再移動(dòng),因?yàn)锳CTION_MOVE比較靈敏,
// 手指即使只是放在上面,依然能夠檢測到手指的抖動(dòng),然后讓圖片移動(dòng)。
if (Math.abs(dx) > 1 || Math.abs(dy) > 1)
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
}
}
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode += 1;
oldDist = calculateDist(event); break;
case MotionEvent.ACTION_POINTER_UP:
mode -= 1;
break;
case MotionEvent.ACTION_UP:
backToPosition();
mode = 0;
break;
//在ACTION_MOVE中,事件被攔截了之后,有時(shí)候ACTION_UP無法觸發(fā),所以加上了ACTION_CANCEL
case MotionEvent.ACTION_CANCEL:
backToPosition();
mode = 0;
break;
default:
break;
}
return true;
}
首先先來看一個(gè)方法,根據(jù)圖片的matrix獲得圖片的邊界范圍,這個(gè)范圍映射在rect上。(這個(gè)范圍檢測是用在單指拖拽上的)
/**
* 根據(jù)當(dāng)前圖片的Matrix獲得圖片的范圍
*
* @return
*/
private RectF getMatrixRectF()
{
RectF rect = new RectF();
Drawable d = getDrawable();
if (null != d)
{
rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(rect);
}
Log.e("aaaa",""+rect.bottom+" "+rect.left+" "+rect.right+" "+rect.top);
return rect;
}
rect.bottom:圖片下邊界的縱坐標(biāo)
rect.left:圖片左邊界的橫坐標(biāo)
rect.right:圖片右邊界的橫坐標(biāo)
rect.top:圖片上邊界的縱坐標(biāo)
rectF.width():圖片寬度
rectF.height():圖片高度
需要注意的是Matrix對圖片的操作都是操作ImageView里面的bitmap,ImageView是沒有變化的,上面所說的屏幕邊界其實(shí)ImageView的邊界,getWidth(),getHeight()是ImageView的寬和高。
方法 backToPosition()主要是實(shí)現(xiàn)單指拖拽的技術(shù)點(diǎn)2,當(dāng)手指快速劃過去的時(shí)候,在檢測到無法繼續(xù)滑動(dòng)前圖片邊界與屏幕邊界已經(jīng)出現(xiàn)了距離,所以松開手指的時(shí)候要復(fù)位,讓圖片邊界與屏幕邊界重合。
/**
* 若是在移動(dòng)后圖片的邊界脫離屏幕邊界,那么就讓圖片邊界與屏幕邊界重合
* 若手指快速移動(dòng),停止后會(huì)出現(xiàn)圖片距離屏幕有一段空白距離,然后經(jīng)過判斷不能再移動(dòng),
* 但是在進(jìn)行下一次判斷是否可以繼續(xù)移動(dòng)之前就已經(jīng)出現(xiàn)了。
* 所以需要復(fù)位
*/
private void backToPosition() {
if (rectF.left >= 0) { //圖片左邊界與屏幕出現(xiàn)距離
matrix.postTranslate(-rectF.left, 0);
setImageMatrix(matrix);
}
if (rectF.right <= getWidth()) { //圖片右邊界與屏幕出現(xiàn)距離
matrix.postTranslate(getWidth() - rectF.right, 0);
setImageMatrix(matrix);
}
if (rectF.top >= 0) { //圖片上邊界與屏幕出現(xiàn)距離
matrix.postTranslate(0, -rectF.top);
setImageMatrix(matrix);
}
if (rectF.bottom <= getHeight()) { //圖片下邊界與屏幕出現(xiàn)距離
matrix.postTranslate(0, getHeight() - rectF.bottom);
setImageMatrix(matrix);
}
}
獲取兩手指間的中點(diǎn)坐標(biāo)
/**
* 獲取雙指縮放時(shí)候的縮放中點(diǎn)
*
* @return
*/
private Point getMiPoint(MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
mPoint.set((int) x / 2, (int) y / 2);
return mPoint;
}
計(jì)算兩指觸摸點(diǎn)的距離
/**
* 計(jì)算兩指觸摸點(diǎn)之間的距離
*/
private float calculateDist(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
雙指縮放圖片
/**
* 雙指縮放圖片
*/
private void changeViewSize(float oldDist, float newDist, Point mPoint) {
float scale = newDist / oldDist; //縮放比例
matrix.postScale(scale, scale, mPoint.x, mPoint.y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
//防止縮小的時(shí)候小于初始的圖片大小,需要重置
reSetMatrix();
//如果縮放已經(jīng)大于目標(biāo)倍數(shù),停止,因?yàn)橛锌赡芤呀?jīng)超出,那么就直接縮放到目標(biāo)大小
if (getMatrixValueX() >= MAX_SCALE)
{
matrix.postScale(MAX_SCALE/getMatrixValueX(), MAX_SCALE/getMatrixValueX(), x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
return;
}
}
reSetMatrix()的代碼如下:
/**
* 重置Matrix
*/
private void reSetMatrix() {
if (checkRestScale()) {
matrix.set(oldMatrix);
setImageMatrix(matrix);
return;
}
}
checkRestScale()的代碼在上面已經(jīng)給出了。oldMatrix為最初始的Matrix。
到這里還沒有結(jié)束,設(shè)置Imageview的ScaleType為Matrix,那么圖片不會(huì)主動(dòng)縮放到適應(yīng)屏幕,也不會(huì)處于屏幕中間,因此我們的自定義ImageView需要繼承ViewTreeObserver.OnGlobalLayoutListener
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
@Override
public void onGlobalLayout() {
if (once)
{
Drawable d = getDrawable();
if (d == null)
return;
Log.e("TAG", d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight());
int width = getWidth();
int height = getHeight();
// 拿到圖片的寬和高
int dw = d.getIntrinsicWidth();
int dh = d.getIntrinsicHeight();
float scale = 1.0f;
// 如果圖片的寬或者高大于屏幕,則縮放至屏幕的寬或者高
if (dw > width && dh <= height)
{
scale = width * 1.0f / dw;
}
if (dh > height && dw <= width)
{
scale = height * 1.0f / dh;
}
// 如果寬和高都大于屏幕,則讓其按按比例適應(yīng)屏幕大小
if (dw > width && dh > height)
{
scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
}
initScale = scale;
Log.e("TAG", "initScale = " + initScale);
matrix.postTranslate((width - dw) / 2, (height - dh) / 2);
matrix.postScale(scale, scale, getWidth() / 2,
getHeight() / 2);
// 圖片移動(dòng)至屏幕中心
setImageMatrix(matrix);
oldMatrix.set(getImageMatrix());
once = false;
RectF rectF=getMatrixRectF();
setDoubleClickScale(rectF);
}
}
// 拿到圖片的寬和高
int dw = d.getIntrinsicWidth();
int dh = d.getIntrinsicHeight();
拿到的圖片寬和高是bitmap的真實(shí)高度。
初始的oldMatrix就是在這里設(shè)置的,然后作為初始模板,代表著圖片沒被動(dòng)手改變的Matrix。至于方法 setDoubleClickScale(rectF);只是設(shè)置雙擊放大的倍數(shù)而已,如果圖片高度比屏幕的高度小得多,那么就將圖片放大到高度與屏幕高度相等,否則就放大一個(gè)特定的倍數(shù)。必須在這里設(shè)置,因?yàn)樵谶@里取到的rectF才能反映原始圖片的邊界,因?yàn)檫@時(shí)候還沒有動(dòng)手改變圖片。
/**
* 設(shè)置雙擊放大的倍數(shù)
*/
private void setDoubleClickScale(RectF rectF)
{
if(rectF.height()<getHeight()-100)
{
mDoubleClickScale=getHeight()/rectF.height();
}
else
mDoubleClickScale=2f;
}
到這里大概結(jié)束了,下面就貼出完整的代碼:
package com.example.tangzh.myimageview;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
/**
* Created by TangZH on 2017/5/3.
*/
public class MyImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,View.OnTouchListener {
private final static int SINGLE_TOUCH = 1; //單指
private final static int DOUBLE_TOUCH = 2; //雙指
//多指觸控模式,單指,雙指
private int mode;
//兩指觸碰點(diǎn)之間的距離
private float oldDist;
private float newDist;
/**
* 最大縮放級(jí)別
*/
private static final float MAX_SCALE = 5f;
/**
* 雙擊時(shí)的縮放級(jí)別
*/
private float mDoubleClickScale = 2;
/**
* 初始化時(shí)的縮放比例,如果圖片寬或高大于屏幕,此值將小于0
*/
private float initScale = 1.0f;
private boolean once = true;
private RectF rectF;
/**
* 用于雙擊檢測
*/
private GestureDetector mGestureDetector;
private int x = 0;
private int y = 0;
private Point mPoint = new Point();
private final Matrix matrix = new Matrix();
private Matrix oldMatrix = new Matrix();
private ValueAnimator animator;
public MyImageView(Context context) {
this(context, null);
}
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setScaleType(ScaleType.MATRIX);
setOnTouchListener(this);
/**
* 雙擊實(shí)現(xiàn)圖片放大縮小
*/
mGestureDetector = new GestureDetector(context,
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
changeViewSize(e);
return true;
}
});
}
@Override
public boolean onTouch(View view, MotionEvent event) {
rectF = getMatrixRectF(); //獲取圖片邊界范圍
if (mGestureDetector.onTouchEvent(event))
return true;
switch (event.getAction() & event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
//如果放大后圖片的邊界超出了屏幕,那么就攔截事件,不讓viewPager處理
if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
mode = SINGLE_TOUCH;
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (mode >= DOUBLE_TOUCH) //雙指縮放
{
getParent().requestDisallowInterceptTouchEvent(true);
newDist = calculateDist(event); //計(jì)算距離
Point point = getMiPoint(event); //獲取兩手指間的中點(diǎn)坐標(biāo)
if (newDist > oldDist + 1) //放大(加一是為了防止抖動(dòng))
{
changeViewSize(oldDist, newDist, point); //根據(jù)距離實(shí)現(xiàn)放大縮小
oldDist = newDist;
}
if (oldDist > newDist + 1) //縮小
{
changeViewSize(oldDist, newDist, point);
oldDist = newDist;
}
}
if (mode == SINGLE_TOUCH) //單指拖拽
{
float dx = event.getRawX() - x;
float dy = event.getRawY() - y;
//如果移動(dòng)過程中圖片的邊界超出了屏幕,那么就攔截事件,不讓viewPager處理
if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
//如果向右移動(dòng)圖片到了盡頭,那么就不要攔截事件,讓viewPager處理
if (rectF.left >= 0 && dx > 0)
getParent().requestDisallowInterceptTouchEvent(false);
//如果向左移動(dòng)到了盡頭,那么就不要攔截事件,讓viewPager處理
if (rectF.right <= getWidth() && dx < 0)
getParent().requestDisallowInterceptTouchEvent(false);
if (getDrawable() != null) {
//如果圖片寬度或高度沒有超出屏幕,那么就禁止左右或上下滑動(dòng)
if (rectF.width() <= getWidth())
dx = 0;
if (rectF.height() < getHeight())
dy = 0;
//如果圖片向下移動(dòng)到了盡頭,不讓它繼續(xù)移動(dòng)
if (rectF.top >= 0 && dy > 0)
dy = 0;
//如果圖片向上移動(dòng)到了盡頭,不讓它繼續(xù)移動(dòng)
if (rectF.bottom <= getHeight() && dy < 0)
dy = 0;
//當(dāng)移動(dòng)距離大于1的時(shí)候再移動(dòng),因?yàn)锳CTION_MOVE比較靈敏,
// 手指即使只是放在上面,依然能夠檢測到手指的抖動(dòng),然后讓圖片移動(dòng)。
if (Math.abs(dx) > 1 || Math.abs(dy) > 1)
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
}
}
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode += 1;
oldDist = calculateDist(event);
Log.e("q", "" + "a");
Log.e(":::", "" + event.getPointerCount() + " " + event.getActionIndex() + " " + event.findPointerIndex(0));
break;
case MotionEvent.ACTION_POINTER_UP:
mode -= 1;
break;
case MotionEvent.ACTION_UP:
backToPosition();
mode = 0;
break;
//在ACTION_MOVE中,事件被攔截了之后,有時(shí)候ACTION_UP無法觸發(fā),所以加上了ACTION_CANCEL
case MotionEvent.ACTION_CANCEL:
backToPosition();
mode = 0;
break;
default:
break;
}
return true;
}
/**
* 計(jì)算兩指觸摸點(diǎn)之間的距離
*/
private float calculateDist(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
/**
* 若是在移動(dòng)后圖片的邊界脫離屏幕邊界,那么就讓圖片邊界與屏幕邊界重合
* 若手指快速移動(dòng),停止后會(huì)出現(xiàn)圖片距離屏幕有一段空白距離,然后經(jīng)過判斷不能再移動(dòng),
* 但是在進(jìn)行下一次判斷是否可以繼續(xù)移動(dòng)之前就已經(jīng)出現(xiàn)了。
* 所以需要復(fù)位
*/
private void backToPosition() {
if (rectF.left >= 0) { //圖片左邊界與屏幕出現(xiàn)距離
matrix.postTranslate(-rectF.left, 0);
setImageMatrix(matrix);
}
if (rectF.right <= getWidth()) { //圖片右邊界與屏幕出現(xiàn)距離
matrix.postTranslate(getWidth() - rectF.right, 0);
setImageMatrix(matrix);
}
if (rectF.top >= 0) { //圖片上邊界與屏幕出現(xiàn)距離
matrix.postTranslate(0, -rectF.top);
setImageMatrix(matrix);
}
if (rectF.bottom <= getHeight()) { //圖片下邊界與屏幕出現(xiàn)距離
matrix.postTranslate(0, getHeight() - rectF.bottom);
setImageMatrix(matrix);
}
}
/**
* 獲取雙指縮放時(shí)候的縮放中點(diǎn)
*
* @return
*/
private Point getMiPoint(MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
mPoint.set((int) x / 2, (int) y / 2);
return mPoint;
}
/**
* 雙指縮放圖片
*/
private void changeViewSize(float oldDist, float newDist, Point mPoint) {
float scale = newDist / oldDist; //縮放比例
matrix.postScale(scale, scale, mPoint.x, mPoint.y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
//防止縮小的時(shí)候小于初始的圖片大小,需要重置
reSetMatrix();
//如果縮放已經(jīng)大于目標(biāo)倍數(shù),停止,因?yàn)橛锌赡芤呀?jīng)超出,那么就直接縮放到目標(biāo)大小
if (getMatrixValueX() >= MAX_SCALE)
{
matrix.postScale(MAX_SCALE/getMatrixValueX(), MAX_SCALE/getMatrixValueX(), x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
return;
}
}
/**
* 雙擊縮放圖片
*/
private void changeViewSize(MotionEvent e) {
//獲取雙擊的坐標(biāo)
final float x = e.getX();
final float y = e.getY();
//如果此時(shí)還在縮放那就直接返回
if (animator != null && animator.isRunning())
return;
//判斷是處于放大還是縮小的狀態(tài)
if (!isZoomChanged()) {
animator = ValueAnimator.ofFloat(1.0f, 2.0f);
} else {
animator = ValueAnimator.ofFloat(1.0f, 0.0f);
}
animator.setTarget(this);
animator.setDuration(500);
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Float value = (Float) animator.getAnimatedValue();
matrix.postScale(value, value, x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
/**
* 控制縮小的范圍
* 如果已經(jīng)小于初始大小,那么恢復(fù)到初始大小,然后停止
*/
if (checkRestScale()) {
matrix.set(oldMatrix);
setImageMatrix(matrix);
return;
}
/**
* 控制放大的范圍
* 如果已經(jīng)大于目標(biāo)的放大倍數(shù),那么直接置為目標(biāo)的放大倍數(shù)
* 然后停止
*/
if (getMatrixValueX() >= mDoubleClickScale)
{
matrix.postScale(mDoubleClickScale/getMatrixValueX(), mDoubleClickScale/getMatrixValueX(), x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
return;
}
}
});
}
/**
* 判斷縮放級(jí)別是否是改變過
*
* @return true表示非初始值, false表示初始值
*/
private boolean isZoomChanged() {
float[] values = new float[9];
getImageMatrix().getValues(values);
//獲取當(dāng)前X軸縮放級(jí)別
float scale = values[Matrix.MSCALE_X];
//獲取模板的X軸縮放級(jí)別,兩者做比較
oldMatrix.getValues(values);
return scale != values[Matrix.MSCALE_X];
}
/**
* 重置Matrix
*/
private void reSetMatrix() {
if (checkRestScale()) {
matrix.set(oldMatrix);
setImageMatrix(matrix);
return;
}
}
/**
* 設(shè)置雙擊放大的倍數(shù)
*/
private void setDoubleClickScale(RectF rectF)
{
if(rectF.height()<getHeight()-100)
{
mDoubleClickScale=getHeight()/rectF.height();
}
else
mDoubleClickScale=2f;
}
/**
* 判斷是否需要重置
*
* @return 當(dāng)前縮放級(jí)別小于模板縮放級(jí)別時(shí),重置
*/
private boolean checkRestScale() {
// TODO Auto-generated method stub
float[] values = new float[9];
getImageMatrix().getValues(values);
//獲取當(dāng)前X軸縮放級(jí)別
float scale = values[Matrix.MSCALE_X];
//獲取模板的X軸縮放級(jí)別,兩者做比較
oldMatrix.getValues(values);
return scale < values[Matrix.MSCALE_X];
}
private float getMatrixValueX()
{
// TODO Auto-generated method stub
float[] values = new float[9];
getImageMatrix().getValues(values);
//獲取當(dāng)前X軸縮放級(jí)別
float scale = values[Matrix.MSCALE_X];
//獲取模板的X軸縮放級(jí)別,兩者做比較
oldMatrix.getValues(values);
return scale / values[Matrix.MSCALE_X];
}
/**
* 在縮放時(shí),進(jìn)行圖片顯示范圍的控制
*/
private void checkBorderAndCenterWhenScale()
{
RectF rect = getMatrixRectF();
float deltaX = 0;
float deltaY = 0;
int width = getWidth();
int height = getHeight();
// 如果寬或高大于屏幕,則控制范圍
if (rect.width() >= width)
{
if (rect.left > 0)
{
deltaX = -rect.left;
}
if (rect.right < width)
{
deltaX = width - rect.right;
}
}
if (rect.height() >= height)
{
if (rect.top > 0)
{
deltaY = -rect.top;
}
if (rect.bottom < height)
{
deltaY = height - rect.bottom;
}
}
// 如果寬或高小于屏幕,則讓其居中
if (rect.width() < width)
{
deltaX = width * 0.5f - rect.right + 0.5f * rect.width();
}
if (rect.height() < height)
{
deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height();
}
Log.e("TAG", "deltaX = " + deltaX + " , deltaY = " + deltaY);
matrix.postTranslate(deltaX, deltaY);
setImageMatrix(matrix);
}
/**
* 根據(jù)當(dāng)前圖片的Matrix獲得圖片的范圍
*
* @return
*/
private RectF getMatrixRectF()
{
RectF rect = new RectF();
Drawable d = getDrawable();
if (null != d)
{
rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(rect); //如果沒有這個(gè),那么下面Log的輸出將會(huì)與上一句的一樣。
}
Log.e("aaaa",""+rect.bottom+" "+rect.left+" "+rect.right+" "+rect.top);
return rect;
}
@Override
public void onGlobalLayout() {
if (once)
{
Drawable d = getDrawable();
if (d == null)
return;
Log.e("TAG", d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight());
int width = getWidth();
int height = getHeight();
// 拿到圖片的寬和高
int dw = d.getIntrinsicWidth();
int dh = d.getIntrinsicHeight();
float scale = 1.0f;
// 如果圖片的寬或者高大于屏幕,則縮放至屏幕的寬或者高
if (dw > width && dh <= height)
{
scale = width * 1.0f / dw;
}
if (dh > height && dw <= width)
{
scale = height * 1.0f / dh;
}
// 如果寬和高都大于屏幕,則讓其按按比例適應(yīng)屏幕大小
if (dw > width && dh > height)
{
scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
}
initScale = scale;
Log.e("TAG", "initScale = " + initScale);
matrix.postTranslate((width - dw) / 2, (height - dh) / 2);
matrix.postScale(scale, scale, getWidth() / 2,
getHeight() / 2);
// 圖片移動(dòng)至屏幕中心
setImageMatrix(matrix);
oldMatrix.set(getImageMatrix());
once = false;
RectF rectF=getMatrixRectF();
setDoubleClickScale(rectF);
}
}
}
唉,雖然已經(jīng)寫完了,但是還有一個(gè)問題沒有解決,就是移動(dòng)圖片到盡頭,這時(shí)候不要放手,往反方向移動(dòng),就會(huì)出現(xiàn)一個(gè)問題,圖片反方向的部分被遮擋,無法看到,然后移動(dòng)的時(shí)候是直接切換圖片,而不再繼續(xù)移動(dòng)圖片,這個(gè)問題的原因是:當(dāng)你移動(dòng)圖片到盡頭時(shí),就把事件交給viewpager來處理了,即使再往反方向移動(dòng)圖片,viewPager也一樣繼續(xù)攔截了事件。目前沒解決。
以上所述是小編給大家介紹的在viewPager中雙指縮放圖片雙擊縮放圖片單指拖拽圖片的實(shí)現(xiàn)思路,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
- Android 單雙擊實(shí)現(xiàn)的方法步驟
- Android實(shí)現(xiàn)雙擊返回鍵退出應(yīng)用實(shí)現(xiàn)方法詳解
- Android雙擊事件攔截方法
- Android使用PhotoView實(shí)現(xiàn)圖片雙擊放大單擊退出效果
- Android 雙擊Back鍵退出應(yīng)用的實(shí)現(xiàn)方法
- Android實(shí)現(xiàn)雙擊TitleBar回頂部的功能示例代碼
- Android 雙擊返回鍵退出程序的方法總結(jié)
- Android中雙擊返回鍵退出應(yīng)用實(shí)例代碼
- Android 高仿微信朋友圈動(dòng)態(tài)支持雙擊手勢放大并滑動(dòng)查看圖片效果
- Android 自定義View實(shí)現(xiàn)單擊和雙擊事件的方法
- Android 屏幕雙擊事件的捕獲簡單示例
- Android 實(shí)現(xiàn)雙擊退出的功能
- Android App中實(shí)現(xiàn)可以雙擊放大和縮小圖片功能的實(shí)例
- Android實(shí)現(xiàn)ImageView圖片雙擊放大及縮小
- Android雙擊退出的實(shí)現(xiàn)方法
- Android雙擊返回鍵退出程序的實(shí)現(xiàn)方法
- 使用python編寫android截屏腳本雙擊運(yùn)行即可
- Android開發(fā)實(shí)現(xiàn)控件雙擊事件的監(jiān)聽接口封裝類
相關(guān)文章
Android中HttpURLConnection類使用介紹
早些時(shí)候其實(shí)我們都習(xí)慣性使用HttpClient,但是后來Android6.0之后不再支持HttpClient,需要添加Apache的jar才行,所以,就有很多開發(fā)者放棄使用HttpClient了,HttpURLConnection畢竟是標(biāo)準(zhǔn)Java接口(java.net) ,適配性還是很強(qiáng)的2022-12-12
搭建簡易藍(lán)牙定位系統(tǒng)的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄罱ê喴姿{(lán)牙定位系統(tǒng)的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-03-03
Android開發(fā)實(shí)現(xiàn)帶有反彈效果仿IOS反彈scrollview教程詳解
本文給大家分享android開發(fā)實(shí)現(xiàn)帶有反彈效果,模仿ios反彈scrollview詳細(xì)教程,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-09-09
Android中home鍵和back鍵區(qū)別實(shí)例分析
這篇文章主要介紹了Android中home鍵和back鍵區(qū)別,以實(shí)例形式較為詳細(xì)的分析并總結(jié)了home鍵和back鍵區(qū)別及使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09
Android點(diǎn)擊事件的實(shí)現(xiàn)方式
這篇文章主要為大家詳細(xì)介紹了Android點(diǎn)擊事件的實(shí)現(xiàn)方式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
Android實(shí)現(xiàn)倒計(jì)時(shí)30分鐘功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)倒計(jì)時(shí)30分鐘功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
Android WebViewClient 的 `shouldOverrideUrlLoa
這篇文章主要介紹了Android WebViewClient 的 shouldOverrideUrlLoading方法,了解并正確實(shí)現(xiàn) WebViewClient 中的 shouldOverrideUrlLoading 方法對于在你的 Android 應(yīng)用中提供順暢且安全的瀏覽體驗(yàn)至關(guān)重要,需要的朋友可以參考下2024-07-07

