您的位置:首页 > 移动开发 > Android开发

一个简易的下拉刷新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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 下拉刷新