Android手勢(shì)ImageView三部曲 第三部
接著上一節(jié) Android手勢(shì)ImageView三部曲(二)的往下走,我們講到了github上的GestureDetector框架,
先附上github鏈接:
https://github.com/Almeros/android-gesture-detectors
其實(shí)把這個(gè)框架的主體思想也是參考的Android自帶的ScaleGestureDetector工具類,ScaleGestureDetector估計(jì)是參考的GestureDetector工具類,不管誰(shuí)參考誰(shuí)的,既然被我們遇到了,我們就要變成自己的東西,真不能全變成自己的東西的話,至少
我們要了解下它的思想。
我們先了解一下android自帶的ScaleGestureDetector(縮放手勢(shì)監(jiān)測(cè)器):
ScaleGestureDetector跟GestureDetector構(gòu)造都差不多,但是ScaleGestureDetector只能用于監(jiān)測(cè)縮放的手勢(shì),而GestureDetector監(jiān)測(cè)的手勢(shì)就比較多了,我們上一節(jié)內(nèi)容中有提到。
ScaleGestureDetector的一些用法跟api,小伙伴自行去查看官網(wǎng)文檔:
https://developer.android.google.cn/reference/android/view/ScaleGestureDetector.html
我們?cè)趺词褂盟兀ㄎ乙缘谝还?jié)中最后一個(gè)demo為例)?
首先創(chuàng)建一個(gè)ScaleGestureDetector對(duì)象:
private void initView() { .... mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener()); .... }
然后傳遞一個(gè)叫ScaleListener的回調(diào)接口給它,ScaleListener里面有三個(gè)回調(diào)方法:
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); } }
小伙伴應(yīng)該看得懂哈,就是onScale放縮時(shí)回調(diào),onScaleBegin縮放開始時(shí)回調(diào),onScaleEnd縮放完畢后回調(diào)。
最后在view的onTouchEvent方法中把事件給ScaleGestureDetector對(duì)象:
@Override public boolean onTouchEvent(MotionEvent event) { //把縮放事件給mScaleDetector mScaleDetector.onTouchEvent(event); return true; } 好啦~??!上
一節(jié)最后的時(shí)候,我寫了一個(gè)小demo去實(shí)現(xiàn)了圖片的位移,下面我們繼續(xù)加上圖片的縮放:
public class MatrixImageView extends ImageView { private Matrix currMatrix; private GestureDetector detector; private ScaleGestureDetector scaleDetector; private float currX;//當(dāng)前圖片的x坐標(biāo)值 private float currY;//當(dāng)前圖片的y坐標(biāo)值 private float scaleFactor=1f;//當(dāng)前圖片的縮放值 public MatrixImageView(Context context, AttributeSet attrs) { super(context, attrs); initView(); detector=new GestureDetector(context,onGestureListener); //創(chuàng)建一個(gè)縮放手勢(shì)監(jiān)測(cè)器 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(); /** * 因?yàn)間etScaleFactor=當(dāng)前兩個(gè)手指之間的距離(preEvent)/手指按下時(shí)候兩個(gè)點(diǎn)的距離(currEvent) * 這里如果返回true的話,會(huì)在move操作的時(shí)候去更新之前的event, * 如果為false的話,不會(huì)去更新之前按下時(shí)候保存的event */ return true; } }; }
尷尬了,模擬器不太好用于兩個(gè)手指縮放的錄制,所以效果小伙伴自己拿到代碼運(yùn)行一下哈~!??!
下面一起擼一擼ScaleGestureDetector的源碼:
我們知道,ScaleGestureDetector核心代碼也就是onTouchEvent,于是我們點(diǎn)開onTouchEvent:
@SuppressLint("NewApi") public boolean onTouchEvent(MotionEvent event) { //獲取當(dāng)前event事件 mCurrTime = event.getEventTime(); final int action = event.getActionMasked(); // Forward the event to check for double tap gesture if (mQuickScaleEnabled) { mGestureDetector.onTouchEvent(event); } //獲取手指?jìng)€(gè)數(shù) 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; //手指按下的時(shí)候 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; //處理多點(diǎn)之間距離 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; }
一堆代碼,數(shù)學(xué)不太好的看起來(lái)還真比較艱難,大概就是根據(jù)多個(gè)觸碰點(diǎn)的x坐標(biāo)算出一個(gè)x軸平均值,然后y軸也一樣,然后通過(guò)Math.hypot(spanX, spanY);算出斜邊長(zhǎng),斜邊長(zhǎng)即為兩點(diǎn)之間的距離,然后保存當(dāng)前的span跟移動(dòng)過(guò)后的span。
最后當(dāng)我們調(diào)用getScaleFactor獲取縮放比例的時(shí)候,即用現(xiàn)在的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; }
這數(shù)學(xué)渣真的是硬傷啊~~~
有了android自帶的ScaleGestureDetector作為參考,我們能自己實(shí)現(xiàn)ScaleGestureDetector嗎?? 當(dāng)然github上大神已經(jīng)實(shí)現(xiàn)了,但是如果沒(méi)有的話,你能寫出來(lái)么?
先寫到這,未完待續(xù)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android手勢(shì)ImageView三部曲 第二部
- Android手勢(shì)ImageView三部曲 第一部
- Android自定義GestureDetector實(shí)現(xiàn)手勢(shì)ImageView
- Android使用ImageView實(shí)現(xiàn)支持手勢(shì)縮放效果
- Android ImageView隨手勢(shì)變化動(dòng)態(tài)縮放圖片
- Android手勢(shì)滑動(dòng)實(shí)現(xiàn)ImageView縮放圖片大小
- Android實(shí)現(xiàn)手勢(shì)控制ImageView圖片大小
- Android通過(guò)手勢(shì)實(shí)現(xiàn)的縮放處理實(shí)例代碼
- android開發(fā)之為activity增加左右手勢(shì)識(shí)別示例
- android使用gesturedetector手勢(shì)識(shí)別示例分享
相關(guān)文章
Android Retrofit和Rxjava的網(wǎng)絡(luò)請(qǐng)求
這篇文章主要介紹了Android Retrofit和Rxjava的網(wǎng)絡(luò)請(qǐng)求的相關(guān)資料,需要的朋友可以參考下2017-03-03Android多線程+單線程+斷點(diǎn)續(xù)傳+進(jìn)度條顯示下載功能
這篇文章主要介紹了Android多線程+單線程+斷點(diǎn)續(xù)傳+進(jìn)度條顯示下載功能,需要的朋友可以參考下2017-06-06Android編程實(shí)現(xiàn)3D旋轉(zhuǎn)效果實(shí)例
這篇文章主要介紹了Android編程實(shí)現(xiàn)3D旋轉(zhuǎn)效果的方法,基于Android的Camera類實(shí)現(xiàn)坐標(biāo)變換達(dá)到圖片3D旋轉(zhuǎn)效果,需要的朋友可以參考下2016-01-01Android使用http請(qǐng)求手機(jī)號(hào)碼歸屬地查詢代碼分享
這篇文章主要介紹了Android使用http請(qǐng)求手機(jī)號(hào)碼歸屬地查詢代碼分享的相關(guān)資料,需要的朋友可以參考下2016-06-06Android中微信搶紅包助手的實(shí)現(xiàn)詳解
本篇文章主要介紹了Android中微信搶紅包助手的實(shí)現(xiàn)詳解,通過(guò)利用AccessibilityService輔助服務(wù),監(jiān)測(cè)屏幕內(nèi)容,如監(jiān)聽(tīng)狀態(tài)欄的信息,屏幕跳轉(zhuǎn)等,以此來(lái)實(shí)現(xiàn)自動(dòng)拆紅包的功能,有興趣的可以了解一下。2017-02-02Android編程獲取網(wǎng)絡(luò)時(shí)間實(shí)例分析
這篇文章主要介紹了Android編程獲取網(wǎng)絡(luò)時(shí)間,結(jié)合實(shí)例形式對(duì)比分析了Android通過(guò)訪問(wèn)網(wǎng)絡(luò)及通過(guò)GPS獲取網(wǎng)絡(luò)時(shí)間的具體步驟與實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-01-01Android編程實(shí)現(xiàn)帶有單選按鈕和復(fù)選按鈕的dialog功能示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)帶有單選按鈕和復(fù)選按鈕的dialog功能,結(jié)合具體實(shí)例形式分析了Android實(shí)現(xiàn)帶有單選按鈕的dialog對(duì)話框及帶有復(fù)選按鈕的dialog對(duì)話框相關(guān)操作技巧,需要的朋友可以參考下2017-09-09Android實(shí)現(xiàn)千變?nèi)f化的ViewPager切換動(dòng)畫
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)千變?nèi)f化的ViewPager切換動(dòng)畫,自定義PageTransformer實(shí)現(xiàn)個(gè)性的切換動(dòng)畫,感興趣的小伙伴們可以參考一下2016-05-05