效果圖以下html
1.原理java
其實就是繼承了Listview控件,爲其默認加一個header,這個header就是帶箭頭的那些東西。而後override onTouchEvent函數,根據滑動過程當中y座標的變化來動態修改header的狀態(包括箭頭的方向和文字提示),其餘用法和普通的Listview同樣。android
public class PullToRefreshListView extends ListView implements OnScrollListener { private static final String TAG = "PullToRefreshListView"; private final static int RELEASE_TO_REFRESH = 0; private final static int PULL_TO_REFRESH = 1; private final static int REFRESHING = 2; private final static int DONE = 3; private final static int LOADING = 4; // 實際的padding的距離與界面上偏移距離的比例 private final static int RATIO = 3; private LayoutInflater inflater; //listview的頭部 用於顯示刷新的箭頭等 private LinearLayout headView; private TextView tipsTextview; private TextView lastUpdatedTextView; private ImageView arrowImageView; private ProgressBar progressBar; //箭頭旋轉的動畫 private RotateAnimation animation; private RotateAnimation reverseAnimation; // 用於保證startY的值在一個完整的touch事件中只被記錄一次 private boolean isRecored; private int headContentWidth; private int headContentHeight; private int startY; private int firstItemIndex; private int state; private boolean isBack; private OnRefreshListener refreshListener; private boolean isRefreshable; public PullToRefreshListView(Context context) { super(context); init(context); } public PullToRefreshListView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context) { setCacheColorHint(context.getResources().getColor(R.color.transparent)); inflater = LayoutInflater.from(context); headView = (LinearLayout) inflater.inflate(R.layout.head, null); arrowImageView = (ImageView) headView .findViewById(R.id.head_arrowImageView); arrowImageView.setMinimumWidth(70); arrowImageView.setMinimumHeight(50); progressBar = (ProgressBar) headView .findViewById(R.id.head_progressBar); tipsTextview = (TextView) headView.findViewById(R.id.head_tipsTextView); lastUpdatedTextView = (TextView) headView .findViewById(R.id.head_lastUpdatedTextView); measureView(headView); headContentHeight = headView.getMeasuredHeight(); headContentWidth = headView.getMeasuredWidth(); headView.setPadding(0, -1 * headContentHeight, 0, 0); headView.invalidate(); Log.v("size", "width:" + headContentWidth + " height:" + headContentHeight); addHeaderView(headView, null, false); setOnScrollListener(this); 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); state = DONE; isRefreshable = false; } @Override public void onScroll(AbsListView arg0, int firstVisiableItem, int arg2, int arg3) { firstItemIndex = firstVisiableItem; } @Override public void onScrollStateChanged(AbsListView arg0, int arg1) { } @Override public boolean onTouchEvent(MotionEvent event) { if (isRefreshable) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (firstItemIndex == 0 && !isRecored) { isRecored = true; startY = (int) event.getY(); Log.v(TAG, "在down時候記錄當前位置"); } break; case MotionEvent.ACTION_UP: if (state != REFRESHING && state != LOADING) { if (state == DONE) { // 什麼都不作 } if (state == PULL_TO_REFRESH) { state = DONE; changeHeaderViewByState(); Log.v(TAG, "由下拉刷新狀態,到done狀態"); } if (state == RELEASE_TO_REFRESH) { state = REFRESHING; changeHeaderViewByState(); onRefresh(); Log.v(TAG, "由鬆開刷新狀態,到done狀態"); } } isRecored = false; isBack = false; break; case MotionEvent.ACTION_MOVE: int tempY = (int) event.getY(); if (!isRecored && firstItemIndex == 0) { Log.v(TAG, "在move時候記錄下位置"); isRecored = true; startY = tempY; } if (state != REFRESHING && isRecored && state != LOADING) { // 保證在設置padding的過程當中,當前的位置一直是在head,不然若是當列表超出屏幕的話,當在上推的時候,列表會同時進行滾動 // 能夠鬆手去刷新了 if (state == RELEASE_TO_REFRESH) { setSelection(0); // 往上推了,推到了屏幕足夠掩蓋head的程度,可是尚未推到所有掩蓋的地步 if (((tempY - startY) / RATIO < headContentHeight) && (tempY - startY) > 0) { state = PULL_TO_REFRESH; changeHeaderViewByState(); Log.v(TAG, "由鬆開刷新狀態轉變到下拉刷新狀態"); } // 一會兒推到頂了 else if (tempY - startY <= 0) { state = DONE; changeHeaderViewByState(); Log.v(TAG, "由鬆開刷新狀態轉變到done狀態"); } // 往下拉了,或者尚未上推到屏幕頂部掩蓋head的地步 else { // 不用進行特別的操做,只用更新paddingTop的值就好了 } } // 尚未到達顯示鬆開刷新的時候,DONE或者是PULL_To_REFRESH狀態 if (state == PULL_TO_REFRESH) { setSelection(0); // 下拉到能夠進入RELEASE_TO_REFRESH的狀態 if ((tempY - startY) / RATIO >= headContentHeight) { state = RELEASE_TO_REFRESH; isBack = true; changeHeaderViewByState(); Log.v(TAG, "由done或者下拉刷新狀態轉變到鬆開刷新"); } // 上推到頂了 else if (tempY - startY <= 0) { state = DONE; changeHeaderViewByState(); Log.v(TAG, "由DOne或者下拉刷新狀態轉變到done狀態"); } } // done狀態下 if (state == DONE) { if (tempY - startY > 0) { state = PULL_TO_REFRESH; changeHeaderViewByState(); } } // 更新headView的size if (state == PULL_TO_REFRESH) { headView.setPadding(0, -1 * headContentHeight + (tempY - startY) / RATIO, 0, 0); } // 更新headView的paddingTop if (state == RELEASE_TO_REFRESH) { headView.setPadding(0, (tempY - startY) / RATIO - headContentHeight, 0, 0); } } break; } } return super.onTouchEvent(event); } // 當狀態改變時候,調用該方法,以更新界面 private void changeHeaderViewByState() { switch (state) { case RELEASE_TO_REFRESH: arrowImageView.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); tipsTextview.setVisibility(View.VISIBLE); lastUpdatedTextView.setVisibility(View.VISIBLE); arrowImageView.clearAnimation(); arrowImageView.startAnimation(animation); tipsTextview.setText("放開以刷新"); Log.v(TAG, "當前狀態,鬆開刷新"); break; case PULL_TO_REFRESH: progressBar.setVisibility(View.GONE); tipsTextview.setVisibility(View.VISIBLE); lastUpdatedTextView.setVisibility(View.VISIBLE); arrowImageView.clearAnimation(); arrowImageView.setVisibility(View.VISIBLE); // 是由RELEASE_To_REFRESH狀態轉變來的 if (isBack) { isBack = false; arrowImageView.clearAnimation(); arrowImageView.startAnimation(reverseAnimation); tipsTextview.setText("下拉刷新"); } else { tipsTextview.setText("下拉刷新"); } Log.v(TAG, "當前狀態,下拉刷新"); break; case REFRESHING: headView.setPadding(0, 0, 0, 0); progressBar.setVisibility(View.VISIBLE); arrowImageView.clearAnimation(); arrowImageView.setVisibility(View.GONE); tipsTextview.setText("正在刷新..."); lastUpdatedTextView.setVisibility(View.VISIBLE); Log.v(TAG, "當前狀態,正在刷新..."); break; case DONE: headView.setPadding(0, -1 * headContentHeight, 0, 0); progressBar.setVisibility(View.GONE); arrowImageView.clearAnimation(); arrowImageView.setImageResource(R.drawable.arrow); tipsTextview.setText("下拉刷新"); lastUpdatedTextView.setVisibility(View.VISIBLE); Log.v(TAG, "當前狀態,done"); break; } } public void setonRefreshListener(OnRefreshListener refreshListener) { this.refreshListener = refreshListener; isRefreshable = true; } public interface OnRefreshListener { public void onRefresh(); } public void onRefreshComplete() { state = DONE; lastUpdatedTextView.setText("最近更新:" + new Date().toLocaleString()); changeHeaderViewByState(); } private void onRefresh() { if (refreshListener != null) { refreshListener.onRefresh(); } } // 此方法直接照搬自網絡上的一個下拉刷新的demo,此處是「估計」headView的width以及height private void measureView(View child) { ViewGroup.LayoutParams p = child.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } public void setAdapter(BaseAdapter adapter) { lastUpdatedTextView.setText("最近更新:" + new Date().toLocaleString()); super.setAdapter(adapter); } }
header的layout xml網絡
<?xml version="1.0" encoding="utf-8"?> <!-- ListView的頭部 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <!-- 內容 --> <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/head_contentLayout" android:paddingLeft="30dp" > <!-- 箭頭圖像、進度條 --> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" > <!-- 箭頭 --> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/arrow" android:id="@+id/head_arrowImageView" /> <!-- 進度條 --> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/progressBarStyleSmall" android:layout_gravity="center" android:id="@+id/head_progressBar" android:visibility="gone" /> </FrameLayout> <!-- 提示、最近更新 --> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:orientation="vertical" android:gravity="center_horizontal" > <!-- 提示 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉刷新" android:textColor="#33CCFF" android:textSize="20sp" android:id="@+id/head_tipsTextView" /> <!-- 最近更新 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/head_lastUpdatedTextView" android:text="上次更新" android:textColor="@color/gold" android:textSize="10sp" /> </LinearLayout> </RelativeLayout> </LinearLayout>
2.如何使用ide
使用起來很簡單,只有一點不一樣,要實現OnRefreshListener接口,標誌當下拉刷新的時候你所要作的事情。函數
listView.setonRefreshListener(new OnRefreshListener() { @Override public void onRefresh() { new AsyncTask<Void, Void, Void>() { //刷新過程當中須要作的操做在這裏 protected Void doInBackground(Void... params) { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } data.add("new item"); return null; } //刷新完成後要通知listview進行界面調整 @Override protected void onPostExecute(Void result) { adapter.notifyDataSetChanged(); listView.onRefreshComplete(); } }.execute(null); } });