PullToRefreshListView上拉加载更多,下拉刷新
2016-06-01 16:27
435 查看
使用PullToRefreshListView实现上拉加载更多,下拉刷新。
源码如下:
首先在pullToRefresh包下创建PullableListView.java:
/pullToRefresh/PullableListView.java
在同包下创建PullToRefreshLayout.java
/pullToRefresh/PullToRefreshLayout.java
再在相同包下创建Pullable.java
public interface Pullable
{
boolean canPullDown();
boolean canPullUp();
}
布局中,创建如下xml文件:
res下创建anim,创建reverse_anim.xml:
res下anim中添加rotating.xml
旋转动画
在String.xml中添加如下内容:
在res下layout下创建load_more.xml:
列表下部加载更多时显示的布局
在res中layout下,创建refresh_head.xml:列表中,下拉刷新时头部显示的布局
至此所有的准备工作已经完成,所需要的资源文件已经创建完毕,在调用时,在布局中使用PullToRefreshListView或PullToRefrehGridView,包名+类名。在代码中findViewById找到控件之后需要设置一个监听才能完成上拉加载更多,下拉刷新的效果。如下:
其中,调用loadmoreFinish方法表示加载更多完成,同样refreshFinish表示刷新完成,通知视图回归原位。
源码如下:
首先在pullToRefresh包下创建PullableListView.java:
/pullToRefresh/PullableListView.java
import android.content.Context; import android.util.AttributeSet; import android.widget.ListView; public class PullableListView extends ListView implements Pullable { public PullableListView(Context context) { super(context); } public PullableListView(Context context, AttributeSet attrs) { super(context, attrs); } public PullableListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public boolean canPullDown() { if (getCount() == 0) { return true; } else if (getFirstVisiblePosition() == 0 && getChildAt(0).getTop() >= 0) { return true; } else return false; } public boolean canPullUp() { if (getCount() == 0) { return true; } else if (getLastVisiblePosition() == (getCount() - 1)) { if (getChildAt(getLastVisiblePosition() - getFirstVisiblePosition()) != null && getChildAt( getLastVisiblePosition() - getFirstVisiblePosition()).getBottom() <= getMeasuredHeight()) return true; } return false; } }
在同包下创建PullToRefreshLayout.java
/pullToRefresh/PullToRefreshLayout.java
import java.util.Timer; import java.util.TimerTask; import com.example.demo.R; import android.content.Context; import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.RelativeLayout; import android.widget.TextView; public class PullToRefreshLayout extends RelativeLayout { public static final String TAG = "PullToRefreshLayout"; public static final int INIT = 0; public static final int RELEASE_TO_REFRESH = 1; public static final int REFRESHING = 2; public static final int RELEASE_TO_LOAD = 3; public static final int LOADING = 4; public static final int DONE = 5; private int state = INIT; private OnRefreshListener mListener; public static final int SUCCEED = 0; public static final int FAIL = 1; private float downY, lastY; public float pullDownY = 0; private float pullUpY = 0; private float refreshDist = 200; private float loadmoreDist = 200; private MyTimer timer; public float MOVE_SPEED = 8; private boolean isLayout = false; private boolean isTouch = false; private float radio = 2; private RotateAnimation rotateAnimation; private RotateAnimation refreshingAnimation; private View refreshView; private View pullView; private View refreshingView; private View refreshStateImageView; private TextView refreshStateTextView; private View loadmoreView; private View pullUpView; private View loadingView; private View loadStateImageView; private TextView loadStateTextView; private View pullableView; private int mEvents; private boolean canPullDown = true; private boolean canPullUp = true; private Context mContext; Handler updateHandler = new Handler() { @Override public void handleMessage(Message msg) { MOVE_SPEED = (float) (8 + 5 * Math.tan(Math.PI / 2 / getMeasuredHeight() * (pullDownY + Math.abs(pullUpY)))); if (!isTouch) { if (state == REFRESHING && pullDownY <= refreshDist) { pullDownY = refreshDist; timer.cancel(); } else if (state == LOADING && -pullUpY <= loadmoreDist) { pullUpY = -loadmoreDist; timer.cancel(); } } if (pullDownY > 0) pullDownY -= MOVE_SPEED; else if (pullUpY < 0) pullUpY += MOVE_SPEED; if (pullDownY < 0) { pullDownY = 0; pullView.clearAnimation(); if (state != REFRESHING && state != LOADING) changeState(INIT); timer.cancel(); requestLayout(); } if (pullUpY > 0) { pullUpY = 0; pullUpView.clearAnimation(); if (state != REFRESHING && state != LOADING) changeState(INIT); timer.cancel(); requestLayout(); } Log.d("handle", "handle"); requestLayout(); if (pullDownY + Math.abs(pullUpY) == 0) timer.cancel(); } }; public void setOnRefreshListener(OnRefreshListener listener) { mListener = listener; } public PullToRefreshLayout(Context context) { super(context); initView(context); } public PullToRefreshLayout(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(context); } private void initView(Context context) { mContext = context; timer = new MyTimer(updateHandler); rotateAnimation = (RotateAnimation) AnimationUtils.loadAnimation( context, R.anim.reverse_anim); refreshingAnimation = (RotateAnimation) AnimationUtils.loadAnimation( context, R.anim.rotating); LinearInterpolator lir = new LinearInterpolator(); rotateAnimation.setInterpolator(lir); refreshingAnimation.setInterpolator(lir); } private void hide() { timer.schedule(5); } public void refreshFinish(int refreshResult) { refreshingView.clearAnimation(); refreshingView.setVisibility(View.GONE); switch (refreshResult) { case SUCCEED: refreshStateImageView.setVisibility(View.VISIBLE); refreshStateTextView.setText(R.string.refresh_succeed); refreshStateImageView .setBackgroundResource(R.drawable.refresh_succeed); break; case FAIL: default: refreshStateImageView.setVisibility(View.VISIBLE); refreshStateTextView.setText(R.string.refresh_fail); refreshStateImageView .setBackgroundResource(R.drawable.refresh_failed); break; } if (pullDownY > 0) { new Handler() { @Override public void handleMessage(Message msg) { changeState(DONE); hide(); } }.sendEmptyMessageDelayed(0, 1000); } else { changeState(DONE); hide(); } } public void loadmoreFinish(int refreshResult) { loadingView.clearAnimation(); loadingView.setVisibility(View.GONE); switch (refreshResult) { case SUCCEED: loadStateImageView.setVisibility(View.VISIBLE); loadStateTextView.setText(R.string.load_succeed); loadStateImageView.setBackgroundResource(R.drawable.load_succeed); break; case FAIL: default: loadStateImageView.setVisibility(View.VISIBLE); loadStateTextView.setText(R.string.load_fail); loadStateImageView.setBackgroundResource(R.drawable.load_failed); break; } if (pullUpY < 0) { new Handler() { @Override public void handleMessage(Message msg) { changeState(DONE); hide(); } }.sendEmptyMessageDelayed(0, 1000); } else { changeState(DONE); hide(); } } private void changeState(int to) { state = to; switch (state) { case INIT: refreshStateImageView.setVisibility(View.GONE); refreshStateTextView.setText(R.string.pull_to_refresh); pullView.clearAnimation(); pullView.setVisibility(View.VISIBLE); loadStateImageView.setVisibility(View.GONE); loadStateTextView.setText(R.string.pullup_to_load); pullUpView.clearAnimation(); pullUpView.setVisibility(View.VISIBLE); break; case RELEASE_TO_REFRESH: refreshStateTextView.setText(R.string.release_to_refresh); pullView.startAnimation(rotateAnimation); break; case REFRESHING: pullView.clearAnimation(); refreshingView.setVisibility(View.VISIBLE); pullView.setVisibility(View.INVISIBLE); refreshingView.startAnimation(refreshingAnimation); refreshStateTextView.setText(R.string.refreshing); break; case RELEASE_TO_LOAD: loadStateTextView.setText(R.string.release_to_load); pullUpView.startAnimation(rotateAnimation); break; case LOADING: pullUpView.clearAnimation(); loadingView.setVisibility(View.VISIBLE); pullUpView.setVisibility(View.INVISIBLE); loadingView.startAnimation(refreshingAnimation); loadStateTextView.setText(R.string.loading); break; case DONE: break; } } private void releasePull() { canPullDown = true; canPullUp = true; } @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: if (mEvents == 0) { if (pullDownY > 0 || (((Pullable) pullableView).canPullDown() && canPullDown && state != LOADING)) { pullDownY = pullDownY + (ev.getY() - lastY) / radio; if (pullDownY < 0) { pullDownY = 0; canPullDown = false; canPullUp = true; } if (pullDownY > getMeasuredHeight()) pullDownY = getMeasuredHeight(); if (state == REFRESHING) { isTouch = true; } } else if (pullUpY < 0 || (((Pullable) pullableView).canPullUp() && canPullUp && state != REFRESHING)) { pullUpY = pullUpY + (ev.getY() - lastY) / radio; if (pullUpY > 0) { pullUpY = 0; canPullDown = true; canPullUp = false; } if (pullUpY < -getMeasuredHeight()) pullUpY = -getMeasuredHeight(); if (state == LOADING) { isTouch = true; } } else releasePull(); } else mEvents = 0; lastY = ev.getY(); radio = (float) (2 + 2 * Math.tan(Math.PI / 2 / getMeasuredHeight() * (pullDownY + Math.abs(pullUpY)))); if (pullDownY > 0 || pullUpY < 0) requestLayout(); if (pullDownY > 0) { if (pullDownY <= refreshDist && (state == RELEASE_TO_REFRESH || state == DONE)) { changeState(INIT); } if (pullDownY >= refreshDist && state == INIT) { changeState(RELEASE_TO_REFRESH); } } else if (pullUpY < 0) { if (-pullUpY <= loadmoreDist && (state == RELEASE_TO_LOAD || state == DONE)) { changeState(INIT); } if (-pullUpY >= loadmoreDist && state == INIT) { changeState(RELEASE_TO_LOAD); } } if ((pullDownY + Math.abs(pullUpY)) > 8) { ev.setAction(MotionEvent.ACTION_CANCEL); } break; case MotionEvent.ACTION_UP: if (pullDownY > refreshDist || -pullUpY > loadmoreDist) { isTouch = false; } if (state == RELEASE_TO_REFRESH) { changeState(REFRESHING); if (mListener != null) mListener.onRefresh(this); } else if (state == RELEASE_TO_LOAD) { changeState(LOADING); if (mListener != null) mListener.onLoadMore(this); } hide(); default: break; } super.dispatchTouchEvent(ev); return true; } private class AutoRefreshAndLoadTask extends AsyncTask<Integer, Float, String> { @Override protected String doInBackground(Integer... params) { while (pullDownY < 4 / 3 * refreshDist) { pullDownY += MOVE_SPEED; publishProgress(pullDownY); try { Thread.sleep(params[0]); } catch (InterruptedException e) { e.printStackTrace(); } } return null; } @Override protected void onPostExecute(String result) { changeState(REFRESHING); if (mListener != null) mListener.onRefresh(PullToRefreshLayout.this); hide(); } @Override protected void onProgressUpdate(Float... values) { if (pullDownY > refreshDist) changeState(RELEASE_TO_REFRESH); requestLayout(); } } public void autoRefresh() { AutoRefreshAndLoadTask task = new AutoRefreshAndLoadTask(); task.execute(20); } public void autoLoad() { pullUpY = -loadmoreDist; requestLayout(); changeState(LOADING); if (mListener != null) mListener.onLoadMore(this); } private void initView() { pullView = refreshView.findViewById(R.id.pull_icon); refreshStateTextView = (TextView) refreshView .findViewById(R.id.state_tv); refreshingView = refreshView.findViewById(R.id.refreshing_icon); refreshStateImageView = refreshView.findViewById(R.id.state_iv); pullUpView = loadmoreView.findViewById(R.id.pullup_icon); loadStateTextView = (TextView) loadmoreView .findViewById(R.id.loadstate_tv); loadingView = loadmoreView.findViewById(R.id.loading_icon); loadStateImageView = loadmoreView.findViewById(R.id.loadstate_iv); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { Log.d("Test", "Test"); if (!isLayout) { refreshView = getChildAt(0); pullableView = getChildAt(1); loadmoreView = getChildAt(2); isLayout = true; initView(); refreshDist = ((ViewGroup) refreshView).getChildAt(0) .getMeasuredHeight(); loadmoreDist = ((ViewGroup) loadmoreView).getChildAt(0) .getMeasuredHeight(); } refreshView.layout(0, (int) (pullDownY + pullUpY) - refreshView.getMeasuredHeight(), refreshView.getMeasuredWidth(), (int) (pullDownY + pullUpY)); pullableView.layout(0, (int) (pullDownY + pullUpY), pullableView.getMeasuredWidth(), (int) (pullDownY + pullUpY) + pullableView.getMeasuredHeight()); loadmoreView.layout(0, (int) (pullDownY + pullUpY) + pullableView.getMeasuredHeight(), loadmoreView.getMeasuredWidth(), (int) (pullDownY + pullUpY) + pullableView.getMeasuredHeight() + loadmoreView.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(); } } } public interface OnRefreshListener { void onRefresh(PullToRefreshLayout pullToRefreshLayout); void onLoadMore(PullToRefreshLayout pullToRefreshLayout); } }
再在相同包下创建Pullable.java
public interface Pullable
{
boolean canPullDown();
boolean canPullUp();
}
布局中,创建如下xml文件:
res下创建anim,创建reverse_anim.xml:
/res/anim/reverse_anim.xml <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:duration="100" android:fillAfter="true" android:fromDegrees="0" android:pivotX="50%" android:pivotY="50%" android:repeatCount="0" android:toDegrees="180" > </rotate>
res下anim中添加rotating.xml
旋转动画
/res/anim/rotating.xml <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1500" android:fillAfter="true" android:fromDegrees="0" android:pivotX="50%" android:pivotY="50%" android:repeatCount="-1" android:toDegrees="360" > </rotate>
在String.xml中添加如下内容:
<string name="pull_to_refresh">下拉刷新</string> <string name="release_to_refresh">释放立即刷新</string> <string name="refreshing">正在刷新...</string> <string name="refresh_succeed">刷新成功</string> <string name="refresh_fail">刷新失败</string> <string name="pullup_to_load">上拉加载更多</string> <string name="release_to_load">释放立即加载</string> <string name="loading">正在加载...</string> <string name="load_succeed">加载成功</string> <string name="load_fail">加载失败</string>
在res下layout下创建load_more.xml:
列表下部加载更多时显示的布局
/res/layout/load_more.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/loadmore_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/gray" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:paddingBottom="20dp" android:paddingTop="20dp" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" > <ImageView android:id="@+id/pullup_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="60dp" android:background="@drawable/pullup_icon_big" /> <ImageView android:id="@+id/loading_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="60dp" android:background="@drawable/loading" android:visibility="gone" /> <TextView android:id="@+id/loadstate_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/pullup_to_load" android:textColor="@color/black" android:textSize="16sp" /> <ImageView android:id="@+id/loadstate_iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginRight="8dp" android:layout_toLeftOf="@id/loadstate_tv" android:visibility="gone" /> </RelativeLayout> </RelativeLayout> </RelativeLayout>
在res中layout下,创建refresh_head.xml:列表中,下拉刷新时头部显示的布局
/res/layout/refresh_head.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/head_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/light_blue" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:paddingBottom="20dp" android:paddingTop="20dp" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" > <ImageView android:id="@+id/pull_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="60dp" android:background="@drawable/pull_icon_big" /> <ImageView android:id="@+id/refreshing_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="60dp" android:background="@drawable/refreshing" android:visibility="gone" /> <TextView android:id="@+id/state_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/pull_to_refresh" android:textColor="@color/white" android:textSize="16sp" /> <ImageView android:id="@+id/state_iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginRight="8dp" android:layout_toLeftOf="@id/state_tv" android:visibility="gone" /> </RelativeLayout> </RelativeLayout> </RelativeLayout> <com.baway.pullToRefresh.PullToRefreshLayout android:id="@+id/layout" android:layout_width="match_parent" android:layout_height="match_parent" > <include layout="@layout/refresh_head" /> <com.baway.pullToRefresh.PullableListView android:id="@+id/lv" android:layout_width="match_parent" android:layout_height="match_parent" android:dividerHeight="10dp" > </com.baway.pullToRefresh.PullableListView> <include layout="@layout/load_more" /> </com.baway.pullToRefresh.PullToRefreshLayout>
至此所有的准备工作已经完成,所需要的资源文件已经创建完毕,在调用时,在布局中使用PullToRefreshListView或PullToRefrehGridView,包名+类名。在代码中findViewById找到控件之后需要设置一个监听才能完成上拉加载更多,下拉刷新的效果。如下:
layout.setOnRefreshListener(new OnRefreshListener() { public void onRefresh(PullToRefreshLayout pullToRefreshLayout) { pullToRefreshLayout.refreshFinish(PullToRefreshLayout.SUCCEED); } public void onLoadMore(PullToRefreshLayout pullToRefreshLayout) { pullToRefreshLayout.loadmoreFinish(PullToRefreshLayout.SUCCEED); } });
其中,调用loadmoreFinish方法表示加载更多完成,同样refreshFinish表示刷新完成,通知视图回归原位。
相关文章推荐
- 完美实现Android ListView中的TextView的跑马灯效果
- android上改变listView的选中颜色
- Delphi7中Listview的常用功能汇总
- Delphi控件ListView的属性及使用方法详解
- Android ListView弹性效果的实现方法
- android中ListView数据刷新时的同步方法
- Android提高之ListView实现自适应表格的方法
- Android中实现水平滑动(横向滑动)ListView示例
- Android ListView分页功能实现方法
- C#实现ListView选中项向上或向下移动的方法
- C#下listview如何插入图片
- Listview加载的性能优化是如何实现的
- C#实现listview Group收缩扩展的方法
- C# listview添加combobox到单元格的实现代码
- ListView 百分比进度条(delphi版)
- Android listview多视图嵌套多视图
- ListView Adapter优化 实例
- Android用ListView显示SDCard文件列表的小例子
- Adapter实现ListView带多选框等状态的自定义控件的注意事项
- asp.net ListView 数据绑定