Android手势ImageView三部曲 第三部
接着上一节 android手势imageview三部曲(二)的往下走,我们讲到了github上的gesturedetector框架,
先附上github链接:
https://github.com/almeros/android-gesture-detectors
其实把这个框架的主体思想也是参考的android自带的scalegesturedetector工具类,scalegesturedetector估计是参考的gesturedetector工具类,不管谁参考谁的,既然被我们遇到了,我们就要变成自己的东西,真不能全变成自己的东西的话,至少
我们要了解下它的思想。
我们先了解一下android自带的scalegesturedetector(缩放手势监测器):
scalegesturedetector跟gesturedetector构造都差不多,但是scalegesturedetector只能用于监测缩放的手势,而gesturedetector监测的手势就比较多了,我们上一节内容中有提到。
scalegesturedetector的一些用法跟api,小伙伴自行去查看官网文档:
https://developer.android.google.cn/reference/android/view/scalegesturedetector.html
我们怎么使用它呢(我以第一节中最后一个demo为例)?
首先创建一个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; } 好啦~!!上
一节最后的时候,我写了一个小demo去实现了图片的位移,下面我们继续加上图片的缩放:
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); //创建一个缩放手势监测器 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上大神已经实现了,但是如果没有的话,你能写出来么?
先写到这,未完待续。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。