Android手勢ImageView三部曲 第三部
接著上一節(jié) Android手勢ImageView三部曲(二)的往下走,我們講到了github上的GestureDetector框架,
先附上github鏈接:
https://github.com/Almeros/android-gesture-detectors
其實把這個框架的主體思想也是參考的Android自帶的ScaleGestureDetector工具類,ScaleGestureDetector估計是參考的GestureDetector工具類,不管誰參考誰的,既然被我們遇到了,我們就要變成自己的東西,真不能全變成自己的東西的話,至少
我們要了解下它的思想。
我們先了解一下android自帶的ScaleGestureDetector(縮放手勢監(jiān)測器):
ScaleGestureDetector跟GestureDetector構造都差不多,但是ScaleGestureDetector只能用于監(jiān)測縮放的手勢,而GestureDetector監(jiān)測的手勢就比較多了,我們上一節(jié)內容中有提到。
ScaleGestureDetector的一些用法跟api,小伙伴自行去查看官網文檔:
https://developer.android.google.cn/reference/android/view/ScaleGestureDetector.html
我們怎么使用它呢(我以第一節(jié)中最后一個demo為例)?
首先創(chuàng)建一個ScaleGestureDetector對象:
private void initView() {
....
mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
....
}
然后傳遞一個叫ScaleListener的回調接口給它,ScaleListener里面有三個回調方法:
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor(); // scale change since previous event
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
changeMatrix();
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return super.onScaleBegin(detector);
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
super.onScaleEnd(detector);
}
}
小伙伴應該看得懂哈,就是onScale放縮時回調,onScaleBegin縮放開始時回調,onScaleEnd縮放完畢后回調。
最后在view的onTouchEvent方法中把事件給ScaleGestureDetector對象:
@Override
public boolean onTouchEvent(MotionEvent event) {
//把縮放事件給mScaleDetector
mScaleDetector.onTouchEvent(event);
return true;
}
好啦~?。∩?/pre>
一節(jié)最后的時候,我寫了一個小demo去實現了圖片的位移,下面我們繼續(xù)加上圖片的縮放:
public class MatrixImageView extends ImageView {
private Matrix currMatrix;
private GestureDetector detector;
private ScaleGestureDetector scaleDetector;
private float currX;//當前圖片的x坐標值
private float currY;//當前圖片的y坐標值
private float scaleFactor=1f;//當前圖片的縮放值
public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
detector=new GestureDetector(context,onGestureListener);
//創(chuàng)建一個縮放手勢監(jiān)測器
scaleDetector=new ScaleGestureDetector(context,onScaleGestureListener);
}
private void initView() {
currMatrix = new Matrix();
DisplayMetrics dm = getResources().getDisplayMetrics();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
setImageBitmap(bitmap);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
detector.onTouchEvent(event);
//把事件給scaleDetector
scaleDetector.onTouchEvent(event);
return true;
}
private void setMatrix(){
currMatrix.reset();
currMatrix.postTranslate(currX,currY);
currMatrix.postScale(scaleFactor,scaleFactor,getMeasuredWidth()/2,getMeasuredHeight()/2);
setImageMatrix(currMatrix);
}
private GestureDetector.SimpleOnGestureListener onGestureListener=new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
currX-=distanceX;
currY-=distanceY;
setMatrix();
return super.onScroll(e1, e2, distanceX, distanceY);
}
};
private ScaleGestureDetector.SimpleOnScaleGestureListener onScaleGestureListener=new ScaleGestureDetector.SimpleOnScaleGestureListener(){
@Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor*=detector.getScaleFactor();
setMatrix();
/**
* 因為getScaleFactor=當前兩個手指之間的距離(preEvent)/手指按下時候兩個點的距離(currEvent)
* 這里如果返回true的話,會在move操作的時候去更新之前的event,
* 如果為false的話,不會去更新之前按下時候保存的event
*/
return true;
}
};
}
尷尬了,模擬器不太好用于兩個手指縮放的錄制,所以效果小伙伴自己拿到代碼運行一下哈~!??!
下面一起擼一擼ScaleGestureDetector的源碼:
我們知道,ScaleGestureDetector核心代碼也就是onTouchEvent,于是我們點開onTouchEvent:
@SuppressLint("NewApi")
public boolean onTouchEvent(MotionEvent event) {
//獲取當前event事件
mCurrTime = event.getEventTime();
final int action = event.getActionMasked();
// Forward the event to check for double tap gesture
if (mQuickScaleEnabled) {
mGestureDetector.onTouchEvent(event);
}
//獲取手指個數
final int count = event.getPointerCount();
final boolean isStylusButtonDown =
(event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
final boolean anchoredScaleCancelled =
mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;
final boolean streamComplete = action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
//手指按下的時候
if (action == MotionEvent.ACTION_DOWN || streamComplete) {
// Reset any scale in progress with the listener.
// If it's an ACTION_DOWN we're beginning a new event stream.
// This means the app probably didn't give us all the events. Shame on it.
if (mInProgress) {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = 0;
mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
} else if (inAnchoredScaleMode() && streamComplete) {
mInProgress = false;
mInitialSpan = 0;
mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
}
if (streamComplete) {
return true;
}
}
if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode()
&& !streamComplete && isStylusButtonDown) {
// Start of a button scale gesture
mAnchoredScaleStartX = event.getX();
mAnchoredScaleStartY = event.getY();
mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;
mInitialSpan = 0;
}
final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_POINTER_UP ||
action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;
final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? event.getActionIndex() : -1;
//處理多點之間距離
float sumX = 0, sumY = 0;
final int div = pointerUp ? count - 1 : count;
final float focusX;
final float focusY;
if (inAnchoredScaleMode()) {
// In anchored scale mode, the focal pt is always where the double tap
// or button down gesture started
focusX = mAnchoredScaleStartX;
focusY = mAnchoredScaleStartY;
if (event.getY() < focusY) {
mEventBeforeOrAboveStartingGestureEvent = true;
} else {
mEventBeforeOrAboveStartingGestureEvent = false;
}
} else {
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
sumX += event.getX(i);
sumY += event.getY(i);
}
focusX = sumX / div;
focusY = sumY / div;
}
// Determine average deviation from focal point
float devSumX = 0, devSumY = 0;
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
// Convert the resulting diameter into a radius.
devSumX += Math.abs(event.getX(i) - focusX);
devSumY += Math.abs(event.getY(i) - focusY);
}
final float devX = devSumX / div;
final float devY = devSumY / div;
final float spanX = devX * 2;
final float spanY = devY * 2;
final float span;
if (inAnchoredScaleMode()) {
span = spanY;
} else {
span = (float) Math.hypot(spanX, spanY);
}
// Dispatch begin/end events as needed.
// If the configuration changes, notify the app to reset its current state by beginning
// a fresh scale event stream.
final boolean wasInProgress = mInProgress;
mFocusX = focusX;
mFocusY = focusY;
if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = span;
}
if (configChanged) {
mPrevSpanX = mCurrSpanX = spanX;
mPrevSpanY = mCurrSpanY = spanY;
mInitialSpan = mPrevSpan = mCurrSpan = span;
}
final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;
if (!mInProgress && span >= minSpan &&
(wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
mPrevSpanX = mCurrSpanX = spanX;
mPrevSpanY = mCurrSpanY = spanY;
mPrevSpan = mCurrSpan = span;
mPrevTime = mCurrTime;
mInProgress = mListener.onScaleBegin(this);
}
// Handle motion; focal point and span/scale factor are changing.
if (action == MotionEvent.ACTION_MOVE) {
mCurrSpanX = spanX;
mCurrSpanY = spanY;
mCurrSpan = span;
boolean updatePrev = true;
if (mInProgress) {
updatePrev = mListener.onScale(this);
}
if (updatePrev) {
mPrevSpanX = mCurrSpanX;
mPrevSpanY = mCurrSpanY;
mPrevSpan = mCurrSpan;
mPrevTime = mCurrTime;
}
}
return true;
}
一堆代碼,數學不太好的看起來還真比較艱難,大概就是根據多個觸碰點的x坐標算出一個x軸平均值,然后y軸也一樣,然后通過Math.hypot(spanX, spanY);算出斜邊長,斜邊長即為兩點之間的距離,然后保存當前的span跟移動過后的span。
最后當我們調用getScaleFactor獲取縮放比例的時候,即用現在的span/之前的span:
public float getScaleFactor() {
if (inAnchoredScaleMode()) {
// Drag is moving up; the further away from the gesture
// start, the smaller the span should be, the closer,
// the larger the span, and therefore the larger the scale
final boolean scaleUp =
(mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan < mPrevSpan)) ||
(!mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan > mPrevSpan));
final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR);
return mPrevSpan <= 0 ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff);
}
return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
}
這數學渣真的是硬傷啊~~~
有了android自帶的ScaleGestureDetector作為參考,我們能自己實現ScaleGestureDetector嗎?? 當然github上大神已經實現了,但是如果沒有的話,你能寫出來么?
先寫到這,未完待續(xù)。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Android多線程+單線程+斷點續(xù)傳+進度條顯示下載功能
這篇文章主要介紹了Android多線程+單線程+斷點續(xù)傳+進度條顯示下載功能,需要的朋友可以參考下2017-06-06
Android編程實現帶有單選按鈕和復選按鈕的dialog功能示例
這篇文章主要介紹了Android編程實現帶有單選按鈕和復選按鈕的dialog功能,結合具體實例形式分析了Android實現帶有單選按鈕的dialog對話框及帶有復選按鈕的dialog對話框相關操作技巧,需要的朋友可以參考下2017-09-09

