下拉刷新是一種比較常用的效果,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