Android事件分发机制四:学了事件分发有什么用? (2)

这个需求可以通过结合属性动画来实现。按钮块本身有高度、有圆角,我们可以考虑继承cardView来实现,重写cardView的dispatchTouchEvent方法,在按下的时候,也就是接收到down事件的时候缩小,在接收到up和cancel事件的时候恢复。注意,这里可能会忽视cancel事件,导致按钮块的状态无法恢复,一定要加以考虑cancel事件 。然后来看下代码实现:

public class NewCardView extends CardView { //点击事件到来的时候进行判断处理 @Override public boolean dispatchTouchEvent(MotionEvent ev) { // 获取事件类型 int actionMarked = ev.getActionMasked(); // 根据时间类型判断调用哪个方法来展示动画 switch (actionMarked){ case MotionEvent.ACTION_DOWN :{ clickEvent(); break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: upEvent(); break; default: break; } // 最后回调默认的事件分发方法即可 return super.dispatchTouchEvent(ev); } //手指按下的时候触发的事件;大小高度变小,透明度减少 private void clickEvent(){ setCardElevation(4); AnimatorSet set = new AnimatorSet(); set.playTogether( ObjectAnimator.ofFloat(this,"scaleX",1,0.97f), ObjectAnimator.ofFloat(this,"scaleY",1,0.97f), ObjectAnimator.ofFloat(this,"alpha",1,0.9f) ); set.setDuration(100).start(); } //手指抬起的时候触发的事件;大小高度恢复,透明度恢复 private void upEvent(){ setCardElevation(getCardElevation()); AnimatorSet set = new AnimatorSet(); set.playTogether( ObjectAnimator.ofFloat(this,"scaleX",0.97f,1), ObjectAnimator.ofFloat(this,"scaleY",0.97f,1), ObjectAnimator.ofFloat(this,"alpha",0.9f,1) ); set.setDuration(100).start(); } }

动画方面的内容就不分析了,不属于本文的范畴。可以看到我们只是给cardView设置了动画效果,监听事件我们可以设置给cardView内部的ImageView或者直接设置给CardView。需要注意的是,如果设置给cardView需要重写cardView的 intercepTouchEvent 方法永远返回true,防止事件被子view消费而无法触发监听事件。

解决滑动冲突

滑动冲突是事件分发运用最频繁的场景,也是一个重难点(敲黑板,考试要考的)。滑动冲突的基本场景有以下三种:

Android事件分发机制四:学了事件分发有什么用?

情况一:内外view的滑动方向不同,例如viewPager嵌套ListView

情况二:内外view滑动方向相同,例如viewPager嵌套水平滑动的recyclerView

情况三:情况一和情况二的组合

解决这类问题一般有两个步骤:确定最终实现效果、代码实现。

滑动冲突的解决需要结合具体的实现需求,而不是一套解决方案可以解决一切的滑动冲突问题,这不现实。因此在解决这类问题时,需要先确定好最终的实现效果,然后再根据这个效果去思考代码实现。这里主要讨论情况一和情况二,情况三类同。

情况一

情况一是内外滑动方向不一致。这种情况的通用解决方案就是:根据手指滑动直线与水平线的角度来判断是左右滑动还是上下滑动:

Android事件分发机制四:学了事件分发有什么用?

如果这个角度小于45度,可以认为是在左右滑动,如果大于45度,则认为是上下滑动。那么现在确定好解决方案,接下来就思考如何代码实现。

滑动角度可以通过两个连续的MotionEvent对象的坐标计算出来,之后我们再根据角度的情况选择把事件交给外部容器还是内部view。这里根据事件处理的位置可分为内部拦截法和外部拦截法

外部拦截法:在viewGroup中判断滑动的角度,如果符合自身滑动方向消费则拦截事件

内部拦截法:在内部view中判断滑动的角度,如果是符合自身滑动方向则继续消费事件,否则请求外部viewGroup拦截事件处理

从实现的复杂度来看,外部拦截法会更加优秀,不需要里外view去配合,只需要viewGroup自身做好事件拦截处理即可。两者的区别就在于主动权在谁的手上。如果view需要做更多的判断可以采用内部拦截法,而一般情况下采用外部拦截法会更加简单。

接下来思考一下这两种方法的代码实现。

外部拦截法中,重点在于是否拦截事件,那么我们的重心就放在了 onInterceptTouchEvent 方法中。在这个方法中计算滑动角度并判断是否要进行拦截。这里以ScrollView为例子(外部是垂直滑动,内部是水平滑动),代码如下:

public class MyScrollView extends ScrollView { // 记录上一次事件的坐标 float lastX = 0; float lastY = 0; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int actionMasked = ev.getActionMasked(); // 不能拦截down事件,否则子view永远无法获取到事件 // 不能拦截up事件,否则子view的点击事件无法被触发 if (actionMasked == MotionEvent.ACTION_DOWN || actionMasked == MotionEvent.ACTION_UP){ lastX = ev.getX(); lastY = ev.getY(); return false; } // 获取斜率并判断 float x = ev.getX(); float y = ev.getY(); return Math.abs(lastX - x) < Math.abs(lastY - y); } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wssxwz.html