下拉刷新是一種比較常用的效果,Android 5.0之前官方並未提供類似的控件,App中主要是用的第三方庫,例如PullToRefresh,ActionBar-PullToRefresh等。剛好現在項目中需要處理 Android 5.0 材質設計部分的東西,就順帶學習下這部分。
大體介紹一下;
- SwipeRefreshLayout是Google在support v4 19.1版本的library更新的一個下拉刷新控件 (android-support-v4.jar)
- 目前只支持下拉刷新,不支持上拉加載更多的操作(需要自行進行擴展)
- 作為官方自帶的控件,相對能能夠保證比較好的通用性及風格(這里不包括各種自家定制的系統 L)
- SwipeRefreshLayout繼承於ViewGroup,ViewGroup中可以包含其他不同控件,so UI定制起來也相對比較容易
- 使用起來比較方便,可以很容易的實現Google Now的刷新效果
SwipeRefreshLayout布局中目前只能包含一個子布局,使用偵聽機制來通知刷新事件。例如當用戶使用下拉手勢時,SwipeRefreshLayout會觸發OnRefreshListener,然后刷新事件會在onRefresh()方法中進行處理。當需要結束刷新的時候,可以調用setRefreshing(false)。如果要禁用手勢和進度動畫,調用setEnabled(false)即可。
接下來介紹一下其大體使用方法:
1.布局文件(示例代碼)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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.support.v4.widget.SwipeRefreshLayout
android:id="@+id/id_explore_swipe_ly"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ffffff" >
<ListView
android:id="@+id/id_listview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>
2.java邏輯代碼:
首先需要實現 SwipeRefreshLayout.OnRefreshListener 接口,然后重寫方法 onRefresh():
@Override
public void onRefresh() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// 設置SwipeRefreshLayout當前是否處於刷新狀態,一般是在請求數據的時候設置為true,在數據被加載到View中后,設置為false。
mSwipeRefreshLayout.setRefreshing(false);
}
}, 3000);
}
現在我們初始化該控件:
public void initSwipeRefreshParameters() {
// 設置進度條的顏色變化,最多可以設置4種顏色
mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_green_dark, android.R.color.holo_green_light,
android.R.color.holo_orange_light, android.R.color.holo_red_light);
// 設置下拉監聽,當用戶下拉的時候會去執行回調
mSwipeRefreshLayout.setOnRefreshListener(this);
// 調整進度條距離屏幕頂部的距離
mSwipeRefreshLayout.setProgressViewOffset(false, 0,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources().getDisplayMetrics()));
}
以上是基本用法,現在來大體介紹一下定制支持上拉加載的部分,演示圖示:

