一个简易的下拉刷新Layout
2016-04-13 21:55
555 查看
一个简易的下拉刷新Layout
以下是实现顺序-实现下拉时RefreshIcon能随着下拉的距离往下移动
-实现下拉时RefreshIcon能随着高度旋转
-解决与ListView、ScrollView的滑动冲突(项目中未引入RecyclerView)
-添加Refresh回调
转载请注明出处 http://blog.csdn.net/BalckSeven/article/details/51118534
【来自BlackSeven的CSDN博客】
前言
在前几年,google的support V4包就引入了SwipeRefreshLayout这个空间,那时候刷新的时候还是使用一个从中间向两边的进度条。随着android 5.0的发布,google也将其SwipeRefreshLayout改成了Meterail Design风格,即刷新时以一个旋转的圆形卡片提示用户正在刷新。刚好最近在学习touch事件的分发,于是就模仿了一个SwipeRefreshLayout,当然,做的比较粗糙,圆形的卡片也只是用一张图片代替,而不是像google一样动态绘制一个箭头 。这里不得不佩服一下google的工程师,真的厉害。致敬!这里我选择继承FrameLayout来实现SwipeRefreshLayout,因为FrameLayout相对简单一点,而且使用时我们只要将这个控件当作根布局然后嵌套LinearLayout、RelativeLayout等布局或者直接放ListView都是可以的。
1.实现下拉时RefreshIcon能随着下拉的距离往下移动
这个可以看我之前的博文【仿微信滑动退出】,通过记录action_down的坐标,和move时的坐标便可以求出滑动的距离,直接上代码@Override public boolean onTouchEvent(MotionEvent event) { if (isRefreshing) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: originY = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: int moveY = (int) event.getRawY() - originY; doDragging(moveY); break; case MotionEvent.ACTION_UP: moveY = (int) event.getRawY() - originY; releaseDrag(moveY); break; } return true; }
originY是一个记录action_down坐标的全局变量。
然后我们初始化一个ImageView作为RefreshIcon
private void initRefreshIcon() { refreshIcon = new ImageView(getContext()); refreshIcon.setImageResource(R.drawable.repeat); LayoutParams params = new LayoutParams(mIconSize, mIconSize); params.gravity = Gravity.CENTER_HORIZONTAL; params.setMargins(0, -mIconSize, 0, 0); refreshIcon.setLayoutParams(params); addView(refreshIcon); maxY = getHeight() / 4; }
从代码里不难看出这是一个正方形的ImageView,maxY是用来限制icon可下拉的最大距离的。然后把这个imageView的marginTop设置成负值,它就缩到屏幕外面去了。
再看看doDragging的实现
private void doDragging(int distance) { if (refreshIcon == null) { initRefreshIcon(); } if (isRefreshing) { return; } if (distance < maxY) { setIconPosition(distance - mIconSize); } }
这里有三个判断,首先判断Icon是否初始化,如果未初始化则初始化,然后如果是正在刷新,那么就什么都不做处理,最后就是设置Icon的位置了.
为什么是distance-mIconSize?因为原来的margin是负的,所以这里就是做一个下拉时有那种从屏幕外拉进来的感觉。
2. 实现下拉时RefreshIcon能随着高度旋转
这个过渡主要在setIconPosition里面做了,还是先贴代码private void setIconPosition(int pos) { if (pos > maxY) { pos = maxY; } LayoutParams params = (LayoutParams) refreshIcon.getLayoutParams(); params.setMargins(0, pos, 0, 0); refreshIcon.setLayoutParams(params); float rotation = 360 * (float) (pos) / maxY; refreshIcon.setRotation(rotation); }
这里不像之前使用scrollTo和scrollBy来改变视图的位置,而是采用动态设置refreshIcon的marginTop来给人向下滑动的感觉,然后调用View的setRotation来设置icon的旋转角度,从最顶端到最低端一共旋转360°,如果想旋转两圈可以把360设置成720,以此类推。
3. 解决与ListView、ScrollView的滑动冲突
重写onLayout@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); getAbsListView(this); } private void getAbsListView(ViewGroup vg) { if (vg instanceof AbsListView) { abslist = (AbsListView) vg; return; } for (int i = 0; i < vg.getChildCount(); ++i) { View v = vg.getChildAt(i); if (getChildAt(i) instanceof AbsListView) { abslist = (AbsListView) v; break; } else if (v instanceof ViewGroup) { if (viewGroup == null) { // 记录第一层的ViewGroup viewGroup = (ViewGroup) v; } getAbsListView((ViewGroup) v); } } }
在onLayout里遍历整个View树,直至找到AbsListView(ListView、GridView的父类),同时记录第一层的ViewGroup,用于拦截touch事件时的判断。
然后重写onInterceptTouchEvent,进行对touch事件的拦截。
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (isRefreshing) { return false; } boolean shouldIntercept = false; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // 不拦截down事件,记录位置 shouldIntercept = false; originY = (int) ev.getRawY(); break; case MotionEvent.ACTION_MOVE: int moveY = (int) ev.getRawY() - originY; if (isRefreshing) { shouldIntercept = false; } else { if (abslist != null) { if (abslist.getFirstVisiblePosition() == 0 && moveY > 0) { shouldIntercept = true; interceptMove = true; } else { shouldIntercept = false; } } else if (viewGroup != null) { if (viewGroup.getScrollY() == 0 && moveY > 0) { shouldIntercept = true; interceptMove = true; } else { shouldIntercept = false; } } else { interceptMove = true; shouldIntercept = true; } } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (interceptMove) { shouldIntercept = true; interceptMove = false; } else { shouldIntercept = false; } break; } return shouldIntercept; }
在这里并不拦截action_down事件,将action_down交给子控件去处理,因为onInterceptTouchEvent如果拦截了action_down事件,将拦截不到后面的move和up,而是在onTouchEvent里处理这些事件。
在move里面我们有一系列的判断条件,先是判断整个View里面有没有listview,如果有,那么只有当listview滑动到顶部的时候才会去拦截这个事件。同理,对第一层ViewGroup也是这样处理。
那么为什么对ListView是getFirstVisiblePosition,而对viewgroup则是getScrollY呢?因为对于ListView的getScrollY,取得的值一直都是0。具体为什么可以百度一下。
4. 添加Refresh回调
定义一个接口public static interface OnRefreshListener { public void onRefresh(); }
之后选择在releaseDrag的时候调用接口的onRefresh即可。
private void releaseDrag(int distance) { int refreshHeight = maxY / 3 * 2; if (refreshAnimation == null || backAnimation == null) { initAnimation(); } if (distance > refreshHeight) {// 大于三分二高度就刷新 setIconPosition(refreshHeight); isRefreshing = true; refreshIcon.startAnimation(refreshAnimation); backAnimation.setIntValues(distance, refreshHeight); backAnimation.start(); if (listener != null) { listener.onRefresh(); } } else { backAnimation.setIntValues(distance, -mIconSize); backAnimation.start(); } }
当下拉超过一定的高度,这里设置成maxY的三分之二,使用一个ValueAnimation和一个RotateAnimation,让Icon回到maxY*2/3处,进行旋转。否则就弹回屏幕顶部。
那么怎么关掉知道旋转结束了呢?google采用的是当刷新结束后,用户手动setRefreshing(false),让它停止动画。这里同样采用这种方法,因为不知道什么时候刷新动作可以完成。
public void setRefreshing(boolean refreshing) { this.isRefreshing = refreshing; if (!refreshing) { setIconPosition(-mIconSize); refreshIcon.clearAnimation(); } }
至此整个下拉刷新的控件就做完了。
自定义属性就不加了(因为懒233333)
上效果图
录制效果不是很好,可以下源码下来看一下。这里是嵌入ListView进行测试的。
源码地址:SwipeRefreshLayout v1.0
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories