由于在日常开发中,我们经常要用到下拉刷新组件,像
QQ
刷新好友那样的功能,或者像人人网那样左侧可以滑出一个菜单,于是之前在网上找了两个例子,
当然是别人写的代码,在下还只是菜鸟一枚,只是下载的时间久了,没办法贴出原帖的地址了,如果原作者看到了也可以联系我或者帮我加上原帖的出处,这里先谢过那些无私奉献的高手们
。两个
DEMO
已做为附件上传。
前段时间一直在赶项目没什么时间,今天特意想到对之前用到的一些组件进行总结,之所以挑这两个组件,是因为这两个组件有许多共同之处,比如都是自定义组件,都是要求某一个组件根据用户的手指进行滑动,手指抬起之后都要自动滚动一段距离,然而不同的人有不同的编程风格,两个组件在一些实现细节上还是有许多不同之处,再就是对于我这样的初学者来说,这些代码还是很有难度的,至少我自己写不出来,看过几遍还是难以完全消化,所以以下对这两个组件写一些自己的理解,如有不妥之处欢迎各位指出。也希望能帮到那些跟我有一样困惑的朋友。
一、PullDown ( HeadrView 、 ListView 垂直排列于一个 LinearLayout 中,重写 ListView 的 onTouchEvent 方法)
1、手指滑动阶段 (核心思想: getRawY 的值不断相减算差值,再与 HeaderView 的 layoutParams.height 进行加减运算):
(1) action_down 时获取 getRawY() 的值,记录;
(2) action_move 时不断用当前的 getRawY 的值减去 down 时的 getRawY 的值,做为差值 deltaY ,减完后还要将当前的 y 值做为最后的 y 值,下次的 getRawY 做差值是跟记录下的最新的 getRawY 做差值,而不再是 down 时的 getRawY 的值;
(3) 用 HeaderView 的 layoutParams.height 加上或减去 deltaY ,再对 HeaderView 进行 setLayoutParams 。
2、自动滚动阶段 (没有使用 ScrollTo 方法,还是跟上边一样用 layoutParams.height 与 deltaY 进行加减运算,只不过 deltaY 由 Timer 和 TimerTask 自动生成):
(1) 当检测到 UP 时先 new 一个 Timer 类实例;
(2) 用 timer 实例每 10 毫秒 new 一个 TimerTask 类实例(因为 Timer 类的方法中要求传入你定义的 TimerTask 实例(其实 TimerTask 是 Runnable 接口的实现类,里边有 run 方法,每 new 一次 run 方法就会被调用一次),其实本质上就是每 10 毫秒执行一次你定义的方法);
(3) 接下来就是在 TimerTask 中写你定义的方法了,在 run 体中写,注意这个 run 体运行在子线程,表面上看不出来,所以更新 UI 的操作要放在主线程当中,具体怎么做我想不必我细说了,用 handler 向主线程发送消息就可以了;那么具体做法就是 HeaderView 的 layoutParams.height 减 10 个像素,由于这里的 run 方法会每 10 毫秒调用一次,所以每 10 毫秒高度减 10 ,就形成类似于动画的效果了;
(4) 当发现已经滚动到我们想要的位置了,要调用 TimerTask 的 cancel() 方法取消 Timer 的时间表。
二、SliddingMenu ( MenuView 、 ContentView 重叠放到一个 RelativeLayout 中,重写 ContentView 的 onTouchEvent 事件)
1、手指滑动阶段 (核心思想: event.getX() 与 getScrollX 运算后的值做为最终的滚动位置传入 ViewGroup 的 scrollTo() 方法中):
(1) action_down 时记录 getX() 的值;
(2) action_move 时用当前的 getX 减去 down 时记录的 x ,并用当前的 x 替换 down 时的 x ,以便下次减时用 getX 减这次记录的 x ,做为 deltaX ;
(3) 紧接着还是在 action_move 中,用 ViewGroup 的 getScrollX+deltaX 做为组件当前的 X 值;
(4) 用 ViewGroup 的 scrollTo() 方法将其子 View 滑动到当前 X 位置(注意是子 View ,其内部的直接子 View 是个 FrameLayout ,而整个 ContentView 其实不动,这也是为什么 ContentView 要继承 ViewGroup 的原因之一)。
2、自动滚动阶段 (核心思想:算出要滚动的距离,用 Scroller 组件生成要滚动到的位置,还是传入 scrollTo 方法):
(1) 当检测到 UP 事件时开始自动滚动;
(2) 用 -MENU_WIDTH 减去 getScrollX 做为要滚动的距离 dx (或者直接就是 getScrollX ,取决于过没过 MENU 的中线,这里要注意 getScrollX 的值的正负,屏幕左边界还往左是正,屏幕左边界往右获取到的是负值);
(3) 定义一个 Scroller 实例,将前两个参数(起始 x,y )设置为当前的 getScrollX,getScrollY ,第三、第四个参数是要滚动的距离(注意不是滚动的终点,而是相对于起始的距离)设置为 dx 和 getScrollY ,这里由于不涉及纵向滚动,所以第四个参数设成什么都没关系,其实 getScrollY 一直是 0 ;
(4) 重写 ViewGroup 的 computeScroll() 方法(其实这个方法是 View 的),在这个方法中使用刚才定义好的 scroller ,如果 scroller 还在滚动,则获取 scroller 当前的 x,y 值,传入到 scrollTo() 方法中,将子 View 滑动到 scoller 的当前位置(注意 scoller 的起点是刚才 UP 时的 getScrollX,getScollY 的值)。
三、对比以上两个组件的实现方式
相同点是:
(1) 都重写了 ViewGroup 的 onTouchEvent 方法,都对 down 、 move 、 up 三个事件进行监听;
(2) 都是在某一个子 View 中重写 onTouchEvent 方法,而不是在父容器中重写 onTouchEvent ,这一点很重要。例如在下拉刷新组件中,一个 LinearLayout 内部垂直放了 HeaderView 和 ListView ,但并没有在 LinearLayout 中重写 onTouchEvent ,而是在 ListView 中重写 onTouchEvent ,这样可以在很大程度上减少一个父容器中各个子 View 之间的触摸冲突,也可以减少父容器与子 View 的触摸冲突,当然完全避免是不大可能的;侧滑组件也是这样,一个 RelativeLayout 中放了两个 ViewGroup ,分别是 MenuView 和 ContentView ,却只在 ContentView 中重写 onTouchEvent 事件,实际上在常规方式下也只有 ContentView 在动,除非有特殊要求。
不同点是:
(1) 第一个组件用 gerRawY ,获取的是该组件相对于屏幕左上角的位置,第二个组件用 getX ,获取的是相对其父容器左上角的位置;
(2) 在实现组件跟随手指滑动的效果上,第一次组件是不断改变 LinearLayout 中第一个组件的高度,那么下边的组件自然就被“顶”下来或“挤”上去;而第二个组件用的是 ViewGroup 自带的 scrollTo 方法去挪动它子 View 的位置;
(3) 在手指离开屏幕实现自动移动效果上,第一个组件用了线程,开启子线程,每 10 毫秒使 LinearLayout 的第一个组件的高度减 10 ,实现了类似于动画的效果;而第二个组件则是另外一种思想,用一个现成的 Scroller 组件去 scrollTo 子 View ,也是实现了类似动画的效果。
以上两个组件都想实现动画,却都没有使用 animation ,一个用 Timer+TimerTask 的线程思想,一个用 scroller+scrollTo ,通过逐渐改变组件坐标的思想去实现。
前段时间一直在赶项目没什么时间,今天特意想到对之前用到的一些组件进行总结,之所以挑这两个组件,是因为这两个组件有许多共同之处,比如都是自定义组件,都是要求某一个组件根据用户的手指进行滑动,手指抬起之后都要自动滚动一段距离,然而不同的人有不同的编程风格,两个组件在一些实现细节上还是有许多不同之处,再就是对于我这样的初学者来说,这些代码还是很有难度的,至少我自己写不出来,看过几遍还是难以完全消化,所以以下对这两个组件写一些自己的理解,如有不妥之处欢迎各位指出。也希望能帮到那些跟我有一样困惑的朋友。
一、PullDown ( HeadrView 、 ListView 垂直排列于一个 LinearLayout 中,重写 ListView 的 onTouchEvent 方法)
1、手指滑动阶段 (核心思想: getRawY 的值不断相减算差值,再与 HeaderView 的 layoutParams.height 进行加减运算):
(1) action_down 时获取 getRawY() 的值,记录;
(2) action_move 时不断用当前的 getRawY 的值减去 down 时的 getRawY 的值,做为差值 deltaY ,减完后还要将当前的 y 值做为最后的 y 值,下次的 getRawY 做差值是跟记录下的最新的 getRawY 做差值,而不再是 down 时的 getRawY 的值;
(3) 用 HeaderView 的 layoutParams.height 加上或减去 deltaY ,再对 HeaderView 进行 setLayoutParams 。
2、自动滚动阶段 (没有使用 ScrollTo 方法,还是跟上边一样用 layoutParams.height 与 deltaY 进行加减运算,只不过 deltaY 由 Timer 和 TimerTask 自动生成):
(1) 当检测到 UP 时先 new 一个 Timer 类实例;
(2) 用 timer 实例每 10 毫秒 new 一个 TimerTask 类实例(因为 Timer 类的方法中要求传入你定义的 TimerTask 实例(其实 TimerTask 是 Runnable 接口的实现类,里边有 run 方法,每 new 一次 run 方法就会被调用一次),其实本质上就是每 10 毫秒执行一次你定义的方法);
(3) 接下来就是在 TimerTask 中写你定义的方法了,在 run 体中写,注意这个 run 体运行在子线程,表面上看不出来,所以更新 UI 的操作要放在主线程当中,具体怎么做我想不必我细说了,用 handler 向主线程发送消息就可以了;那么具体做法就是 HeaderView 的 layoutParams.height 减 10 个像素,由于这里的 run 方法会每 10 毫秒调用一次,所以每 10 毫秒高度减 10 ,就形成类似于动画的效果了;
(4) 当发现已经滚动到我们想要的位置了,要调用 TimerTask 的 cancel() 方法取消 Timer 的时间表。
二、SliddingMenu ( MenuView 、 ContentView 重叠放到一个 RelativeLayout 中,重写 ContentView 的 onTouchEvent 事件)
1、手指滑动阶段 (核心思想: event.getX() 与 getScrollX 运算后的值做为最终的滚动位置传入 ViewGroup 的 scrollTo() 方法中):
(1) action_down 时记录 getX() 的值;
(2) action_move 时用当前的 getX 减去 down 时记录的 x ,并用当前的 x 替换 down 时的 x ,以便下次减时用 getX 减这次记录的 x ,做为 deltaX ;
(3) 紧接着还是在 action_move 中,用 ViewGroup 的 getScrollX+deltaX 做为组件当前的 X 值;
(4) 用 ViewGroup 的 scrollTo() 方法将其子 View 滑动到当前 X 位置(注意是子 View ,其内部的直接子 View 是个 FrameLayout ,而整个 ContentView 其实不动,这也是为什么 ContentView 要继承 ViewGroup 的原因之一)。
2、自动滚动阶段 (核心思想:算出要滚动的距离,用 Scroller 组件生成要滚动到的位置,还是传入 scrollTo 方法):
(1) 当检测到 UP 事件时开始自动滚动;
(2) 用 -MENU_WIDTH 减去 getScrollX 做为要滚动的距离 dx (或者直接就是 getScrollX ,取决于过没过 MENU 的中线,这里要注意 getScrollX 的值的正负,屏幕左边界还往左是正,屏幕左边界往右获取到的是负值);
(3) 定义一个 Scroller 实例,将前两个参数(起始 x,y )设置为当前的 getScrollX,getScrollY ,第三、第四个参数是要滚动的距离(注意不是滚动的终点,而是相对于起始的距离)设置为 dx 和 getScrollY ,这里由于不涉及纵向滚动,所以第四个参数设成什么都没关系,其实 getScrollY 一直是 0 ;
(4) 重写 ViewGroup 的 computeScroll() 方法(其实这个方法是 View 的),在这个方法中使用刚才定义好的 scroller ,如果 scroller 还在滚动,则获取 scroller 当前的 x,y 值,传入到 scrollTo() 方法中,将子 View 滑动到 scoller 的当前位置(注意 scoller 的起点是刚才 UP 时的 getScrollX,getScollY 的值)。
三、对比以上两个组件的实现方式
相同点是:
(1) 都重写了 ViewGroup 的 onTouchEvent 方法,都对 down 、 move 、 up 三个事件进行监听;
(2) 都是在某一个子 View 中重写 onTouchEvent 方法,而不是在父容器中重写 onTouchEvent ,这一点很重要。例如在下拉刷新组件中,一个 LinearLayout 内部垂直放了 HeaderView 和 ListView ,但并没有在 LinearLayout 中重写 onTouchEvent ,而是在 ListView 中重写 onTouchEvent ,这样可以在很大程度上减少一个父容器中各个子 View 之间的触摸冲突,也可以减少父容器与子 View 的触摸冲突,当然完全避免是不大可能的;侧滑组件也是这样,一个 RelativeLayout 中放了两个 ViewGroup ,分别是 MenuView 和 ContentView ,却只在 ContentView 中重写 onTouchEvent 事件,实际上在常规方式下也只有 ContentView 在动,除非有特殊要求。
不同点是:
(1) 第一个组件用 gerRawY ,获取的是该组件相对于屏幕左上角的位置,第二个组件用 getX ,获取的是相对其父容器左上角的位置;
(2) 在实现组件跟随手指滑动的效果上,第一次组件是不断改变 LinearLayout 中第一个组件的高度,那么下边的组件自然就被“顶”下来或“挤”上去;而第二个组件用的是 ViewGroup 自带的 scrollTo 方法去挪动它子 View 的位置;
(3) 在手指离开屏幕实现自动移动效果上,第一个组件用了线程,开启子线程,每 10 毫秒使 LinearLayout 的第一个组件的高度减 10 ,实现了类似于动画的效果;而第二个组件则是另外一种思想,用一个现成的 Scroller 组件去 scrollTo 子 View ,也是实现了类似动画的效果。
以上两个组件都想实现动画,却都没有使用 animation ,一个用 Timer+TimerTask 的线程思想,一个用 scroller+scrollTo ,通过逐渐改变组件坐标的思想去实现。