背景知識
ListView使用非常廣泛,對於使用ListView的應用來說,下拉刷新是必不可少要實現的功能。
我們常用的微博、網易新聞,搜狐新聞都使用了這一功能,如下圖所示。
微博
搜狐新聞
具體學習:
首先分析下拉刷新的具體操作過程:
用戶手指在ListView上按下並往下拉----->出現一個提示的View在ListView頂部----->ListView內容更新,頂部的提示View消失
具體實現步驟:
1.創建繼承自ListView的RefreshListView,並添加頂部提示View
public class RefreshListView extends ListView { View header;// 頂部提示View public RefreshListView(Context context) { super(context); initView(context); } public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } private void initView(Context context) { // LayoutInflater作用是加載布局 LayoutInflater inflater = LayoutInflater.from(context); header = inflater.inflate(R.layout.header_layout, null); this.addHeaderView(header); } }
頂部提示View布局文件 header_layout.xml:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <RelativeLayout 8 android:layout_width="match_parent" 9 android:layout_height="wrap_content" 10 android:paddingTop="10dip" 11 android:paddingBottom="10dip" 12 > 13 <LinearLayout 14 android:layout_width="wrap_content" 15 android:layout_height="wrap_content" 16 android:orientation="vertical" 17 android:id="@+id/layout" 18 android:layout_centerInParent="true" 19 android:gravity="center" 20 > 21 <!-- 提示文字 --> 22 <TextView 23 android:id="@+id/tips" 24 android:layout_width="wrap_content" 25 android:layout_height="wrap_content" 26 android:text="下拉可以刷新" 27 /> 28 <!-- 距上次更新至今的時間 --> 29 <TextView 30 android:id="@+id/time" 31 android:layout_width="wrap_content" 32 android:layout_height="wrap_content" 33 /> 34 35 </LinearLayout> 36 <!-- 箭頭 --> 37 <ImageView 38 android:id="@+id/arrow" 39 android:layout_width="wrap_content" 40 android:layout_height="wrap_content" 41 android:layout_toLeftOf="@id/layout" 42 android:src="@drawable/pull_to_refresh_arrow" 43 /> 44 <!-- 更新進度條 --> 45 <ProgressBar 46 android:id="@+id/progress" 47 android:layout_width="wrap_content" 48 android:layout_height="wrap_content" 49 style="?android:attr/progressBarStyleSmall" 50 android:layout_toLeftOf="@id/layout" 51 android:visibility="gone" 52 /> 53 </RelativeLayout> 54 </LinearLayout>
運行效果圖:
2.由上圖可以知道,默認加載header是顯示出來的。默認我們應該隱藏頂部提示布局header。
1 private void initView(Context context){ 2 //LayoutInflater作用是加載布局 3 LayoutInflater inflater = LayoutInflater.from(context); 4 header = inflater.inflate(R.layout.header_layout, null); 5 measureView(header); 6 headerHeight = header.getMeasuredHeight(); 7 topPadding(-headerHeight); 8 this.addHeaderView(header); 9 } 10 /** 11 * 設置頂部布局的上邊距 12 * @param topPadding 13 */ 14 private void topPadding(int topPadding){ 15 //設置頂部提示的邊距 16 //除了頂部用參數值topPadding外,其他三個用header默認的值 17 header.setPadding(header.getPaddingLeft(), topPadding, 18 header.getPaddingRight(), header.getPaddingBottom()); 19 //使header無效,將來調用onDraw()重繪View 20 header.invalidate(); 21 } 22 /** 23 * 通知父布局,占用的寬和高 24 */ 25 private void measureView(View view){ 26 //LayoutParams are used by views to tell their parents 27 //how they want to be laid out. 28 //LayoutParams被view用來告訴它們的父布局它們應該被怎樣安排 29 ViewGroup.LayoutParams p = view.getLayoutParams(); 30 if(p==null){ 31 //兩個參數:width,height 32 p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 33 ViewGroup.LayoutParams.WRAP_CONTENT); 34 } 35 //getChildMeasureSpec:獲取子View的widthMeasureSpec、heightMeasureSpec值 36 int width = ViewGroup.getChildMeasureSpec(0, 0, p.width); 37 int height; 38 int tempHeight = p.height; 39 if(tempHeight>0){ 40 height = MeasureSpec.makeMeasureSpec(tempHeight, MeasureSpec.EXACTLY); 41 }else{ 42 height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 43 } 44 view.measure(width, height); 45 }
運行效果圖:
3.監聽用戶滑動屏幕操作
1.實現OnScrollListener接口
2.根據用戶按下、移動、抬起手勢來設置不同的響應事件
3.頂部提示View--header有四種狀態:
1. 正常狀態(NONE)
2.提示用戶下拉可以刷新(PULL)
3.提示用戶釋放可以刷新(RELEASE)
4.提示用戶正在刷新狀態(REFREASH)
1 public class RefreshListView extends ListView implements OnScrollListener{ 2 View header;//下拉刷新時出現的頂部布局View 3 int headerHeight;//header的高度 4 int firstVisibleItem;//當前界面第一個可見的item的位置 5 6 boolean isFlag;//標志,是在當前顯示的listView是在listView最頂端時按下額 7 int startY;//用戶按下的Y值 8 9 10 int state;//當前狀態 11 final int NONE = 0;//正常狀態 12 final int PULL = 1;//提示下拉狀態 13 final int RELEASE = 2;//提示釋放狀態 14 final int REFRESH = 3;//提示正在刷新狀態 15 16 17 @Override 18 public void onScrollStateChanged(AbsListView view, int scrollState) { 19 // TODO Auto-generated method stub 20 this.scrollState = scrollState; 21 } 22 @Override 23 public void onScroll(AbsListView view, int firstVisibleItem, 24 int visibleItemCount, int totalItemCount) { 25 // TODO Auto-generated method stub 26 this.firstVisibleItem = firstVisibleItem; 27 } 28 @Override 29 public boolean onTouchEvent(MotionEvent ev) { 30 // TODO Auto-generated method stub 31 switch (ev.getAction()) { 32 case MotionEvent.ACTION_DOWN: 33 if(firstVisibleItem == 0){ 34 isFlag = true;//ListView最頂端按下,標志值設為真 35 startY = (int)ev.getY(); 36 } 37 break; 38 case MotionEvent.ACTION_MOVE: 39 onMove(ev); 40 break; 41 case MotionEvent.ACTION_UP: 42 if(state == RELEASE){ 43 state = REFRESH; 44 //加載數據 45 refreshViewByState(); 46 iRefreshlistener.onRefresh(); 47 }else if(state == PULL){ 48 state = NONE; 49 isFlag = false; 50 refreshViewByState(); 51 } 52 break; 53 } 54 return super.onTouchEvent(ev); 55
onMove()方法----->根據用戶下拉距離的不同設置不同的響應方法
1 private void onMove(MotionEvent ev){ 2 //如果不是最頂端按下,則直接返回 3 if(!isFlag){ 4 return; 5 } 6 int currentY = (int)ev.getY();//當前的Y值 7 int space = currentY - startY;//用戶向下拉的距離 8 int topPadding = space - headerHeight;//頂部提示View距頂部的距離值 9 switch (state) { 10 //正常狀態 11 case NONE: 12 if(space>0){ 13 state = PULL;//下拉的距離大於0,則將狀態改為PULL(提示下拉更新) 14 refreshViewByState();//根據狀態的不同更新View 15 } 16 break; 17 case PULL: 18 topPadding(topPadding); 19 if(space>headerHeight+30//下拉的距離大於header的高度加30且用戶滾動屏幕,手指仍在屏幕上 20 &&scrollState == SCROLL_STATE_TOUCH_SCROLL ){ 21 state = RELEASE;//將狀態改為RELEASE(提示松開更新) 22 refreshViewByState(); 23 } 24 break; 25 case RELEASE: 26 topPadding(topPadding); 27 if(space<headerHeight+30){//用戶下拉的距離不夠 28 state = PULL; //將狀態改為PULL 29 refreshViewByState(); 30 }else if(space<=0){ //用戶下拉的距離為非正值 31 state = NONE; //將狀態改為NONE 32 isFlag = false; //標志改為false 33 refreshViewByState(); 34 } 35 break; 36 } 37 }
根據不同狀態,改變用戶界面的顯示
1 /** 2 * 根據當前狀態state,改變界面顯示 3 * state: 4 * NONE:無操作 5 * PULL:下拉可以刷新 6 * RELEASE:松開可以刷新 7 * REFREASH:正在刷新 8 */ 9 private void refreshViewByState(){ 10 //提示 11 TextView tips = (TextView)header.findViewById(R.id.tips); 12 //箭頭 13 ImageView arrow = (ImageView)header.findViewById(R.id.arrow); 14 //進度條 15 ProgressBar progress = (ProgressBar)header.findViewById(R.id.progress); 16 //箭頭的動畫效果1,由0度轉向180度,即箭頭由朝下轉為朝上 17 RotateAnimation animation1 = new RotateAnimation(0, 180, 18 RotateAnimation.RELATIVE_TO_SELF,0.5f, 19 RotateAnimation.RELATIVE_TO_SELF,0.5f); 20 animation1.setDuration(500); 21 animation1.setFillAfter(true); 22 //箭頭的動畫效果2,由180度轉向0度,即箭頭由朝上轉為朝下 23 RotateAnimation animation2 = new RotateAnimation(180, 0, 24 RotateAnimation.RELATIVE_TO_SELF,0.5f, 25 RotateAnimation.RELATIVE_TO_SELF,0.5f); 26 animation2.setDuration(500); 27 animation2.setFillAfter(true); 28 29 switch (state) { 30 case NONE: //正常狀態 31 arrow.clearAnimation(); //清除箭頭動畫效果 32 topPadding(-headerHeight); //設置header距離頂部的距離 33 break; 34 35 case PULL: //下拉狀態 36 arrow.setVisibility(View.VISIBLE); //箭頭設為可見 37 progress.setVisibility(View.GONE); //進度條設為不可見 38 tips.setText("下拉可以刷新"); //提示文字設為"下拉可以刷新" 39 arrow.clearAnimation(); //清除之前的動畫效果,並將其設置為動畫效果2 40 arrow.setAnimation(animation2); 41 break; 42 43 case RELEASE: //下拉狀態 44 arrow.setVisibility(View.VISIBLE); //箭頭設為可見 45 progress.setVisibility(View.GONE); //進度條設為不可見 46 tips.setText("松開可以刷新"); //提示文字設為"松開可以刷新" 47 arrow.clearAnimation(); //清除之前的動畫效果,並將其設置為動畫效果2 48 arrow.setAnimation(animation1); 49 break; 50 51 case REFRESH: //更新狀態 52 topPadding(50); //距離頂部的距離設置為50 53 arrow.setVisibility(View.GONE); //箭頭設為不可見 54 progress.setVisibility(View.VISIBLE); //進度條設為可見 55 tips.setText("正在刷新..."); //提示文字設為""正在刷新..." 56 arrow.clearAnimation(); //清除動畫效果 57 break; 58 59 } 60 }
4.更新數據 由於在RefreshListView中不能直接更新數據,必須設置回調接口來實現更新數據這一功能。
IRefreshListener iRefreshlistener;//刷新數據的接口 ... public void setInterface(IRefreshListener listener){ this.iRefreshlistener = listener; } /** * 刷新數據接口 * @author lenovo * */ public interface IRefreshListener{ public void onRefresh(); }
在MainActivity中實現IRefreshListener接口並實現onRefresh()方法
public class MainActivity extends Activity implements IRefreshListener{ ...... @Override public void onRefresh() { // TODO Auto-generated method stub //handler設置刷新延時效果 Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { // TODO Auto-generated method stub //獲取最新數據 getRefreshData(); //通知界面顯示 adapter.notifyDataSetChanged(); //通知listView刷新數據完畢 listView.refreshComplete(); } }, 2000); }
刷新完成的操作:
1 public void refreshComplete(){ 2 state = NONE; //狀態設為正常狀態 3 isFlag = false; //標志設為false 4 refreshViewByState(); 5 //設置提示更新時間間隔 6 Time t = new Time(); 7 t.setToNow(); 8 time = t.hour*60+t.minute-updateTime; 9 updateTime = t.hour*60+t.minute; 10 TextView lastUpdateTime = (TextView)findViewById(R.id.time); 11 lastUpdateTime.setText(time+"分鍾前更新"); 12 }
Demo運行效果圖
想及時獲取最新最有用的Android開發干貨,請關注我
轉載請注明網址:http://www.cnblogs.com/JohnTsai
如果覺得本文對你的學習工作有所幫助,不妨在右下方點推薦一下,謝謝。
聯系我:JohnTsai.Work@gmail.com