提要:本文主要介绍了android提供的原生下拉刷新控件SwipeRefreshLayout;在github上,由作者Chris Banes实现的第三方下拉刷新(上拉刷新)控件的源码浅析以及根据其源码自己实现的简单下拉刷新demo。
下拉刷新(上拉刷新)是近两三年android APP比较流行的实现页面更新的控件,可以使用android.support.v4.widget包的原生控件SwipeRefreshLayout实现下拉刷新功能,本demo实现了每次执行下拉刷新后生成一个1-100之间的随机整数,效果如下:
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}" >
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_world" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉刷新以生成一个随机数:"
android:textSize="23sp" />
<TextView
android:id="@+id/text_view_generate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#69FF0000"
android:textSize="25sp" />
LinearLayout>
ScrollView>
android.support.v4.widget.SwipeRefreshLayout>
xml布局代码如上所示,在引入SwipeRefreshLayout控件时,需要写全限定类名;另外需要注意的是,该控件只能嵌套一个直接子控件,而且这个子控件必须是具有滑动功能的AdapterView(如ListView,Spinner,GridView等)。
public class MainActivity extends Activity {
private SwipeRefreshLayout mSwipeRefreshLayout;
private TextView mTextView;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.text_view_generate);
mHandler = new Handler();
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
mSwipeRefreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh() {
// TODO Auto-generated method stub
// 产生一个刷新效果的动画
mSwipeRefreshLayout.setRefreshing(true);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
// 三秒后停止刷新动画
mSwipeRefreshLayout.setRefreshing(false);
// 产生1-100随机整数
int i = (int) (Math.random() * 100 + 1);
mTextView.setText(String.valueOf(i));
}
}, 3000);
}
});
}
}
activity代码如上所示,首先声明SwipeRefreshLayout控件并绑定setOnRefreshListener监听器,参数是一个OnRefreshListener类型的接口对象,需要实现接口中未实现的方法onRefresh,在该方法中首先调用SwipeRefreshLayout的方法setRefreshing(true)方法,表示开启动画效果,接着在Handler的postDelay方法中传入两个参数,第一个参数是一个匿名Runnable接口对象,第二个参数是一个int类型整数,本demo中传入3000,代表3秒,该方法表示动画显示的持续时长,然后在run方法中关闭动画效果,并进行相应的UI操作。
系统自带的原生下拉刷新控件SwipeRefreshLayout的优点是比较简洁,没有过多花哨的动画效果,android版chrome浏览器的下拉刷新功能便使用的该控件(chrome是我最喜欢的android浏览器),然而缺点也显而易见,那就是扩展性不够好,对于现今这个APP也看脸的时代,没有炫酷的动画已经很难满足用户的需要,另外,该控件不支持上拉刷新,如果打算实现上拉刷新和更多动画效果,需要下面介绍的第三方控件。
PullToRefresh是一个开源控件,不仅支持下拉刷新,还支持上拉刷新,而且可以利用刷新时的动画效果为用户提供有用信息(如刷新时间,手势操作提示等),很多商业级别的应用已经才用了这个控件,该控件有以下特点:
该控件的demo源程序可以在github上下载,地址是:https://github.com/chrisbanes/Android-PullToRefresh。下面将浅析其中的部分代码。
先看看该控件包含一个ListView的效果:
向下拉动,显示”放开以刷新”的提示和上次刷新的时间,左边显式圆形箭头图标
刷新完毕后,ListView在第一行增加一条内容”Added after refresh…”
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<com.handmark.pulltorefresh.library.PullToRefreshListView
android:id="@+id/pull_refresh_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000"
android:divider="#19000000"
android:dividerHeight="4dp"
android:fadingEdge="none"
android:fastScrollEnabled="false"
android:footerDividersEnabled="false"
android:headerDividersEnabled="false"
android:smoothScrollbar="true" />
LinearLayout>
xml布局控件做了封装,控件com.handmark.pulltorefresh.library.PullToRefreshListView里面含有一个ListView。
public final class PullToRefreshListActivity extends ListActivity {
static final int MENU_MANUAL_REFRESH = 0;
static final int MENU_DISABLE_SCROLL = 1;
static final int MENU_SET_MODE = 2;
static final int MENU_DEMO = 3;
private LinkedList mListItems;
private PullToRefreshListView mPullRefreshListView;
private ArrayAdapter mAdapter;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ptr_list);
mPullRefreshListView = (PullToRefreshListView) findViewById(R.id.pull_refresh_list);
// Set a listener to be invoked when the list should be refreshed.
mPullRefreshListView.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(PullToRefreshBase refreshView) {
String label = DateUtils.formatDateTime(getApplicationContext(), System.currentTimeMillis(),
DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_ALL);
// Update the LastUpdatedLabel
refreshView.getLoadingLayoutProxy().setLastUpdatedLabel(label);
// Do work to refresh the list here.
new GetDataTask().execute();
}
});
// Add an end-of-list listener
mPullRefreshListView.setOnLastItemVisibleListener(new OnLastItemVisibleListener() {
@Override
public void onLastItemVisible() {
Toast.makeText(PullToRefreshListActivity.this, "End of List!", Toast.LENGTH_SHORT).show();
}
});
ListView actualListView = mPullRefreshListView.getRefreshableView();
// Need to use the Actual ListView when registering for Context Menu
registerForContextMenu(actualListView);
mListItems = new LinkedList();
mListItems.addAll(Arrays.asList(mStrings));
mAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, mListItems);
mPullRefreshListView.setOnPullEventListener(soundListener);
// You can also just use setListAdapter(mAdapter) or
// mPullRefreshListView.setAdapter(mAdapter)
actualListView.setAdapter(mAdapter);
}
private class GetDataTask extends AsyncTask<Void, Void, String[]> {
@Override
protected String[] doInBackground(Void... params) {
// Simulates a background job.
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
}
return mStrings;
}
@Override
protected void onPostExecute(String[] result) {
mListItems.addFirst("Added after refresh...");
mAdapter.notifyDataSetChanged();
// Call onRefreshComplete when the list has been refreshed.
mPullRefreshListView.onRefreshComplete();
super.onPostExecute(result);
}
}
private String[] mStrings = { "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
"Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",
"Allgauer Emmentaler", "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
"Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",
"Allgauer Emmentaler" };
}
首先为PullToRefresh控件绑定监听器setOnRefreshListener,参数是匿名接口OnRefreshListener对象,注意,若希望同时实现上拉刷新和下拉刷新,需实现匿名接口OnRefreshListener2;在onRefresh方法中首先设置了显示刷新的时间,然后执行异步任务AsyncTask,在onPostExecute方法中为ListView增加一行,并通知Adapter刷新页面,最后调用onRefreshComplete方法完成刷新;绑定setOnLastItemVisibleListener监听器用于监听ListView滑到底部时的情况。
利用PullToRefresh控件,实现了每次下拉刷新(或上拉刷新)后,生成一组双色球号码的demo。
效果如下所示:
向下拉动,显示文字提示”放开以刷新” 左侧有Android机器人图标转动
松开手,显示”正在载入…”,左侧Android机器人图标转动
向上拉动,底部提示”放开以刷新”,左侧Android机器人图标转动
松开手,显示”正在载入…”,左侧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"
tools:context="${relativePackage}.${activityClass}" >
<com.handmark.pulltorefresh.library.PullToRefreshListView
xmlns:ptr="http://schemas.android.com/apk/res-auto"
android:id="@+id/pull_refresh_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000"
android:divider="#19000000"
android:dividerHeight="4dp"
android:fadingEdge="none"
android:fastScrollEnabled="false"
android:footerDividersEnabled="false"
android:headerDividersEnabled="false"
android:smoothScrollbar="true"
ptr:ptrMode="both"
ptr:ptrDrawable="@drawable/ic_launcher" />
注意要引用控件的全限定类名com.handmark.pulltorefresh.library.PullToRefreshListView。
public class MainActivity extends Activity {
private PullToRefreshListView mPullToRefresh;
private ArrayAdapter mArrayAdapter;
private List mList;
private ListView mListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mList = new ArrayList();
mArrayAdapter = new ArrayAdapter(this,
android.R.layout.simple_list_item_1, mList);
mPullToRefresh = (PullToRefreshListView) findViewById(R.id.pull_refresh_list);
mListView = mPullToRefresh.getRefreshableView();
mListView.setAdapter(mArrayAdapter);
// 同时实现上拉刷新和下拉刷新
mPullToRefresh.setOnRefreshListener(new OnRefreshListener2() {
// 实现下拉刷新
@Override
public void onPullDownToRefresh(PullToRefreshBase refreshView) {
// TODO Auto-generated method stub
new GetDataTask().execute();
}
// 实现下拉刷新
@Override
public void onPullUpToRefresh(PullToRefreshBase refreshView) {
// TODO Auto-generated method stub
new GetDataTask().execute();
}
});
}
private class GetDataTask extends AsyncTask {
@Override
protected String doInBackground(Void... params) {
// Simulates a background job.
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
return generateData();
}
@Override
protected void onPostExecute(String result) {
mList.add(result);
mArrayAdapter.notifyDataSetChanged();
// Call onRefreshComplete when the list has been refreshed.
mPullToRefresh.onRefreshComplete();
super.onPostExecute(result);
}
// 生成ListView中的一行数据
private String generateData()
{
Set treeSet = new TreeSet();
do {
treeSet.add(generateARedBall());
} while (treeSet.size() <= 6);
String[] _string = new String[6];
_string = treeSet.toArray(_string);
StringBuilder _stringBuilder = new StringBuilder();
_stringBuilder.append("【第" + (mList.size() + 1) + "组】红:");
for (int _i = 0; _i < 6; _i++) {
_stringBuilder.append(_string[_i]);
_stringBuilder.append(", ");
}
_stringBuilder = _stringBuilder.delete(_stringBuilder.length() - 2,
_stringBuilder.length() - 1);
_stringBuilder.append("蓝:" + generateABlueBall());
return _stringBuilder.toString();
}
// 生成一组红色球号码
private String generateARedBall() {
// TODO Auto-generated method stub
int i = (int) (Math.random() * 99 + 1);
if (i < 10) {
return "0" + String.valueOf(i);
} else {
return String.valueOf(i);
}
}
// 生成一个蓝色球号码
private String generateABlueBall() {
// TODO Auto-generated method stub
int i = (int) (Math.random() * 16 + 1);
if (i < 10) {
return "0" + String.valueOf(i);
} else {
return String.valueOf(i);
}
}
}
}
代码实现了上述图片所示效果,该程序通过匿名接口OnRefreshListener2对象实现了上拉刷新和下拉刷新两种手势操作,生成双色球随机数。