package net.oschina.app.widget; import net.oschina.app.R; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; /** * 下拉刷新控件 * @version 1.0 * @created 2012-3-21 */ public class PullToRefreshListView extends ListView implements OnScrollListener { private final static String TAG = "PullToRefreshListView"; // 下拉刷新標志 private final static int PULL_To_REFRESH = 0; // 松開刷新標志 private final static int RELEASE_To_REFRESH = 1; // 正在刷新標志 private final static int REFRESHING = 2; // 刷新完成標志 private final static int DONE = 3; private LayoutInflater inflater; 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 headContentOriginalTopPadding; private int startY; private int firstItemIndex; private int currentScrollState; private int state; private boolean isBack; public OnRefreshListener refreshListener; public PullToRefreshListView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { //設置滑動效果 animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); animation.setInterpolator(new LinearInterpolator()); animation.setDuration(100); 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(100); reverseAnimation.setFillAfter(true); inflater = LayoutInflater.from(context); headView = (LinearLayout) inflater.inflate(R.layout.pull_to_refresh_head, null); arrowImageView = (ImageView) headView.findViewById(R.id.head_arrowImageView); arrowImageView.setMinimumWidth(50); 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); headContentOriginalTopPadding = headView.getPaddingTop(); measureView(headView); headContentHeight = headView.getMeasuredHeight(); headContentWidth = headView.getMeasuredWidth(); headView.setPadding(headView.getPaddingLeft(), -1 * headContentHeight, headView.getPaddingRight(), headView.getPaddingBottom()); headView.invalidate(); //System.out.println("初始高度:"+headContentHeight); //System.out.println("初始TopPad:"+headContentOriginalTopPadding); addHeaderView(headView); setOnScrollListener(this); } public void onScroll(AbsListView view, int firstVisiableItem, int visibleItemCount, int totalItemCount) { firstItemIndex = firstVisiableItem; } public void onScrollStateChanged(AbsListView view, int scrollState) { currentScrollState = scrollState; } public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (firstItemIndex == 0 && !isRecored) { startY = (int) event.getY(); isRecored = true; //System.out.println("當前-按下高度-ACTION_DOWN-Y:"+startY); } break; case MotionEvent.ACTION_CANCEL://失去焦點&取消動作 case MotionEvent.ACTION_UP: if (state != REFRESHING) { if (state == DONE) { //System.out.println("當前-抬起-ACTION_UP:DONE什么都不做"); } else if (state == PULL_To_REFRESH) { state = DONE; changeHeaderViewByState(); //System.out.println("當前-抬起-ACTION_UP:PULL_To_REFRESH-->DONE-由下拉刷新狀態到刷新完成狀態"); } else if (state == RELEASE_To_REFRESH) { state = REFRESHING; changeHeaderViewByState(); onRefresh(); //System.out.println("當前-抬起-ACTION_UP:RELEASE_To_REFRESH-->REFRESHING-由松開刷新狀態,到刷新完成狀態"); } } isRecored = false; isBack = false; break; case MotionEvent.ACTION_MOVE: int tempY = (int) event.getY(); //System.out.println("當前-滑動-ACTION_MOVE Y:"+tempY); if (!isRecored && firstItemIndex == 0) { //System.out.println("當前-滑動-記錄拖拽時的位置 Y:"+tempY); isRecored = true; startY = tempY; } if (state != REFRESHING && isRecored) { // 可以松開刷新了 if (state == RELEASE_To_REFRESH) { // 往上推,推到屏幕足夠掩蓋head的程度,但還沒有全部掩蓋 if ((tempY - startY < headContentHeight+20) && (tempY - startY) > 0) { state = PULL_To_REFRESH; changeHeaderViewByState(); //System.out.println("當前-滑動-ACTION_MOVE:RELEASE_To_REFRESH--》PULL_To_REFRESH-由松開刷新狀態轉變到下拉刷新狀態"); } // 一下子推到頂 else if (tempY - startY <= 0) { state = DONE; changeHeaderViewByState(); //System.out.println("當前-滑動-ACTION_MOVE:RELEASE_To_REFRESH--》DONE-由松開刷新狀態轉變到done狀態"); } // 往下拉,或者還沒有上推到屏幕頂部掩蓋head else { // 不用進行特別的操作,只用更新paddingTop的值就行了 } } // 還沒有到達顯示松開刷新的時候,DONE或者是PULL_To_REFRESH狀態 else if (state == PULL_To_REFRESH) { // 下拉到可以進入RELEASE_TO_REFRESH的狀態 if (tempY - startY >= headContentHeight+20 && currentScrollState == SCROLL_STATE_TOUCH_SCROLL) { state = RELEASE_To_REFRESH; isBack = true; changeHeaderViewByState(); //System.out.println("當前-滑動-PULL_To_REFRESH--》RELEASE_To_REFRESH-由done或者下拉刷新狀態轉變到松開刷新"); } // 上推到頂了 else if (tempY - startY <= 0) { state = DONE; changeHeaderViewByState(); //System.out.println("當前-滑動-PULL_To_REFRESH--》DONE-由Done或者下拉刷新狀態轉變到done狀態"); } } // done狀態下 else if (state == DONE) { if (tempY - startY > 0) { state = PULL_To_REFRESH; changeHeaderViewByState(); //System.out.println("當前-滑動-DONE--》PULL_To_REFRESH-由done狀態轉變到下拉刷新狀態"); } } // 更新headView的size if (state == PULL_To_REFRESH) { int topPadding = (int)((-1 * headContentHeight + (tempY - startY))); headView.setPadding(headView.getPaddingLeft(), topPadding, headView.getPaddingRight(), headView.getPaddingBottom()); headView.invalidate(); //System.out.println("當前-下拉刷新PULL_To_REFRESH-TopPad:"+topPadding); } // 更新headView的paddingTop if (state == RELEASE_To_REFRESH) { int topPadding = (int)((tempY - startY - headContentHeight)); headView.setPadding(headView.getPaddingLeft(), topPadding, headView.getPaddingRight(), headView.getPaddingBottom()); headView.invalidate(); //System.out.println("當前-釋放刷新RELEASE_To_REFRESH-TopPad:"+topPadding); } } 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); if (isBack) { isBack = false; arrowImageView.clearAnimation(); arrowImageView.startAnimation(reverseAnimation); } tipsTextview.setText("下拉可以刷新"); //Log.v(TAG, "當前狀態,下拉刷新"); break; case REFRESHING: //System.out.println("刷新REFRESHING-TopPad:"+headContentOriginalTopPadding); headView.setPadding(headView.getPaddingLeft(), headContentOriginalTopPadding, headView.getPaddingRight(), headView.getPaddingBottom()); headView.invalidate(); progressBar.setVisibility(View.VISIBLE); arrowImageView.clearAnimation(); arrowImageView.setVisibility(View.GONE); tipsTextview.setText("加載中..."); lastUpdatedTextView.setVisibility(View.GONE); //Log.v(TAG, "當前狀態,正在刷新..."); break; case DONE: //System.out.println("完成DONE-TopPad:"+(-1 * headContentHeight)); headView.setPadding(headView.getPaddingLeft(), -1 * headContentHeight, headView.getPaddingRight(), headView.getPaddingBottom()); headView.invalidate(); progressBar.setVisibility(View.GONE); arrowImageView.clearAnimation(); // 此處更換圖標 arrowImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow); tipsTextview.setText("下拉可以刷新"); lastUpdatedTextView.setVisibility(View.VISIBLE); //Log.v(TAG, "當前狀態,done"); break; } } //點擊刷新 public void clickRefresh() { setSelection(0); state = REFRESHING; changeHeaderViewByState(); onRefresh(); } public void setOnRefreshListener(OnRefreshListener refreshListener) { this.refreshListener = refreshListener; } public interface OnRefreshListener { public void onRefresh(); } public void onRefreshComplete(String update) { lastUpdatedTextView.setText(update); onRefreshComplete(); } public void onRefreshComplete() { state = DONE; changeHeaderViewByState(); } private void onRefresh() { if (refreshListener != null) { refreshListener.onRefresh(); } } // 計算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); } }
布局 pull_to_refresh_head.xml
ic_pulltorefresh_arrow 圖片名
<?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="fill_parent" android:orientation="vertical"> <RelativeLayout android:id="@+id/head_contentLayout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:paddingTop="10dip" android:paddingBottom="15dip"> <FrameLayout android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_marginLeft="30dip" android:layout_marginRight="20dip"> <ImageView android:id="@+id/head_arrowImageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/ic_pulltorefresh_arrow" /> </FrameLayout> <FrameLayout android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:paddingTop="10dip" android:paddingBottom="15dip" android:layout_marginLeft="100dip" android:layout_marginRight="10dip"> <ProgressBar android:id="@+id/head_progressBar" style="@style/loading_small" android:visibility="gone"/> </FrameLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center_horizontal" android:orientation="vertical"> <TextView android:id="@+id/head_tipsTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉可以刷新" android:textColor="@color/black"/> <TextView android:id="@+id/head_lastUpdatedTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/black" android:textSize="10sp" /> </LinearLayout> </RelativeLayout> </LinearLayout>
調用下拉刷新方法
lvNewsAdapter = new ListViewNewsAdapter(this, lvNewsData, R.layout.news_listitem); lvNews_footer = getLayoutInflater().inflate(R.layout.listview_footer, null); lvNews_foot_more = (TextView)lvNews_footer.findViewById(R.id.listview_foot_more); lvNews_foot_progress = (ProgressBar)lvNews_footer.findViewById(R.id.listview_foot_progress); lvNews = (PullToRefreshListView)findViewById(R.id.frame_listview_news); lvNews.addFooterView(lvNews_footer);//添加底部視圖 必須在setAdapter前 lvNews.setAdapter(lvNewsAdapter); lvNews.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //點擊頭部、底部欄無效 if(position == 0 || view == lvNews_footer) return; News news = null; //判斷是否是TextView if(view instanceof TextView){ news = (News)view.getTag(); }else{ TextView tv = (TextView)view.findViewById(R.id.news_listitem_title); news = (News)tv.getTag(); } if(news == null) return; //跳轉到新聞詳情 UIHelper.showNewsRedirect(view.getContext(), news); } }); lvNews.setOnScrollListener(new AbsListView.OnScrollListener() { public void onScrollStateChanged(AbsListView view, int scrollState) { lvNews.onScrollStateChanged(view, scrollState); //數據為空--不用繼續下面代碼了 if(lvNewsData.isEmpty()) return; //判斷是否滾動到底部 boolean scrollEnd = false; try { if(view.getPositionForView(lvNews_footer) == view.getLastVisiblePosition()) scrollEnd = true; } catch (Exception e) { scrollEnd = false; } int lvDataState = toInt(lvNews.getTag()); if(scrollEnd && lvDataState==1) { lvNews.setTag(2); lvNews_foot_more.setText("加載中···"); lvNews_foot_progress.setVisibility(View.VISIBLE); //當前pageIndex int pageIndex = lvNewsSumData/AppContext.PAGE_SIZE; loadLvNewsData(curNewsCatalog, pageIndex, lvNewsHandler, UIHelper.LISTVIEW_ACTION_SCROLL); } } public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) { lvNews.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } }); lvNews.setOnRefreshListener(new PullToRefreshListView.OnRefreshListener() { public void onRefresh() { loadLvNewsData(curNewsCatalog, 0, lvNewsHandler, UIHelper.LISTVIEW_ACTION_REFRESH); } });
listview_footer.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:padding="3dp"> <ProgressBar android:id="@+id/listview_foot_progress" style="@style/loading_small"/> <TextView android:id="@+id/listview_foot_more" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:padding="5dp" android:textColor="@color/listitem_black" android:text="@string/load_ing"/> </LinearLayout>
/**
* 對象轉整數
* @param obj
* @return 轉換異常返回 0
*/
public static int toInt(Object obj) {
if(obj==null) return 0;
return toInt(obj.toString(),0);
}