android實現圖片縮放、移動、單擊退出、雙擊縮放
實現思路
思路:重寫用於顯示圖片的ImageView,定義ScaleGestureDetector(縮放手勢檢測)型別、GestureDetector(雙擊手勢檢測)型別的變數進行手勢檢測並重寫方法實現圖片的縮放、移動、單擊退出、雙擊放大等功能。
重寫的方法implements OnTouchListener介面,重寫onTouch方法對手勢進行監聽
ScaleGestureDetector mScaleGestureDetector ;
GestureDetector mGestureDetector;
public boolean onTouch(View v, MotionEvent event )
{
if (mGestureDetector.onTouchEvent(event))
return true; //單雙擊監聽
mScaleGestureDetector.onTouchEvent(event); //縮放監聽
圖片移動處理的code; //移動監聽及處理
}
為什麼單雙擊監聽的時候檢測到單雙擊直接就返回而不執行後面的程式碼?這裡主要考慮到但雙擊的時候是自動縮放,一般我們不會在自動縮放的時候還會移動圖片,但是如果是人手動考手勢縮放圖片,往往還會伴隨著移動圖片的處理,所以檢測到手動縮放的程式碼時不返回,仍然要執行後面的移動圖片的程式碼。
單雙擊處理程式碼:
mGestureDetector = new GestureDetector(context,
new SimpleOnGestureListener()
{
//單擊退出
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
EventBus.getDefault().post(new ZoomBus());
return super.onSingleTapConfirmed(e);
}
//雙擊放大縮小
@Override
public boolean onDoubleTap(MotionEvent e)
{
if (isAutoScale == true) //首先判斷是否正在自動縮放,如果是直接返回true
return true;
float x = e.getX();
float y = e.getY();
Log.e("DoubleTap", getScale() + " , " + initScale);
//postDelayed每16ms執行一個AutoScaleRunnable方法
if (getScale() < SCALE_MID)
{
ZoomImageView.this.postDelayed(
new AutoScaleRunnable(SCALE_MID, x, y), 16);//如果獲得的縮放比例小於SCALE_MID,那麼就設定縮放比例為SCALE_MID
isAutoScale = true;
} else if (getScale() >= SCALE_MID
&& getScale() < SCALE_MAX)
{
ZoomImageView.this.postDelayed(
new AutoScaleRunnable(SCALE_MAX, x, y), 16);//如果獲得的縮放比例大於SCALE_MID並且小於SCALE_MAX,就設定縮放比例為SCALE_MAX
isAutoScale = true;
} else
{
ZoomImageView.this.postDelayed(
new AutoScaleRunnable(initScale, x, y), 16);//同理
isAutoScale = true;
}
return true;
}
});
/*AutoScaleRunnable()*/
private class AutoScaleRunnable implements Runnable
{
static final float BIGGER = 1.07f;
static final float SMALLER = 0.93f;
private float mTargetScale;
private float tmpScale;
/**
* 縮放的中心
*/
private float x;
private float y;
/**
* 傳入目標縮放值,根據目標值與當前值,判斷應該放大還是縮小
*
* @param targetScale
*/
public AutoScaleRunnable(float targetScale, float x, float y)
{
this.mTargetScale = targetScale;
this.x = x;
this.y = y;
if (getScale() < mTargetScale)
{
tmpScale = BIGGER;
} else
{
tmpScale = SMALLER;
}
}
@Override
public void run()
{
// 進行縮放
mScaleMatrix.postScale(tmpScale, tmpScale, x, y);
checkBorderAndCenterWhenScale();//如果不用,圖片寬高大於螢幕時,圖片與螢幕間出現白邊;圖片小於螢幕,但是不居中。
setImageMatrix(mScaleMatrix);
final float currentScale = getScale();
// 如果值在合法範圍內,繼續縮放
if (((tmpScale > 1f) && (currentScale < mTargetScale))
|| ((tmpScale < 1f) && (mTargetScale < currentScale)))
{
Log.d("合理範圍",currentScale+" "+mTargetScale+" "+tmpScale);
ZoomImageView.this.postDelayed(this, 16);
} else
// 設定為目標的縮放比例
{
Log.d("目標範圍","目標範圍"+currentScale+" "+mTargetScale);
final float deltaScale = mTargetScale / currentScale;
Log.d("deltaScale",deltaScale+" ");
mScaleMatrix.postScale(deltaScale, deltaScale, x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(mScaleMatrix);
isAutoScale = false;
}
}
}
new SimpleOnGestureListener()方法處理單擊退出,這裡用了EventBus,有關EventBus的使用可以搜尋看一下,有handler的效果。在顯示這個圖片的Activity中的程式碼如下:
public class PictureShowActivity extends Activity {
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_picture);
EventBus.getDefault().register(this);
....
@Subscribe(threadMode = ThreadMode.MAIN)
public void onUserEvent(ZoomBus zoomBus) { //自己定義個ZoomBuS實體,可以是空實體
Log.d("zoomBus",zoomBus+"");
finish();
}
}
public boolean onDoubleTap(MotionEvent e)處理的是雙擊縮放,雙擊時首先判斷當前縮放比例getScale(),然後拿當前縮放比例和initScale、SCALE_MID、SCALE_MAX進行比較以定出自動縮放的縮放比例,然後呼叫AutoScaleRunnable()方法,注意AutoScaleRunnable()方法進行了邊界檢測checkBorderAndCenterWhenScale()
手動縮放程式碼:
@SuppressLint("NewApi")
@Override
public boolean onScale(ScaleGestureDetector detector)
{
Log.d("scale","scale");
float scale = getScale();//當前已經縮放的縮放比例
float scaleFactor = detector.getScaleFactor();//通過手勢檢測檢測到的縮放比例
if (getDrawable() == null)
return true;
/**
* 縮放的範圍控制
*/
if ((scale < SCALE_MAX && scaleFactor > 1.0f)
|| (scale > initScale && scaleFactor < 1.0f))
{
/**
* 最大值最小值判斷
*/
if (scaleFactor * scale < initScale)
{
scaleFactor = initScale / scale;
}
if (scaleFactor * scale > SCALE_MAX)
{
scaleFactor = SCALE_MAX / scale;
}
/**
* 設定縮放比例
*/
mScaleMatrix.postScale(scaleFactor, scaleFactor,
detector.getFocusX(), detector.getFocusY());
checkBorderAndCenterWhenScale();
setImageMatrix(mScaleMatrix);
}
return true;
}
具體流程如下:
移動程式碼:(在onTouch方法裡面)
float x = 0, y = 0;
// 拿到觸控點的個數
final int pointerCount = event.getPointerCount();
// 得到多個觸控點的x與y均值
for (int i = 0; i < pointerCount; i++)
{
x += event.getX(i);
y += event.getY(i);
}
x = x / pointerCount;
y = y / pointerCount;
/**
* 每當觸控點發生變化時,重置mLasX , mLastY
*/
if (pointerCount != lastPointerCount)
{
isCanDrag = false;
mLastX = x;
mLastY = y;
}
lastPointerCount = pointerCount;
RectF rectF = getMatrixRectF();
switch (event.getAction())
{
// case MotionEvent.ACTION_DOWN:
// if (rectF.width() > getWidth() || rectF.height() > getHeight())
// {
// getParent().requestDisallowInterceptTouchEvent(true);
// }
// break;
case MotionEvent.ACTION_MOVE:
// if (rectF.width() > getWidth() || rectF.height() > getHeight())
// {
// getParent().requestDisallowInterceptTouchEvent(true);
// }
Log.e(TAG, "ACTION_MOVE");
float dx = x - mLastX;
float dy = y - mLastY;
if (!isCanDrag)
{
isCanDrag = isCanDrag(dx, dy);
}
if (isCanDrag)
{
if (getDrawable() != null)
{
// if (getMatrixRectF().left == 0 && dx > 0)
// {
// getParent().requestDisallowInterceptTouchEvent(false);
// }
//
// if (getMatrixRectF().right == getWidth() && dx < 0)
// {
// getParent().requestDisallowInterceptTouchEvent(false);
// }
isCheckLeftAndRight = isCheckTopAndBottom = true;
// 如果寬度小於螢幕寬度,則禁止左右移動
if (rectF.width() < getWidth())
{
dx = 0;
isCheckLeftAndRight = false;
}
// 如果高度小雨螢幕高度,則禁止上下移動
if (rectF.height() < getHeight())
{
dy = 0;
isCheckTopAndBottom = false;
}
mScaleMatrix.postTranslate(dx, dy);
checkMatrixBounds();
setImageMatrix(mScaleMatrix);
}
}
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "ACTION_UP");
lastPointerCount = 0;
break;
}
return true;
首先我們拿到觸控點的數量,然後求出多個觸控點的平均值,設定給我們的mLastX , mLastY , 然後在移動的時候,得到dx ,dy 進行範圍檢查以後,呼叫mScaleMatrix.postTranslate進行設定偏移量,設定完成以後,還需要再次校驗一下,不能把圖片移動的與螢幕邊界出現白邊,校驗完成後,呼叫setImageMatrix.