便利实用的下拉刷新控件,支持ScrollView、AbsListView
方便实用的下拉刷新控件,支持ScrollView、AbsListView
最近要做一个下拉刷新的功能,网上找了很多例子,也看了一些开源的下拉刷新项目,但是小例子比较简单,效果和稳定性都差强人意,而开源的项目又太庞大,看起来耗时费劲,所以只好综合一下各处的代码掌握其原理,自己实现一套下拉刷新功能。
该控件特点:
1.子控件必须是一个ScrollView或ListView;
2.支持自定义下拉布局;
3.自定义下拉布局可以不用处理下拉的各种状态(只需要实现几个接口即可),也可以自己处理各种下拉的状态。
先来看看效果图:
上代码:
首先看如何使用:
1.使用的布局:
<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:orientation="vertical" > <com.example.pulldown.PullDownScrollView android:id="@+id/refresh_root" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:background="#161616" android:orientation="vertical" > <ScrollView android:id="@+id/scrollview" android:layout_width="fill_parent" android:layout_height="wrap_content" android:scrollbars="none" > <LinearLayout android:id="@+id/mainView" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#1f1f1f" android:orientation="vertical" > <!-- 自已的布局 --> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:gravity="center" android:text="@string/hello_world" android:textColor="@android:color/white" android:textSize="18sp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:gravity="center" android:text="@string/hello_world" android:textColor="@android:color/white" android:textSize="18sp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:gravity="center" android:text="@string/hello_world" android:textColor="@android:color/white" android:textSize="18sp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:gravity="center" android:text="@string/hello_world" android:textColor="@android:color/white" android:textSize="18sp" /> </LinearLayout> </ScrollView> </com.example.pulldown.PullDownScrollView> </LinearLayout>
2.UI使用:
首先,Activity实现接口:
implements RefreshListener
部分代码如下:
package com.example.pulldown; import com.example.pulldown.PullDownScrollView.RefreshListener; import android.os.Bundle; import android.os.Handler; import android.app.Activity; import android.view.Menu; public class MainActivity extends Activity implements RefreshListener{ private PullDownScrollView mPullDownScrollView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPullDownScrollView = (PullDownScrollView) findViewById(R.id.refresh_root); mPullDownScrollView.setRefreshListener(this); mPullDownScrollView.setPullDownElastic(new PullDownElasticImp(this)); } @Override public void onRefresh(PullDownScrollView view) { new Handler().postDelayed(new Runnable() { @Override public void run() { // TODO Auto-generated method stub mPullDownScrollView.finishRefresh("上次刷新时间:12:23"); } }, 2000); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true; } }
3.再来看看控件代码:
import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.AbsListView; import android.widget.LinearLayout; import android.widget.ScrollView; /** * @author xwangly@163.com * @date 2013-7-9 * */ public class PullDownScrollView extends LinearLayout { private static final String TAG = "PullDownScrollView"; private int refreshTargetTop = -60; private int headContentHeight; private RefreshListener refreshListener; private RotateAnimation animation; private RotateAnimation reverseAnimation; private final static int RATIO = 2; private int preY = 0; private boolean isElastic = false; private int startY; private int state; private String note_release_to_refresh = "松开更新"; private String note_pull_to_refresh = "下拉刷新"; private String note_refreshing = "正在更新..."; private IPullDownElastic mElastic; public PullDownScrollView(Context context) { super(context); init(); } public PullDownScrollView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); animation.setInterpolator(new LinearInterpolator()); animation.setDuration(250); animation.setFillAfter(true); reverseAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); reverseAnimation.setInterpolator(new LinearInterpolator()); reverseAnimation.setDuration(200); reverseAnimation.setFillAfter(true); } /** * 刷新监听 * @param listener */ public void setRefreshListener(RefreshListener listener) { this.refreshListener = listener; } /** * 下拉布局 * @param elastic */ public void setPullDownElastic(IPullDownElastic elastic) { mElastic = elastic; headContentHeight = mElastic.getElasticHeight(); refreshTargetTop = - headContentHeight; LayoutParams lp = new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT, headContentHeight); lp.topMargin = refreshTargetTop; addView(mElastic.getElasticLayout(), 0, lp); } /** * 设置更新提示语 * @param pullToRefresh 下拉刷新提示语 * @param releaseToRefresh 松开刷新提示语 * @param refreshing 正在刷新提示语 */ public void setRefreshTips(String pullToRefresh, String releaseToRefresh, String refreshing) { note_pull_to_refresh = pullToRefresh; note_release_to_refresh = releaseToRefresh; note_refreshing = refreshing; } /* * 该方法一般和ontouchEvent 一起用 (non-Javadoc) * * @see * android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent) */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Logger.d(TAG, "onInterceptTouchEvent"); printMotionEvent(ev); if (ev.getAction() == MotionEvent.ACTION_DOWN) { preY = (int) ev.getY(); } if (ev.getAction() == MotionEvent.ACTION_MOVE) { Logger.d(TAG, "isElastic:" + isElastic + " canScroll:"+ canScroll() + " ev.getY() - preY:"+(ev.getY() - preY)); if (!isElastic && canScroll() && (int) ev.getY() - preY >= headContentHeight / (3*RATIO) && refreshListener != null && mElastic != null) { isElastic = true; startY = (int) ev.getY(); Logger.i(TAG, "在move时候记录下位置startY:" + startY); return true; } } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Logger.d(TAG, "onTouchEvent"); printMotionEvent(event); handleHeadElastic(event); return super.onTouchEvent(event); } private void handleHeadElastic(MotionEvent event) { if (refreshListener != null && mElastic != null) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Logger.i(TAG, "down"); break; case MotionEvent.ACTION_UP: Logger.i(TAG, "up"); if (state != IPullDownElastic.REFRESHING && isElastic) { if (state == IPullDownElastic.DONE) { // 什么都不做 setMargin(refreshTargetTop); } if (state == IPullDownElastic.PULL_To_REFRESH) { state = IPullDownElastic.DONE; setMargin(refreshTargetTop); changeHeaderViewByState(state, false); Logger.i(TAG, "由下拉刷新状态,到done状态"); } if (state == IPullDownElastic.RELEASE_To_REFRESH) { state = IPullDownElastic.REFRESHING; setMargin(0); changeHeaderViewByState(state, false); onRefresh(); Logger.i(TAG, "由松开刷新状态,到done状态"); } } isElastic = false; break; case MotionEvent.ACTION_MOVE: Logger.i(TAG, "move"); int tempY = (int) event.getY(); if (state != IPullDownElastic.REFRESHING && isElastic) { // 可以松手去刷新了 if (state == IPullDownElastic.RELEASE_To_REFRESH) { if (((tempY - startY) / RATIO < headContentHeight) && (tempY - startY) > 0) { state = IPullDownElastic.PULL_To_REFRESH; changeHeaderViewByState(state, true); Logger.i(TAG, "由松开刷新状态转变到下拉刷新状态"); } else if (tempY - startY <= 0) { state = IPullDownElastic.DONE; changeHeaderViewByState(state, false); Logger.i(TAG, "由松开刷新状态转变到done状态"); } } if (state == IPullDownElastic.DONE) { if (tempY - startY > 0) { state = IPullDownElastic.PULL_To_REFRESH; changeHeaderViewByState(state, false); } } if (state == IPullDownElastic.PULL_To_REFRESH) { // 下拉到可以进入RELEASE_TO_REFRESH的状态 if ((tempY - startY) / RATIO >= headContentHeight) { state = IPullDownElastic.RELEASE_To_REFRESH; changeHeaderViewByState(state, false); Logger.i(TAG, "由done或者下拉刷新状态转变到松开刷新"); } else if (tempY - startY <= 0) { state = IPullDownElastic.DONE; changeHeaderViewByState(state, false); Logger.i(TAG, "由DOne或者下拉刷新状态转变到done状态"); } } if (tempY - startY > 0) { setMargin((tempY - startY)/2 + refreshTargetTop); } } break; } } } /** * */ private void setMargin(int top) { LinearLayout.LayoutParams lp = (LayoutParams) mElastic.getElasticLayout() .getLayoutParams(); lp.topMargin = top; // 修改后刷新 mElastic.getElasticLayout().setLayoutParams(lp); mElastic.getElasticLayout().invalidate(); } private void changeHeaderViewByState(int state, boolean isBack) { mElastic.changeElasticState(state, isBack); switch (state) { case IPullDownElastic.RELEASE_To_REFRESH: mElastic.showArrow(View.VISIBLE); mElastic.showProgressBar(View.GONE); mElastic.showLastUpdate(View.VISIBLE); mElastic.setTips(note_release_to_refresh); mElastic.clearAnimation(); mElastic.startAnimation(animation); Logger.i(TAG, "当前状态,松开刷新"); break; case IPullDownElastic.PULL_To_REFRESH: mElastic.showArrow(View.VISIBLE); mElastic.showProgressBar(View.GONE); mElastic.showLastUpdate(View.VISIBLE); mElastic.setTips(note_pull_to_refresh); mElastic.clearAnimation(); // 是由RELEASE_To_REFRESH状态转变来的 if (isBack) { mElastic.startAnimation(reverseAnimation); } Logger.i(TAG, "当前状态,下拉刷新"); break; case IPullDownElastic.REFRESHING: mElastic.showArrow(View.GONE); mElastic.showProgressBar(View.VISIBLE); mElastic.showLastUpdate(View.GONE); mElastic.setTips(note_refreshing); mElastic.clearAnimation(); Logger.i(TAG, "当前状态,正在刷新..."); break; case IPullDownElastic.DONE: mElastic.showProgressBar(View.GONE); mElastic.clearAnimation(); // arrowImageView.setImageResource(R.drawable.goicon); // tipsTextview.setText("下拉刷新"); // lastUpdatedTextView.setVisibility(View.VISIBLE); Logger.i(TAG, "当前状态,done"); break; } } private void onRefresh() { // downTextView.setVisibility(View.GONE); // scroller.startScroll(0, i, 0, 0 - i); // invalidate(); if (refreshListener != null) { refreshListener.onRefresh(this); } } /** * */ @Override public void computeScroll() { // if (scroller.computeScrollOffset()) { // int i = this.scroller.getCurrY(); // LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) this.refreshView // .getLayoutParams(); // int k = Math.max(i, refreshTargetTop); // lp.topMargin = k; // this.refreshView.setLayoutParams(lp); // this.refreshView.invalidate(); // invalidate(); // } } /** * 结束刷新事件,UI刷新完成后必须回调此方法 * @param text 一般传入:“上次更新时间:12:23” */ public void finishRefresh(String text) { if (mElastic == null) { Logger.d(TAG, "finishRefresh mElastic:" + mElastic); return; } state = IPullDownElastic.DONE; mElastic.setLastUpdateText(text); changeHeaderViewByState(state,false); Logger.i(TAG, "执行了=====finishRefresh"); mElastic.showArrow(View.VISIBLE); mElastic.showLastUpdate(View.VISIBLE); setMargin(refreshTargetTop); // scroller.startScroll(0, i, 0, refreshTargetTop); // invalidate(); } private boolean canScroll() { View childView; if (getChildCount() > 1) { childView = this.getChildAt(1); if (childView instanceof AbsListView) { int top = ((AbsListView) childView).getChildAt(0).getTop(); int pad = ((AbsListView) childView).getListPaddingTop(); if ((Math.abs(top - pad)) < 3 && ((AbsListView) childView).getFirstVisiblePosition() == 0) { return true; } else { return false; } } else if (childView instanceof ScrollView) { if (((ScrollView) childView).getScrollY() == 0) { return true; } else { return false; } } } return canScroll(this); } /** * 子类重写此方法可以兼容其它的子控件,目前只兼容AbsListView和ScrollView * @param view * @return */ public boolean canScroll(PullDownScrollView view) { return false; } private void printMotionEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Logger.d(TAG, "down"); break; case MotionEvent.ACTION_MOVE: Logger.d(TAG, "move"); break; case MotionEvent.ACTION_UP: Logger.d(TAG, "up"); default: break; } } /** * 刷新监听接口 */ public interface RefreshListener { public void onRefresh(PullDownScrollView view); } }
4.接口:
import android.view.View; import android.view.animation.Animation; /** * @author xwangly@163.com * @date 2013-7-10 * 下拉控件接口 */ public interface IPullDownElastic { public final static int RELEASE_To_REFRESH = 0; public final static int PULL_To_REFRESH = 1; public final static int REFRESHING = 2; public final static int DONE = 3; public View getElasticLayout(); public int getElasticHeight(); public void showArrow(int visibility); public void startAnimation(Animation animation); public void clearAnimation(); public void showProgressBar(int visibility); public void setTips(String tips); public void showLastUpdate(int visibility); public void setLastUpdateText(String text); /** * 可以不用实现此方法,PullDownScrollView会处理ElasticLayout布局中的状态 * 如果需要特殊处理,可以实现此方法进行处理 * * @param state @see RELEASE_To_REFRESH * @param isBack 是否是松开回退 */ public void changeElasticState(int state, boolean isBack); }
5.默认实现:
import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.animation.Animation; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; /** * @author xwangly@163.com * @date 2013-7-10 * 默认下拉控件布局实现 */ public class PullDownElasticImp implements IPullDownElastic { private View refreshView; private ImageView arrowImageView; private int headContentHeight; private ProgressBar progressBar; private TextView tipsTextview; private TextView lastUpdatedTextView; private Context mContext; public PullDownElasticImp(Context context) { mContext = context; init(); } private void init() { // 刷新视图顶端的的view refreshView = LayoutInflater.from(mContext).inflate( R.layout.refresh_top_item, null); // 指示器view arrowImageView = (ImageView) refreshView .findViewById(R.id.head_arrowImageView); // 刷新bar progressBar = (ProgressBar) refreshView .findViewById(R.id.head_progressBar); // 下拉显示text tipsTextview = (TextView) refreshView.findViewById(R.id.refresh_hint); // 下来显示时间 lastUpdatedTextView = (TextView) refreshView .findViewById(R.id.refresh_time); headContentHeight = Utils.dip2px(mContext, 50); } /** * @return * */ @Override public View getElasticLayout() { return refreshView; } /** * @return * */ @Override public int getElasticHeight() { return headContentHeight; } /** * @param show * */ @Override public void showArrow(int visibility) { arrowImageView.setVisibility(visibility); } /** * @param animation * */ @Override public void startAnimation(Animation animation) { arrowImageView.startAnimation(animation); } /** * * */ @Override public void clearAnimation() { arrowImageView.clearAnimation(); } /** * @param show * */ @Override public void showProgressBar(int visibility) { progressBar.setVisibility(visibility); } /** * @param tips * */ @Override public void setTips(String tips) { tipsTextview.setText(tips); } /** * @param show * */ @Override public void showLastUpdate(int visibility) { lastUpdatedTextView.setVisibility(visibility); } /** * @param text * */ public void setLastUpdateText(String text) { lastUpdatedTextView.setText(text); } /** * @param state * @param isBack * */ @Override public void changeElasticState(int state, boolean isBack) { // TODO Auto-generated method stub } }
6.默认实现的布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="-50.0dip" android:orientation="vertical" > <LinearLayout android:layout_width="fill_parent" android:layout_height="0.0dip" android:layout_weight="1.0" android:gravity="center" android:orientation="horizontal" > <!-- 箭头图像、进度条 --> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_marginLeft="30dip" > <!-- 箭头 --> <ImageView android:id="@+id/head_arrowImageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/goicon" /> <!-- 进度条 --> <ProgressBar android:id="@+id/head_progressBar" style="@android:style/Widget.ProgressBar.Small.Inverse" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:visibility="gone" /> </FrameLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:orientation="vertical" > <!-- 提示 --> <TextView android:id="@+id/refresh_hint" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉刷新" android:textColor="#f2f2f2" android:textSize="16sp" /> <!-- 最近更新 --> <TextView android:id="@+id/refresh_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="上次更新" android:textColor="#b89766" android:textSize="10sp" /> </LinearLayout> </LinearLayout> </LinearLayout>
6.图片资源:
@drawable/goicon
完结
修改:从原工程中独立出来,并附上简单的工程,见附件。
1 楼
freezingsky
19 小时前
写得很好。继续努力!