四、界面编程(四) View的滑动及滑动冲突详解
来源:互联网 发布:数据库2008安装教程 编辑:程序博客网 时间:2024/05/29 15:28
- View的滑动
- 1 使用scrollToscrollBy
- 2 使用动画
- 3 改变布局参数
- 4 各种滑动方式的比较
- 弹性滑动
- 1 使用Scroller
- 2 通过动画
- View的滑动冲突处理
- 1常见的滑动冲突场景
- 11 外部滑动方向和内部滑动的方向不一致
- 12 外部滑动方向和内部滑动方向一致
- 2 滑动冲突的解决
- 21 外部拦截法
- 22 内部拦截法
- 1常见的滑动冲突场景
1.View的滑动
View的滑动应用非常广泛,不论是下拉刷新还是SlidingMenu,他们的基础都是滑动。掌握滑动的方法是实现自定义控件的基础。目前,常用的滑动方式是以下三种:
(1)通过View本身提供的scrollTo/scrollBy方法来实现滑动;
(2)通过动画给View施加平移效果来实现滑动
(3)改变View的LayoutParams使得View重新布局从而实现滑动
1.1 使用scrollTo/scrollBy
为了实现View的滑动,View提供了专门的方法来实现这个功能。那就是scrollTo/scrollBy。经过分析源码得知,scrollBy实际上也是调用了scrollTo方法,而scrollTo则实现了基于所传递参数的绝对滑动。利用scrollTo和scrollBy来实现View的滑动,这不困难,但我们要知道。滑动过程中,View内部的两个属性mScrollX和mScrollY的改变规则。这两个属性可以通过getScrollX和getScrollY方法分别得到。
在滑动过程中,mScrollX的值总是等于View左边缘和View内容左边缘在水平方向上的距离,而mScrollY的值,总是等于View上边缘和View内容上边缘的竖直方向的距离。scrollTo和scrollBy只能改变View的内容的位置,而不能改变View在布局中的位置。mScrollX和mScrollY的单位是像素,并且当View左边缘在View内容左边缘右边时,mScrollX为正值,反之为负值。当View上边缘在View内容上边缘的下边时,mScrollY为正值,反之为负值。换句话说,如果从左向右滑动,那么mScrollX为负值,反之为正值。如果从上往下滑动,mScrollY为负值,反之为正值。
1.2 使用动画
使用动画来移动View,主要操作View的translationX和translationY属性,即可以使用传统的View动画来完成,也可以采用属性动画,如果采用属性动画的话,为了兼容3.0一下版本,需要采用开源动画库nineoldandroids(http://nineoldandroids.com/)
使用传统的View动画,可以xml中的指定translate,采用属性动画,更简单了,比如:
ObjectAnimator.ofFloat(targetView,”translationX”,0,100).setDuration(100).start();
需要注意的一点是,View动画是对View的影像的操作,它并不能真正改变View的位置包括宽/高,造成View无法响应点击事件,因为View的真身还在原来的位置,并且如果希望动画后的状态得以保留。还必须将fillAfter的属性设置为true,否则动画完成后,其动画效果就会消失。
1.3 改变布局参数
改变布局参数,即改变LayoutParams比较好理解,比如我们想把一个Button向右平移100px,我们主要将这个Button的LayoutParams里的marginLeft参数的值增加100px即可。
MarginLayoutParams params = (MarginLayoutParams )mButton1.getLayoutParams();Params.width +=100;params.leftMargin +=100;mButton1.requestLayout();
有一种情形,为了达到移动Button的目的,我们可以在Button的左边放置一个空的view,这个View的默认宽度为0,当我们需要向右移动Button时,只需要重新设置空View的宽度即可,当空View的宽度增大时,Button就会被挤到右边,即实现了向右平移的效果。
1.4 各种滑动方式的比较
(1)scrollTo/scrollBy方式:它是View提供的原生方法,作用是专门用于VIew的滑动,它可以比较方便地实现滑动效果并且不影响内部元素的单击事件。但是它的缺点也是很显然的,它只能滑动VIew的内容,并不能滑动View本身。即操作简单,适合View内容的滑动。
(2)动画:实际使用中,如果动画元素不需要响应用户的交互,那么使用动画来做滑动比较合适,否则就不太合适。但是动画有一个很明显的优点,那就是一些复杂的动画效果必须通过动画才能实现。即操作简单,主要适用于没有交互的View和实现复杂的动画效果。
(3)改变布局的方式:操作稍微复杂,适用于有交互的View
2.弹性滑动
View生硬的滑动过去,用户体验实在太差,因此,我们要实现渐进式的滑动,渐进式滑动有一个共同思想,将依次大的滑动分成若干次小的滑动,并在一个时间段内完成。常用的弹性滑动有三种方式:
(1)使用scroller
(2)使用动画
(3)延时策略
2.1 使用Scroller
Scroller的使用方法
Scroller mScroller = new Scroller(mContext); //缓慢滑动到指定位置 private void smoothScrollTo(int destX,int destY){ int scrollX = getScrollX(); int deltaX = destX - scrollX; //1000ms内滑向destX,效果就是慢慢滑动 mScroller.startScroll(scrollX,0,deltaX,0,1000); invalidate(); } public void computeScroll(){ if(mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); postInvalidate(); } }
以上是Scroller的典型使用方法,它的工作原理如下:
当我们构造了一个Scroller对象并且调用它的startScroller方法时,Scroller内部其实什么也没做,它只是保存了我们传递的几个参数,这几个参数从startScroll的原型上就可以看出来,代码如下所示:
public void startScroll(int startX, int startY, int dx, int dy, int duration) { mMode = SCROLL_MODE; mFinished = false; mDuration = duration; //滑动时间,整个滑动过程完成的时间 mStartTime = AnimationUtils.currentAnimationTimeMillis(); mStartX = startX; //滑动的起点x坐标 mStartY = startY; //滑动的起点y坐标 mFinalX = startX + dx; //滑动的终点x坐标 mFinalY = startY + dy; //滑动的终点y坐标 mDeltaX = dx; //x的滑动距离 mDeltaY = dy; //y的滑动距离 mDurationReciprocal = 1.0f / (float) mDuration; }
可以从代码中看出,仅仅调用startScroll方法是无法让View滑动的,因为它内部并没有做滑动相关的事,那么Scroller到底是如何让View弹性滑动的呢?答案是startScroll方法下面的invalidate方法。invalidate方法会导致View的重绘,在View的draw方法中又会去调用computeScroll方法,computeScroll方法在View中是一个空实现,因此需要我们自己去实现,上面的代码实现了computeScroll方法。具体过程如下:当View重绘后再draw方法中调用computeScroll,而computeScroll又会去向Scroller获取当前的scrollX和scrollY;然后通过scrollTo方法实现滑动;接着又调用postInvalidate方法来进行第二次重绘,这一次重绘的过程和第一次重绘一样,还是会导致computeScroll方法被调用;然后继续向Scroller获取当前的scrollX和scrollY,并通过scrollTo方法滑动到新的位置,如此反复,直到整个滑动过程结束。
我们再来看一下Scroller的computeScrollOffset方法的实现,源码如下:
public boolean computeScrollOffset() { if (mFinished) { return false; } int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime); if (timePassed < mDuration) { switch (mMode) { case SCROLL_MODE: final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal); mCurrX = mStartX + Math.round(x * mDeltaX); mCurrY = mStartY + Math.round(x * mDeltaY); break; .... } ... return true; } }
这个方法会根据时间流逝来计算当前的scrollX和scrollY的值。计算方法也很简单,大意就是根据时间流逝的百分比来计算出scrollX和scrollY改变的百分比并计算出当前的值,这个过程类似与动画中的插值器的概念,这里我们先不去深入探究这个过程。这个方法返回true表示滑动还未结束,返回false表示滑动已经结束,因此当这个方法返回true的时候我们要继续进行View的滑动。
2.2 通过动画
动画本身就是一种渐近的过程,因此通过它来实现的滑动天然就具有弹性效果,比如以下代码可以让一个View的内容再100ms内向左移动100像素。
ObjectAnimation.ofFloat(targetView,"translationX",0,100).setDuration(100).start()
我们可以利用动画的特性来实现一些动画不能实现的效果。还拿scrollTo来说,我们也想模仿Scroller来实现View的弹性滑动,那么利用动画的特性,我们可以采用如下方式来实现:
final int startX = 0;final int deltaX = 100;ValueAnimator animator = ValueAnimator.ofInt(0,1).setDuration(1000);animator.addUpdateListener(new AnimatorUpdateListener(){ @Override public void onAnimationUpdate(ValueAnimator animator){ float fraction = animator.getAnimatedFraction(); mButton1.scrollTo(startX+(int)(deltaX*fraction),0); }});animator.start();
上述代码中,我们的动画本质上没有作用于任何对象上,它只是在1000ms内完成了整个动画过程。利用这个特性,我们就可以在动画的每一帧到来时获取完成的比例,然后再根据这个比例计算当前View所要滑动的距离。注意,这里的滑动针对的是VIew的内容而非View本身。可以发现,这个方法的思想其实和Scroller比较类似,都是通过改变一个百分比配合scrollTo方法来完成View的滑动。
3.View的滑动冲突处理
3.1常见的滑动冲突场景
3.1.1 外部滑动方向和内部滑动的方向不一致
这种情况我们经常遇见,比如使用viewpager+listview时,在这种效果中,可以通过左右滑动切换页面,而每一个页面往往又是一个listview,本来在这种情况下是有冲突的,但是viewpager内部处理了这个滑动冲突,因此采用viewpager我们无需关注这个问题,如果我们采用的不是viewpager而是ScrollView等,那么必须手动处理滑动冲突,否则内外两层只能有一层滑动,那就是滑动冲突。另外内部左右滑动,外部上下滑动也同样属于该类。
3.1.2 外部滑动方向和内部滑动方向一致
这种情况就比较复杂,当内外两层都在同一个方向可以滑动的时候,显然存在逻辑问题,因为当手指开始滑动的时候,系统无法知道用户到底是想让那一层动,所以当手指滑动的时候就会出现问题,要么只能一层动,要么内外两成动的都很卡顿。
3.2 滑动冲突的解决
3.2.1 外部拦截法
所谓的外部拦截法就是指点击事件都要先经过父容器的拦截处理,如果父容器需要此事件拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题,这种方法比较符合点击事件的分发机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可,这种方法的伪代码如下:
public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; break; } case MotionEvent.ACTION_MOVE: { if(父容器拦截的规则){ intercepted=true; }else{ intercepted=false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } mLastXIntercept=x; mLastYIntercept=y; return intercepted; }
上面的代码差多就是外部拦截的通用模板了,在onInterceptTouchEvent方法中,
首先是ACTION_DOWN这个事件,父容器必须返回false,即不拦截事件,因为一旦父容器拦截了ACTION_DOWN这个事件,那么后续的ACTION_MOVE和ACTION_UP事件将直接交给父容器处理,这个时候事件没法继续传递给子元素了;
然后是ACTION_MOVE这个事件,这个事件可以根据需要决定是否拦截,如果父容器需要拦截就返回true,否则返回false;
最后是ACTION_UP这个事件,这里必须返回false,因为这个事件本身也没有太多意义。
3.2.2 内部拦截法
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器去处理,这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显复杂。他的伪代码如下,我们需要重写子元素的dispatchTouchEvent方法:
public boolean onInterceptTouchEvent(MotionEvent event){ int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: parent.requestDisallowInterceptTouchEvent(true); //父布局不拦截此事件 break; case MotionEvent.ACTION_MOVE: int deltaX = x - mLastX; int deltaY = y - mLastY; if(父容器需要此类点击事件){ parent.requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: break; default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); }
以上是内部拦截法的典型代码,当面对不同滑动策略的时候只需要修改里面的条件即可,其他不需要做改动而且也不能有改动。除了子元素需要做处理之外,父元素也需要默认拦截除了ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisallowInterceptionTouchEvent(false)方法时,父元素才能继续拦截所需的事件。
- 四、界面编程(四) View的滑动及滑动冲突详解
- view 的滑动冲突
- View的滑动冲突
- View的滑动冲突
- View的滑动冲突
- View的滑动冲突
- View 的滑动冲突
- View的滑动冲突
- View 的滑动冲突
- View的滑动冲突
- View的滑动冲突
- View的滑动冲突
- View的滑动方式及冲突处理
- 四、界面编程(一) View的基础知识及架构详解
- Andorid 中TouchEvent理解(四)滑动冲突的解决
- 【Android View事件(四)】View滑动与实现滑动的几种方法
- View的滑动方式及滑动冲突解决方法(事件分发)
- Android View事件分发机制及View的滑动冲突
- 【Java学习】----如何映射JavaEE到MVC
- 老徐教你识破小程序各种骗局!
- 2017/11/27_周报
- 练习1-13 编写一个程序,打印输入中单词长度的垂直方向的直方图(垂直图)
- 编码解码原理
- 四、界面编程(四) View的滑动及滑动冲突详解
- Spring AOP 测试需要POM添加的jar包
- iOS11和iPhone X适配(这里有一大坑)
- Codeforces Round #448 (Div. 2) C. Square Subsets
- 8080端口被占用,Address already in use: JVM_Bind <null>:8080
- Okhttp 请求添加公共参数、公共Headers 方法
- 位运算
- 基础排序:冒泡排序
- vue项目中从服务器获取图片