下拉刷新组件的简单原理
基本介绍
一个典型的下拉刷新界面如上,对于下拉刷新功能而言,界面主要包含两个部分,一个是展示Refresh界面的部分,一个是展示如ListView之类列表的部分。为了实现下拉刷新功能,我们所需要的就是自定义一个ViewGroup。我们的RefreshLayout中包含两个子View,header和content。header界面如下:
content可以是ListView,同样也是一个ViewGroup。界面初始时由于header和content都可以看到,所以我们在RefreshLayout的onLayout方法结束前,调用scrollTo(0,headerHeight)可以将header滑动出界面。然后,总的思路就是分析RefreshLayout和ListView对于一个触摸事件,谁来拦截谁来处理的问题。
RefreshLayout实现:
RefreshLayout绘制过程:
首先通过 LayoutInflater.from(context).inflate以及addView方法,在RefreshLayout构造函数中向布局添加header和content。对于一个ViewGroup而言,绘制过程中最重要的是onMeasure和onLayout方法。
onMeasure
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = MeasureSpec.getSize(widthMeasureSpec);int height = 0;for(int i=0;i<getChildCount();i++) { measureChild(getChildAt(i),widthMeasureSpec,heightMeasureSpec); height += getChildAt(i).getMeasuredHeight();}height = heightMeasureSpec;setMeasuredDimension(width,height); }onMeasure方法中,一定要对全部子View进行measure,在这里调用的是measureChild方法,因为measureChild内部还会根据子View的LayoutParams进一步封装出MeasureSpec进行测量。
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {int count = getChildCount();int left =getPaddingLeft();Log.d("TAG", l + " " + t + " " + r + " " + b);int top = getPaddingTop();for(int i=0;i<count;i++) { View child = getChildAt(i); child.layout(left,top,child.getMeasuredWidth(),child.getMeasuredHeight() + top); Log.d("TAG", "child: " + child.getMeasuredWidth() + " " + child.getMeasuredHeight()); top += child.getMeasuredHeight();}if(!init){ //将ViewGroup向y轴正方向移动,其实相当于将View向y轴负方向移动 scrollTo(0,mHeaderHeight+getPaddingTop()); invalidate(); init = true;} }onLayout方法中进行我们想要的布局,注意由于重新绘制时,onMeasure和onLayout会多次被调用,所以要注意一些初始化方法的执行。
@Override public boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()) { case MotionEvent.ACTION_DOWN:prevY = (int) ev.getRawY();break; case MotionEvent.ACTION_MOVE:int delY = (int) (ev.getRawY() - prevY);Log.d("TAG", "delY " + delY);if(delY>0) { return true;}break;}return false; }在拦截事件中,只做了一个简单的判断,一旦滑动的纵向距离大于0,表明手指再从上向下滑,同时这里应该判断一下ListView中显示的第一条是不是全部数据中的第一条。然后拦截事件后交由onTouchEvent处理。
@Override public boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) { case MotionEvent.ACTION_MOVE:int dy = (int) (event.getRawY() - prevY);int sy = mHeaderHeight-dy;scrollTo(0,sy>0?sy:0);Log.d("TAG", "dy " + dy);break; case MotionEvent.ACTION_UP:refresh();break;}return true; }之前将ViewGroup向下滑动了headerHeight的距离,为了让header显示出来,其实应该让ViewGroup向上滑动也即y轴变小,同时为了避免过分滑动还要进行一下判断。当手指抬起时,还要根据移动的y轴增量判断一下是否是有效的滑动,然后处理响应的业务逻辑。注意的是,由于当前是主线程,所以要使用
new Thread(new Runnable() { @Override public void run() {mission();post(new Runnable() { @Override public void run() {mScroller.startScroll(getScrollX(),getScrollY(),0,mHeaderHeight+getPaddingTop(),3000);mArrowView.setVisibility(VISIBLE);mProgress.setVisibility(GONE); }}); }}).start();新起一个线程完成mission,同时通过当前ViewGroup的消息队列,在任务完成后修改UI。