圖1 上拉加載效果示意圖
首先實現SwipeRefreshLayout的重寫:
public class mySwipeRefreshLayout extends SwipeRefreshLayout implements OnScrollListener {
private int mTouchSlop;
private ListView mListView;
private OnLoadListener mOnLoadListener;
private View mListViewFooter;
private int mYDown;
private int mLastY;
private boolean isLoading = false;
public mySwipeRefreshLayout(Context context) {
this(context, null);
}
public mySwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mListViewFooter = LayoutInflater.from(context).inflate(R.layout.listview_footer, null, false);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// 初始化ListView對象
if (mListView == null) {
getListView();
}
}
private void getListView() {
int childs = getChildCount();
if (childs > 0) {
View childView = getChildAt(0);
if (childView instanceof ListView) {
mListView = (ListView) childView;
// 設置滾動監聽器給ListView, 使得滾動的情況下也可以自動加載
mListView.setOnScrollListener(this);
}
}
}
public void setListView(ListView list) {
this.mListView = list;
setLoading(true);
this.mListView.setOnScrollListener(this);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mYDown = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
mLastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
if (canLoad()) {
loadData();
}
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
/**
* @方法說明:是否可以加載更多, 條件是到了最底部, listview不在加載中, 且為上拉操作.
*/
private boolean canLoad() {
return isBottom() && !isLoading && isPullUp();
}
/**
* @方法說明:判斷是否到了最底部
*/
private boolean isBottom() {
if (mListView != null && mListView.getAdapter() != null) {
return mListView.getLastVisiblePosition() == (mListView.getAdapter().getCount() - 1);
}
return false;
}
/**
* @方法說明:是否是上拉操作
*/
private boolean isPullUp() {
return (mYDown - mLastY) >= mTouchSlop;
}
/**
* @方法說明: 如果到了最底部,而且是上拉操作.那么執行onLoad方法
*/
private void loadData() {
if (mOnLoadListener != null) {
mOnLoadListener.onLoad();
}
// 設置狀態
setLoading(true);
}
/**
* @方法說明:設置刷新
*/
public void setLoading(boolean loading) {
isLoading = loading;
if (mListView != null && mListView.getFooterViewsCount() > 0)
mListView.removeFooterView(mListViewFooter);
if (isLoading) {
if (mListView != null && mListView.getFooterViewsCount() <= 0)
mListView.addFooterView(mListViewFooter);
} else {
mYDown = 0;
mLastY = 0;
}
}
public void setOnLoadListener(OnLoadListener loadListener) {
mOnLoadListener = loadListener;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (isFastDoubleClick(100))
return;
// 滾動時到了最底部也可以加載更多
isLoading = false;
if (canLoad()) {
loadData();
}
}
public View getmListViewFooter() {
return mListViewFooter;
}
/**
* @類描述:加載更多的監聽器
*/
public static interface OnLoadListener {
public void onLoad();
}
private static long lastClickTime;
public static boolean isFastDoubleClick(long times) {
long time = System.currentTimeMillis();
long timeD = time - lastClickTime;
if (0 < timeD && timeD < times) {
return true;
}
lastClickTime = time;
return false;
}
}
MainActivity.java
public class MainActivity extends Activity {
private mydapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
adapter = new mydapter();
// 獲取RefreshLayout實例
final mySwipeRefreshLayout myRefreshListView = (mySwipeRefreshLayout) findViewById(R.id.swipe_layout);
// 獲取listview實例
ListView listView = (ListView) findViewById(R.id.listview);
myRefreshListView.setListView(listView);
listView.setAdapter(adapter);
// 設置進度條的顏色變化,最多可以設置4種顏色
myRefreshListView.setColorSchemeResources(android.R.color.holo_green_dark, android.R.color.holo_green_light,
android.R.color.holo_orange_light, android.R.color.holo_red_light);
// 設置下拉刷新監聽器
myRefreshListView.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh() {
Toast.makeText(MainActivity.this, "refresh", Toast.LENGTH_SHORT).show();
myRefreshListView.postDelayed(new Runnable() {
@Override
public void run() {
// 更新完后調用該方法結束刷新
myRefreshListView.setRefreshing(false);
adapter.getData().clear();
// 模擬一些數據
for (int i = 0; i < 20; i++) {
adapter.addData("liu hhh " + i);
}
}
}, 1000);
}
});
// 加載監聽器
myRefreshListView.setOnLoadListener(new OnLoadListener() {
@Override
public void onLoad() {
myRefreshListView.postDelayed(new Runnable() {
@Override
public void run() {
// 加載完后調用該方法
adapter.addData(new Date().toGMTString());
adapter.notifyDataSetChanged();
myRefreshListView.setLoading(false);
}
}, 1500);
}
});
}
class mydapter extends BaseAdapter {
List<String> datas = new ArrayList<String>();
public mydapter() {
// 模擬一些數據
for (int i = 0; i < 20; i++) {
datas.add("item - " + i);
}
}
public void setData(List<String> data) {
this.datas = data;
notifyDataSetChanged();
}
public void addData(String str) {
this.datas.add(str);
notifyDataSetChanged();
}
public List<String> getData() {
return datas;
}
@Override
public int getCount() {
return datas.size();
}
@Override
public Object getItem(int position) {
return datas.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = MainActivity.this.getLayoutInflater().inflate(R.layout.item, null);
}
TextView tv = (TextView) convertView.findViewById(R.id.text);
tv.setText(datas.get(position));
return convertView;
}
}
}
listView_footer:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@color/very_light_gray"
android:gravity="center"
android:layout_gravity="center_horizontal"
android:paddingBottom="10dip"
android:paddingTop="10dip" >
<ProgressBar
android:id="@+id/pull_to_refresh_load_progress"
style="@android:style/Widget.ProgressBar.Small.Inverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:indeterminate="true"
android:paddingRight="10dp" />
<TextView
android:id="@+id/pull_to_refresh_loadmore_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_toRightOf="@+id/pull_to_refresh_load_progress"
android:paddingTop="5dip"
android:text="@string/loading"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@android:color/darker_gray"
android:textSize="14sp"
android:textStyle="bold" />
</RelativeLayout>
main.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.demo.mySwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</com.example.demo.mySwipeRefreshLayout>
item.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left"
android:padding="10dp"
android:text="wo lai le"
android:background="@color/very_light_gray"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/white"/>
</LinearLayout>
參考鏈接
http://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html
