本文主要包括以下内容
- 自定义实现pulltorefreshView
- 使用google官方SwipeRefreshLayout
下拉刷新大致原理
判断当前是否在最上面而且是向下滑的,如果是的话,则加载数据,并更新界面。
自定义实现pulltorefreshView
package com.jimstin.pulltorefreshviewdemo.view;
import com.jimstin.pulltorefreshviewdemo.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.view.View.OnTouchListener;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
public class PullToRefreshView extends LinearLayout implements OnTouchListener {
private static PullToRefreshView mPullToRefreshView;
private static Context mContext;
private AttributeSet mAttrs;
private View[] childs;
private RelativeLayout mTopView, mBottomView;
private MyScrollView mScrollView;
private LinearLayout mContentView;
private LayoutParams mParams;
private static TextView mTopTips, mBottomTips;
private static ImageView mTopTipsIcon, mBottomTipsIcon;
private RotateAnimation toAnmt, backAnmt, progressBarAnmt;
private boolean isToTop, isToBottom;
private boolean isGetChilds;
private boolean isRecordY;
private boolean isContentNotFull;
private boolean isResetLayoutParams;
private boolean isActviteScrollEvent;//偏移量达到刷新或加载更多的要求时,为true,否则为false
private boolean isTurnUp;//判断手势,向上时为true,向下为false
private static boolean isRefresing;//是否正在刷新
private static boolean isRefreshed = true;//是否已刷新完毕
private static boolean isLoading;//是否正在加载
private static boolean isLoaded = true;//是否已加载完毕
private static boolean isPulling;//是否正在拖动scrollview
private boolean isRotate;//是否已经动画
private static int topViewHeight;//topview的高度,和bottomview高度一致
private int mHeight;//整个PullToRefreshView的高度
private int scrollToEnd;//scrollview滚动到底部的长度
private float lastY;//
private float guestureLastY;//用于记录手势按下屏幕时的第一个位置
private OnRefreshListener mRefreshListener;
private OnLoadListener mLoadListener;
public PullToRefreshView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
this.mAttrs = attrs;
initView();
}
private void initView() {
mPullToRefreshView = this;
toAnmt = new RotateAnimation(0, 180f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
toAnmt.setDuration(300);
toAnmt.setFillAfter(true);
backAnmt = new RotateAnimation(0, -180f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
backAnmt.setDuration(300);
backAnmt.setFillAfter(true);
progressBarAnmt = new RotateAnimation(0, 360f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
progressBarAnmt.setDuration(1800);
progressBarAnmt.setRepeatCount(-1);
progressBarAnmt.setRepeatMode(RotateAnimation.RESTART);
progressBarAnmt.setInterpolator(new LinearInterpolator());
toAnmt.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationEnd(Animation animation) {
// TODO Auto-generated method stub
mTopTipsIcon.clearAnimation();
mTopTipsIcon.setBackgroundResource(R.drawable.ic_up);
mBottomTipsIcon.clearAnimation();
mBottomTipsIcon.setBackgroundResource(R.drawable.ic_up);
}
});
backAnmt.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationEnd(Animation animation) {
// TODO Auto-generated method stub
mTopTipsIcon.clearAnimation();
mTopTipsIcon.setBackgroundResource(R.drawable.ic_down);
mBottomTipsIcon.clearAnimation();
mBottomTipsIcon.setBackgroundResource(R.drawable.ic_down);
}
});
ViewTreeObserver ob = getViewTreeObserver();
ob.addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if(topViewHeight == 0 || mHeight == 0) {
if(topViewHeight == 0) {
topViewHeight = mTopView.getHeight();
}
if(mHeight == 0) {
mHeight = getHeight();
}
if(mHeight > 0 && topViewHeight > 0) {
SharedPreferences p = mContext.getSharedPreferences("PullToRefreshView", 0);
p.edit().putInt("barHeight", topViewHeight).putInt("height", mHeight).commit();
}
mParams = new LayoutParams(LayoutParams.MATCH_PARENT, mHeight+2*topViewHeight);
setLayoutParams(mParams);
setY(-topViewHeight);
}
return true;
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onFinishInflate() {
// TODO Auto-generated method stub
super.onFinishInflate();
setLayout();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
public void setLayout() {
if(!isGetChilds) {
int count = getChildCount();
childs = new View[count];
for(int i=0; i<count; i++) {
childs[i] = getChildAt(i);
}
for(int i=0; i<count; i++) {
removeAllViews();
}
LayoutInflater inflater = LayoutInflater.from(mContext);
mTopView = (RelativeLayout) inflater.inflate(R.layout.layout_top_pull_to_refresh_view, null);
mBottomView = (RelativeLayout) inflater.inflate(R.layout.layout_bottom_pull_to_refresh_view, null);
mTopTips = (TextView) mTopView.findViewById(R.id.top_tips);
mBottomTips = (TextView) mBottomView.findViewById(R.id.bottom_tips);
mTopTipsIcon = (ImageView) mTopView.findViewById(R.id.ic_down);
mBottomTipsIcon = (ImageView) mBottomView.findViewById(R.id.ic_up);
mScrollView = new MyScrollView(mContext, mAttrs);
mParams = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
mParams.weight = 1;
mScrollView.setLayoutParams(mParams);
mContentView = new LinearLayout(mContext, mAttrs);
mContentView.setLayoutParams(mParams);
mContentView.setOrientation(LinearLayout.VERTICAL);
for(int i=0; i<count; i++) {
mContentView.addView(childs[i]);
}
mScrollView.addView(mContentView);
mScrollView.setOnTouchListener(this);
addView(mTopView);
addView(mScrollView);
addView(mBottomView);
isGetChilds = true;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mScrollView.getHeight() > mContentView.getHeight()) {
isContentNotFull = true;
mBottomView.setVisibility(View.GONE);
} else {
isContentNotFull = false;
}
}
/**
* 设置刷新监听器
*/
public void setOnRefreshListener(OnRefreshListener listener) {
mRefreshListener = listener;
}
/**
* 设置加载监听器
*/
public void setOnLoadListener(OnLoadListener listener) {
mLoadListener = listener;
}
class MyScrollView extends ScrollView {
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
//MyLog.i("t="+t);
if(t == 0) {
isToTop = true;
} else {
if(t == getChildAt(0).getHeight()-getHeight()) {
scrollToEnd = t;
isToBottom = true;
}
}
}
}
/**
* 刷新监听器
*/
public static abstract class OnRefreshListener {
/**
* 正在刷新
*/
public void onRefresh() {
}
/**
* 停止刷新,刷新完毕时请主动调用
*/
public void stopRefresh() {
isRefresing = false;
mTopTips.setText(mContext.getResources().getString(R.string.refreshed));
mTopTipsIcon.clearAnimation();
mTopTipsIcon.setBackgroundResource(R.drawable.ic_finish);
resetYOffset();
}
}
/**
* 加载更多监听器
*
*/
public static abstract class OnLoadListener {
/**
* 正在加载
*/
public void onLoad() {
}
/**
* 停止加载,加载完毕时请主动调用
*/
public void stopLoad() {
isLoading = false;
mBottomTips.setText(mContext.getResources().getString(R.string.loaded));
mBottomTipsIcon.clearAnimation();
mBottomTipsIcon.setBackgroundResource(R.drawable.ic_finish);
resetYOffset();
}
}
public static void resetYOffset() {
new AsyncTask<Integer, Integer, Integer>() {
@Override
protected Integer doInBackground(Integer... params) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(Integer result) {
if(!isPulling) {
mPullToRefreshView.setY(-topViewHeight);
mTopTipsIcon.setBackgroundResource(R.drawable.ic_down);
mBottomTipsIcon.setBackgroundResource(R.drawable.ic_up);
isRefreshed = true;
isLoaded = true;
}
};
}.execute();
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
guestureLastY = event.getRawY();
isPulling = true;
break;
case MotionEvent.ACTION_MOVE:
if(event.getRawY() - guestureLastY > 0) {
isTurnUp = false;
} else {
isTurnUp = true;
}
if(isContentNotFull && !isResetLayoutParams) {
mParams = new LayoutParams(LayoutParams.MATCH_PARENT, getHeight()-topViewHeight);
setLayoutParams(mParams);
isResetLayoutParams = true;
}
if(isToTop && !isTurnUp) {
if(!isRecordY) {
lastY = event.getRawY()/3;
isRecordY = true;
}
mScrollView.setVerticalScrollBarEnabled(false);
float y = 0.0f;
if(!isRefreshed) {
if(!isPulling) {
y = event.getRawY()/3 - lastY - topViewHeight;
} else {
y = event.getRawY()/3 - lastY;
}
} else {
y = event.getRawY()/3 - lastY - topViewHeight;
}
if(!isRefresing && isRefreshed) {
if(Math.abs(event.getRawY()/3 - lastY) >= topViewHeight) {
isActviteScrollEvent = true;
mTopTips.setText(mContext.getResources().getString(R.string.release_to_refresh));
if(!isRotate) {
mTopTipsIcon.startAnimation(toAnmt);
isRotate = true;
}
} else {
isActviteScrollEvent = false;
mTopTips.setText(mContext.getResources().getString(R.string.pull_to_refresh));
if(isRotate) {
mTopTipsIcon.startAnimation(backAnmt);
isRotate = false;
}
}
}
setY(y);
if(event.getRawY()/3 - lastY < 0) {
isToTop = false;
isRecordY = false;
} else {
mScrollView.setScrollY(0);
}
} else if(isToBottom || isContentNotFull) {
if(!isRecordY) {
lastY = event.getRawY()/3;
isRecordY = true;
}
mScrollView.setVerticalScrollBarEnabled(false);
if(!isLoading && isLoaded) {
if(Math.abs(event.getRawY()/3 - lastY) >= topViewHeight) {
isActviteScrollEvent = true;
mBottomTips.setText(mContext.getResources().getString(R.string.release_to_load));
if(!isRotate) {
mBottomTipsIcon.startAnimation(backAnmt);
isRotate = true;
}
} else {
isActviteScrollEvent = false;
mBottomTips.setText(mContext.getResources().getString(R.string.push_to_load));
if(isRotate) {
mBottomTipsIcon.startAnimation(toAnmt);
isRotate = false;
}
}
}
float y = 0.0f;
if(!isLoaded) {
if(isPulling) {
y = event.getRawY()/3 - lastY - 2*topViewHeight;
} else {
y = event.getRawY()/3 - lastY - topViewHeight;
}
} else {
y = event.getRawY()/3 - lastY - topViewHeight;
}
setY(y);
if(event.getRawY()/3 - lastY > 0) {
isToBottom = false;
isRecordY = false;
} else {
mScrollView.setScrollY(scrollToEnd);
}
}
break;
case MotionEvent.ACTION_UP:
mScrollView.setVerticalScrollBarEnabled(true);
isRotate = false;
isRecordY = false;
isPulling = false;
if(isActviteScrollEvent) {
if(isToTop && !isTurnUp) {
if(mRefreshListener != null) {
isRefresing = true;
isRefreshed = false;
setY(0);
mTopTips.setText(mContext.getResources().getString(R.string.refreshing));
mTopTipsIcon.setBackgroundResource(R.drawable.ic_refresh_progress_bar);
mTopTipsIcon.startAnimation(progressBarAnmt);
mRefreshListener.onRefresh();
}
} else if(isToBottom) {
if(mLoadListener != null) {
setY(-2*topViewHeight);
mBottomTips.setText(mContext.getResources().getString(R.string.loading));
mBottomTipsIcon.setBackgroundResource(R.drawable.ic_refresh_progress_bar);
mBottomTipsIcon.startAnimation(progressBarAnmt);
isLoading = true;
isLoaded = false;
mLoadListener.onLoad();
}
} else if(isContentNotFull) {
setY(-topViewHeight);
}
isActviteScrollEvent = false;
} else {
if(isRefresing) {
setY(0);
} else if(isLoading) {
setY(-2*topViewHeight);
} else {
setY(-topViewHeight);
}
}
if(!isRefresing) {
isRefreshed = true;
}
if(!isLoading) {
isLoaded = true;
}
isToTop = false;
isToBottom = false;
break;
default:
break;
}
return false;
}
public void initLayoutParams() {
SharedPreferences p = mContext.getSharedPreferences("PullToRefreshView", 0);
topViewHeight = p.getInt("barHeight", 0);
mHeight = p.getInt("height", 0);
if(mHeight > 0) {
mParams = new LayoutParams(LayoutParams.MATCH_PARENT, mHeight+2*topViewHeight);
setLayoutParams(mParams);
setY(-topViewHeight);
}
}
}
参考链接
PullToRefreshView下拉刷新上来加载更多,支持任何子view! - zhangjm_123的专栏 - 博客频道 - CSDN.NET
SwipeRefreshLayout 使用
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:background="#Ffffff"
>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh_layout_caselist"
android:layout_marginTop="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ListView
android:id="@+id/list"
android:scrollbars="none"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerHeight="0dp"
android:divider="#Ffffff">
</ListView>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
在Activity中使用
@Override
protected void setUpView() {
if (mIsFirstCreated)
{
onRefresh();
}
listView = $(R.id.list);
swipeRefreshLayout=$(R.id.swipe_refresh_layout_caselist);
//设置刷新时动画的颜色,可以设置4个
swipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_red_light, android.R.color.holo_orange_light, android.R.color.holo_green_light);
swipeRefreshLayout.setOnRefreshListener(this);
getData();
// BaseAdapter arrayAdapter = new ArrayAdapter(getActivity(),R.layout.item_list,R.id.name,
// Arrays.asList("美食劵", "活动劵", "优惠劵", "团购劵", "外卖劵"));
// listView.setAdapter(arrayAdapter);
}
@Override
public void onRefresh() {
//tv.setText("正在刷新");
// TODO Auto-generated method stub
mIsFirstCreated = false;
getData();
}
使用的类要实现SwipeRefreshLayout.OnRefreshListener接口
并且在数据获取成功后发送消息,设置swipeRefreshLayout.setRefreshing(false);
参考链接
Google自己的下拉刷新组件SwipeRefreshLayout - 李金尧 - 博客园
你还在用开源控件的下拉刷新吗?你out了,试一试官方的下拉刷新SwipeRefreshLayout - 推酷
Android View 下拉刷新之头部效果自定义 [水] - bfbx5173的专栏 - 博客频道 - CSDN.NET