最近在做项目,用到了下拉刷新,本来这个效果应该是再普通不过的一个效果,自己懒得去写,所以就去网上找,找了很多很多,发现效果都不是很理想,要么不支持多点触控,要么不支持自动加载更多,一直觉得QQ5.3的下拉刷新是很人性化了,所以一直想找这样的下拉刷新和自动加载更多,找了很久才发现一个和QQ5.3接近的效果,http://blog.csdn.net/zhongkejingwang/article/details/38868463,大家可以去看下他的博客,不过这个拿过来貌似有一些bug,自动加载更多会出现bug,而且不能自己控制是否还有更多数据。经过我的修改已经可以自己随意控制是否还有更多数据。
先上图:
下面看修改后的PullToRefreshLayout代码:
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
/**
* 如果不需要下拉刷新直接在canPullDown中返回false,这里的自动加载和下拉刷新没有冲突,通过增加在尾部的footerview实现自动加载,
* 所以在使用中不要再动footerview了
* 更多详解见博客http://blog.csdn.net/zhongkejingwang/article/details/38963177
* @author chenjing
*
*/
public class PullableListView extends ListView implements Pullable
{
public static final int INIT = 0;
public static final int LOADING = 1;
public static final int NO_MORE_DATA = 2;
private OnLoadListener mOnLoadListener;
private View mLoadmoreView;
private ImageView mLoadingView;
private TextView mStateTextView;
private int state = INIT;
private boolean canLoad = true;
private boolean autoLoad = true;
private boolean hasMoreData = true;
private AnimationDrawable mLoadAnim;
public PullableListView(Context context)
{
super(context);
init(context);
}
public PullableListView(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
public PullableListView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init(context);
}
private void init(Context context)
{
mLoadmoreView = LayoutInflater.from(context).inflate(R.layout.load_more,
null);
mLoadingView = (ImageView) mLoadmoreView.findViewById(R.id.loading_icon);
mLoadingView.setBackgroundResource(R.anim.loading_anim);
mLoadAnim = (AnimationDrawable) mLoadingView.getBackground();
mStateTextView = (TextView) mLoadmoreView.findViewById(R.id.loadstate_tv);
mLoadmoreView.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
//点击加载
if(state != LOADING && hasMoreData){
load();
}
}
});
addFooterView(mLoadmoreView, null, false);
}
/**
* 是否开启自动加载
* @param enable true启用,false禁用
*/
public void enableAutoLoad(boolean enable){
autoLoad = enable;
}
/**
* 是否显示加载更多
* @param v true显示,false不显示
*/
public void setLoadmoreVisible(boolean v){
if(v)
{
if (getFooterViewsCount() == 0) {
addFooterView(mLoadmoreView, null, false);
}
}
else {
removeFooterView(mLoadmoreView);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev)
{
switch (ev.getActionMasked())
{
case MotionEvent.ACTION_DOWN:
// 按下的时候禁止自动加载
canLoad = false;
break;
case MotionEvent.ACTION_UP:
// 松开手判断是否自动加载
canLoad = true;
checkLoad();
break;
}
return super.onTouchEvent(ev);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt)
{
super.onScrollChanged(l, t, oldl, oldt);
// 在滚动中判断是否满足自动加载条件
checkLoad();
}
/**
* 判断是否满足自动加载条件
*/
private void checkLoad()
{
if (reachBottom() && mOnLoadListener != null && state != LOADING
&& canLoad && autoLoad && hasMoreData)
load();
}
private void load(){
changeState(LOADING);
mOnLoadListener.onLoad(this);
}
private void changeState(int state)
{
this.state = state;
switch (state)
{
case INIT:
mLoadAnim.stop();
mLoadingView.setVisibility(View.INVISIBLE);
mStateTextView.setText(R.string.more);
break;
case LOADING:
mLoadingView.setVisibility(View.VISIBLE);
mLoadAnim.start();
mStateTextView.setText(R.string.loading);
break;
case NO_MORE_DATA:
mLoadAnim.stop();
mLoadingView.setVisibility(View.INVISIBLE);
mStateTextView.setText("没有更多的数据了");
break;
}
}
/**
* 完成加载
*/
public void finishLoading()
{
changeState(INIT);
}
@Override
public boolean canPullDown()
{
if (getCount() == 0)
{
// 没有item的时候也可以下拉刷新
return true;
} else if (getFirstVisiblePosition() == 0
&& getChildAt(0).getTop() >= 0)
{
// 滑到ListView的顶部了
return true;
} else
return false;
}
public void setOnLoadListener(OnLoadListener listener)
{
this.mOnLoadListener = listener;
}
/**
* @return footerview可见时返回true,否则返回false
*/
private boolean reachBottom()
{
if (getCount() == 0)
{
return true;
} else if (getLastVisiblePosition() == (getCount() - 1))
{
// 滑到底部,且頂部不是第0个,也就是说item数超过一屏后才能自动加载,否则只能点击加载
if (getChildAt(getLastVisiblePosition() - getFirstVisiblePosition()) != null
&& getChildAt(
getLastVisiblePosition()
- getFirstVisiblePosition()).getTop() < getMeasuredHeight() && !canPullDown())
return true;
}
return false;
}
public boolean isHasMoreData() {
return hasMoreData;
}
public void setHasMoreData(boolean hasMoreData) {
this.hasMoreData = hasMoreData;
if (!hasMoreData) {
changeState(NO_MORE_DATA);
}else{
changeState(INIT);
}
}
public interface OnLoadListener
{
void onLoad(PullableListView pullableListView);
}
@Override
public boolean canPullUp() {
// TODO Auto-generated method stub
return false;
}
}
用法也很简单:
pullToRefreshLayout.refreshFinish(PullToRefreshLayout.SUCCEED);
listView.setHasMoreData(true);//是否有更多数据
加载完成后自己设置true或者false就可以了!
后来又发现QQ5.3每一个界面貌似都可以下拉回弹,这样的设计很人性化,模仿的IOS的下拉回弹橡皮筋效果,于是自己又借助上面的代码修改了下做成了和QQ一样的效果:OverScrollView
/**
* 更多详解见博客http://blog.csdn.net/zhongkejingwang/article/details/38868463
*
* @author 陈靖
*/
public class OverScrollView extends ScrollView {
public static final String TAG = "PullToRefreshLayout";
//触发事件的高度默认阀值
private static final int TRIGGER_HEIGHT = 120;
//滑动的总距离
private float overScrollDistance;
//触发事件的高度阀值,最小值为30
private int mOverScrollTrigger = TRIGGER_HEIGHT;
private OverScrollTinyListener mOverScrollTinyListener;
private OverScrollListener mOverScrollListener;
// 按下Y坐标,上一个事件点Y坐标
private float downY, lastY;
// 下拉的距离。注意:pullDownY和pullUpY不可能同时不为0
public float pullDownY = 0;
// 上拉的距离
private float pullUpY = 0;
private MyTimer timer;
// 回滚速度
public float MOVE_SPEED = 8;
// 第一次执行布局
private boolean isLayout = false;
// 手指滑动距离与下拉头的滑动距离比,中间会随正切函数变化
private float radio = 2;
// 实现了Pullable接口的View
private View pullableView;
// 过滤多点触碰
private int mEvents;
// 这两个变量用来控制pull的方向,如果不加控制,当情况满足可上拉又可下拉时没法下拉
private boolean canPullDown = true;
private boolean canPullUp = true;
/**
* 执行自动回滚的handler
*/
Handler updateHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 回弹速度随下拉距离moveDeltaY增大而增大
MOVE_SPEED = (float) (5 + 15 * Math.tan(Math.PI / 2
/ getMeasuredHeight() * (pullDownY + Math.abs(pullUpY))));
if (pullDownY > 0)
pullDownY -= MOVE_SPEED;
else if (pullUpY < 0)
pullUpY += MOVE_SPEED;
if (pullDownY < 0) {
// 已完成回弹
pullDownY = 0;
timer.cancel();
}
if (pullUpY > 0) {
// 已完成回弹
pullUpY = 0;
timer.cancel();
}
// 刷新布局,会自动调用onLayout
requestLayout();
}
};
public OverScrollView(Context context) {
super(context);
initView(context);
}
public OverScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public OverScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context);
}
private void initView(Context context) {
timer = new MyTimer(updateHandler);
}
private void hide() {
timer.schedule(5);
}
/**
* 不限制上拉或下拉
*/
private void releasePull() {
canPullDown = true;
canPullUp = true;
}
/*
* (非 Javadoc)由父控件决定是否分发事件,防止事件冲突
*
* @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent)
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
downY = ev.getY();
lastY = downY;
timer.cancel();
mEvents = 0;
releasePull();
break;
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_POINTER_UP:
// 过滤多点触碰
mEvents = -1;
break;
case MotionEvent.ACTION_MOVE:
float deltaY = ev.getY() - lastY;
if (mEvents == 0) {
if (canPullDown && isCanPullDown()) {
// 可以下拉,正在加载时不能下拉
// 对实际滑动距离做缩小,造成用力拉的感觉
pullDownY = pullDownY + deltaY / radio;
if (ev.getY() - lastY < 0) {
pullDownY = pullDownY + deltaY ;
}
if (pullDownY < 0) {
pullDownY = 0;
canPullDown = false;
canPullUp = true;
}
if (pullDownY > getMeasuredHeight())
pullDownY = getMeasuredHeight();
overScrollDistance = pullDownY;
} else if (canPullUp && isCanPullUp()) {
// 可以上拉,正在刷新时不能上拉
pullUpY = pullUpY + deltaY / radio;
if (ev.getY() - lastY > 0) {
pullUpY = pullUpY + deltaY ;
}
if (pullUpY > 0) {
pullUpY = 0;
canPullDown = true;
canPullUp = false;
}
if (pullUpY < -getMeasuredHeight())
pullUpY = -getMeasuredHeight();
overScrollDistance = pullUpY;
} else
releasePull();
} else
mEvents = 0;
lastY = ev.getY();
// 根据下拉距离改变比例
radio = (float) (2 + 3 * Math.tan(Math.PI / 2 / getMeasuredHeight()
* (pullDownY + Math.abs(pullUpY))));
requestLayout();
// 因为刷新和加载操作不能同时进行,所以pullDownY和pullUpY不会同时不为0,因此这里用(pullDownY +
// Math.abs(pullUpY))就可以不对当前状态作区分了
if ((pullDownY + Math.abs(pullUpY)) > 8) {
// 防止下拉过程中误触发长按事件和点击事件
ev.setAction(MotionEvent.ACTION_CANCEL);
}
if(mOverScrollTinyListener != null){
mOverScrollTinyListener.scrollDistance((int)deltaY, (int)overScrollDistance);
}
break;
case MotionEvent.ACTION_UP:
hide();
overScrollTrigger();
if(mOverScrollTinyListener != null && (isCanPullDown() || isCanPullUp())){
mOverScrollTinyListener.scrollLoosen();
}
break;
case MotionEvent.ACTION_CANCEL:
if(mOverScrollTinyListener != null && (isCanPullDown() || isCanPullUp())){
mOverScrollTinyListener.scrollLoosen();
}
break;
default:
break;
}
// 事件分发交给父类
try {
super.dispatchTouchEvent(ev);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!isLayout) {
// 这里是第一次进来的时候做一些初始化
pullableView = getChildAt(0);
isLayout = true;
}
pullableView.layout(0, (int) (pullDownY + pullUpY),
pullableView.getMeasuredWidth(), (int) (pullDownY + pullUpY)
+ pullableView.getMeasuredHeight());
}
class MyTimer {
private Handler handler;
private Timer timer;
private MyTask mTask;
public MyTimer(Handler handler) {
this.handler = handler;
timer = new Timer();
}
public void schedule(long period) {
if (mTask != null) {
mTask.cancel();
mTask = null;
}
mTask = new MyTask(handler);
timer.schedule(mTask, 0, period);
}
public void cancel() {
if (mTask != null) {
mTask.cancel();
mTask = null;
}
}
class MyTask extends TimerTask {
private Handler handler;
public MyTask(Handler handler) {
this.handler = handler;
}
@Override
public void run() {
handler.obtainMessage().sendToTarget();
}
}
}
/**
* 判断是否滚动到顶部
*/
private boolean isCanPullDown() {
return getScrollY() == 0 ||
pullableView.getHeight() < getHeight() + getScrollY();
}
/**
* 判断是否滚动到底部
*/
private boolean isCanPullUp() {
return pullableView.getHeight() <= getHeight() + getScrollY();
}
private boolean isOnTop(){
return getScrollY() == 0;
}
private boolean isOnBottom(){
return getScrollY() + getHeight() == pullableView.getHeight();
}
/**
* 当OverScroll超出一定值时,调用此监听
*
* @author King
* @since 2014-4-9 下午4:36:29
*/
public interface OverScrollListener {
/**
* 顶部
*/
void headerScroll();
/**
* 底部
*/
void footerScroll();
}
/**
* 每当OverScroll时,都能触发的监听
* @author King
* @since 2014-4-9 下午4:39:06
*/
public interface OverScrollTinyListener{
/**
* 滚动距离
* @param tinyDistance 当前滚动的细小距离
* @param totalDistance 滚动的总距离
*/
void scrollDistance(int tinyDistance, int totalDistance);
/**
* 滚动松开
*/
void scrollLoosen();
}
/**
* 设置OverScrollListener出发阀值
* @param height
*/
public void setOverScrollTrigger(int height){
if(height >= 30){
mOverScrollTrigger = height;
}
}
private void overScrollTrigger(){
if(mOverScrollListener == null){
return;
}
if(overScrollDistance > mOverScrollTrigger && overScrollDistance >= 0){
mOverScrollListener.headerScroll();
}
if(overScrollDistance < -mOverScrollTrigger && overScrollDistance < 0){
mOverScrollListener.footerScroll();
}
}
public OverScrollTinyListener getOverScrollTinyListener() {
return mOverScrollTinyListener;
}
public void setOverScrollTinyListener(OverScrollTinyListener OverScrollTinyListener) {
this.mOverScrollTinyListener = OverScrollTinyListener;
}
public OverScrollListener getOverScrollListener() {
return mOverScrollListener;
}
public void setOverScrollListener(OverScrollListener OverScrollListener) {
this.mOverScrollListener = OverScrollListener;
}
}
上面注意的是我加了两个接口函数,一个是可以在每次滑动的时候触发,一个只是在头部和尾部的时候触发。
其实利用这两个接口就可以实现QQ空间,或者新浪个人中心那个下拉刷新头像伸缩的效果。
上效果:
用法很简单和Scrollview一样,Scollview怎么用这个就怎么用,把Scrollview换成OverScollview就可以了
OverScrollView由于继承了ScrollView那么自然里面不能嵌套listview和其ExpandListView,如果非要嵌套用,那也可以,使你的listview或者expandListview继承
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ExpandableListView;
import android.widget.ListView;
public class CustomerListView extends ListView {
public CustomerListView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
这个就可以了。写的比较粗糙,有啥问题请留言!
好了最后提供下demo:
http://download.csdn.net/detail/wondaymh/8314885