Android之下拉刷新的ListView
不废话,代码里面注释很详细,直接上代码:
自定义的RefreshableListView代码:
1 public class RefreshableListView extends ListView implements OnScrollListener { 2 private View header; // ListView顶部布局 3 private LayoutInflater inflater; 4 private int headerHeight; // 顶部布局Header的高度 5 private int firstVisisblePosition; // 当前第一个可见的Item的位置 6 private int scrollState; // ListView当前的滚动状态 7 8 private boolean remarkTop; // 标记,当前是在ListView的最顶端按下的 9 private int startY; // 手指按下时的Y值 10 11 private int state; // 指示当前的状态 12 private final int STATE_NORMAL = 0; // 正常状态 13 private final int STATE_PULL = 1; // 提示“下拉可以刷新”的状态 14 private final int STATE_TOREFRESH = 2; // 提示“松开手指刷新”的状态 15 private final int STATE_REFRESHING = 3; // 正在刷新的状态 16 17 // Header布局中的四个控件 18 private TextView refreshTip; // 显示“下拉可以刷新”/“松开手指刷新”的TextView 19 private TextView timeTip; // 显示上次刷新的时间的TextView 20 private ImageView arrowImg; // 向上/向下的箭头的ImageView 21 private ProgressBar progressBar; // 刷新数据时用到的ProgressBar 22 23 private ListViewRefreshListener listener; // 刷新数据的接口 24 25 // 自定义控件都必须实现以下三个构造方法(一个参数、两个参数、三个参数的构造方法) 26 // 我们在一个参数的构造方法中调用两个参数的构造方法,在两个参数的构造方法中调用三个参数的构造方法,这样不管我们用哪个构造方法,最终的调用代码是一样的 27 // 一个参数的构造方法:这个方法是在Activity中根据上下文环境直接生成控件时调用的 28 public RefreshableListView(Context context) { 29 this(context, null); 30 } 31 32 // 两个参数的构造方法:这个方法是在使用了系统属性,没有使用自定义属性时调用的 33 public RefreshableListView(Context context, AttributeSet attrs) { 34 this(context, attrs, 0); 35 } 36 37 // 三个参数的构造方法:这个方法是在使用了自定义属性时调用的 38 public RefreshableListView(Context context, AttributeSet attrs, int defStyleAttr) { 39 super(context, attrs, defStyleAttr); 40 initView(context); 41 // 找到Header中的控件 42 refreshTip = (TextView) header.findViewById(R.id.control_header_refreshtip); 43 timeTip = (TextView) header.findViewById(R.id.control_header_timetip); 44 arrowImg = (ImageView) header.findViewById(R.id.control_header_refresharrow); 45 progressBar = (ProgressBar) header.findViewById(R.id.control_header_progressbar); 46 } 47 48 // 初始化界面,添加顶部布局文件到ListView中 49 private void initView(Context context) { 50 inflater = LayoutInflater.from(context); 51 header = inflater.inflate(R.layout.sideworks_layout_header, null); 52 // 测量顶部布局header的高度 53 measureView(context); 54 headerHeight = header.getMeasuredHeight(); 55 setViewTopPadding(-headerHeight); // 设置ListView的上缩进:是负值,表示将header布局缩到屏幕外面去 56 // 把顶部布局添加到ListView的最上面 57 this.addHeaderView(header); 58 // 设置向下滑动时逐渐显示顶部布局(接口回掉方法) 59 this.setOnScrollListener(this); 60 } 61 62 // 测量控件的宽高(通知父佈局:我佔用的寬和高) 63 private void measureView(Context context) { 64 ViewGroup.LayoutParams lp = header.getLayoutParams(); // 获取header布局的宽高属性 65 if (lp == null) { 66 lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 67 } 68 int width = ViewGroup.getChildMeasureSpec(0, 0, lp.width); 69 int height; // 不能用getChildMeasureSpec方法获取高度的原因是ListView的高度不确定,而宽度是确定的 70 int tempHeight = lp.height; 71 if (tempHeight > 0) { // 大于0说明定义了ListView的高度,所以我们用精确布局模式EXACTLY 72 height = MeasureSpec.makeMeasureSpec(tempHeight, MeasureSpec.EXACTLY); 73 } else { // 如果不大于0,则表示没有定义ListView的高度,即UNSPECIFIED 74 height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 75 } 76 header.measure(width, height); // 这行代码很容易报错:NullPointerException,所以SDK17以前的版本必须将布局的最外层设置为LinearLayout 77 } 78 79 // 设置ListView的TopPadding属性 80 private void setViewTopPadding(int topPadding) { 81 this.setPadding(this.getPaddingLeft(), topPadding, this.getPaddingRight(), this.getPaddingBottom()); 82 this.invalidate(); // invalidate()方法的作用是请求对该控件进行重绘 83 } 84 85 @Override 86 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 87 this.firstVisisblePosition = firstVisibleItem; 88 } 89 90 @Override 91 public void onScrollStateChanged(AbsListView view, int scrollState) { 92 this.scrollState = scrollState; 93 } 94 95 @Override 96 public boolean onTouchEvent(MotionEvent ev) { 97 switch (ev.getAction()) { 98 case MotionEvent.ACTION_DOWN: 99 if (firstVisisblePosition == 0) { 100 remarkTop = true; 101 startY = (int) ev.getY(); // 如果按下时是处在ListView最上面的Item,则记录当前的Y坐标值 102 } 103 break; 104 case MotionEvent.ACTION_MOVE: 105 onMove(ev); 106 break; 107 case MotionEvent.ACTION_UP: 108 if (state == STATE_TOREFRESH) { // 滑动到了“松开手指刷新数据”的高度 109 state = STATE_REFRESHING; 110 refreshViewByState(); 111 listener.refreshListView(); // 调用接口,刷新数据 112 } else if (state == STATE_PULL) { // 还是处在“下拉刷新数据”的高度 113 state = STATE_NORMAL; 114 remarkTop = false; 115 refreshViewByState(); 116 } 117 break; 118 } 119 return super.onTouchEvent(ev); 120 } 121 122 // 判断移动过程中的操作 123 private void onMove(MotionEvent ev) { 124 if (!remarkTop) { // 如果按下地点不是ListView的第一个Item,则不做处理,正常滑动 125 return; 126 } 127 int tempY = (int) ev.getY(); // 当前移动到了什么位置(Y坐标值) 128 int space = tempY - startY; // 判断当前移动了多大距离(即header布局被拉下来多少),向下拉时是正值 129 int topPadding = space - headerHeight; // 当前还在屏幕外面的header布局的高度 130 switch (state) { 131 case STATE_NORMAL: 132 if (space > 0) { 133 state = STATE_PULL; 134 refreshViewByState(); 135 } 136 break; 137 case STATE_PULL: 138 setViewTopPadding(topPadding); 139 if (space > headerHeight && scrollState == SCROLL_STATE_TOUCH_SCROLL) { // 滑动过header高度的一半并且仍然在滑动 140 state = STATE_TOREFRESH; 141 refreshViewByState(); 142 } 143 break; 144 case STATE_TOREFRESH: 145 setViewTopPadding(topPadding); 146 if (space < headerHeight) { 147 state = STATE_PULL; 148 refreshViewByState(); 149 } else if (space <= 0) { 150 state = STATE_NORMAL; 151 remarkTop = false; 152 refreshViewByState(); 153 } 154 break; 155 } 156 } 157 158 // 根据当前状态,改变界面显示 159 private void refreshViewByState() { 160 // 箭头反转的两个动画 161 RotateAnimation anim1 = new RotateAnimation(0, 180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); 162 anim1.setDuration(500); 163 anim1.setFillAfter(true); 164 RotateAnimation anim2 = new RotateAnimation(180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); 165 anim2.setDuration(500); 166 anim2.setFillAfter(true); 167 168 switch (state) { 169 case STATE_NORMAL: 170 setViewTopPadding(-headerHeight); 171 arrowImg.clearAnimation(); 172 break; 173 case STATE_PULL: 174 arrowImg.setVisibility(View.VISIBLE); 175 progressBar.setVisibility(View.GONE); 176 refreshTip.setText("下拉可以刷新!"); 177 arrowImg.clearAnimation(); 178 arrowImg.setAnimation(anim2); 179 break; 180 case STATE_TOREFRESH: 181 arrowImg.setVisibility(View.VISIBLE); 182 progressBar.setVisibility(View.GONE); 183 refreshTip.setText("松开立即刷新!"); 184 arrowImg.clearAnimation(); 185 arrowImg.setAnimation(anim1); 186 break; 187 case STATE_REFRESHING: 188 setViewTopPadding(0); 189 arrowImg.setVisibility(View.GONE); 190 progressBar.setVisibility(View.VISIBLE); 191 refreshTip.setText("正在刷新......"); 192 arrowImg.clearAnimation(); 193 break; 194 } 195 } 196 197 public void onRefreshComplete() { 198 state = STATE_NORMAL; 199 remarkTop = false; 200 refreshViewByState(); 201 String time = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()); 202 timeTip.setText(time); 203 } 204 205 // 刷新数据的接口,要通过接口回掉的方式更新数据 206 public interface ListViewRefreshListener { 207 public void refreshListView(); 208 } 209 210 public void setListViewRefreshListener(ListViewRefreshListener listener) { 211 this.listener = listener; 212 } 213 }
header布局界面sideworks_layout_header.xml代码:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="50.0dip" 5 android:background="@color/cl_header_bg" 6 android:gravity="center" 7 android:padding="10.0dip" > 8 9 <RelativeLayout 10 android:layout_width="wrap_content" 11 android:layout_height="wrap_content" 12 android:background="@color/cl_transparent" > 13 14 <ImageView 15 android:id="@+id/control_header_refresharrow" 16 android:layout_width="wrap_content" 17 android:layout_height="35.0dip" 18 android:layout_centerVertical="true" 19 20 android:layout_marginRight="15.0dip" 21 android:contentDescription="@string/app_name" 22 android:src="@drawable/refresh_arrow" /> 23 24 <ProgressBar 25 android:id="@+id/control_header_progressbar" 26 style="?android:attr/progressBarStyleSmall" 27 android:layout_width="wrap_content" 28 android:layout_height="wrap_content" 29 android:layout_centerVertical="true" 30 android:layout_marginRight="15.0dip" 31 android:visibility="gone" /> 32 33 <LinearLayout 34 android:id="@+id/position_header_tips" 35 android:layout_width="wrap_content" 36 android:layout_height="wrap_content" 37 android:layout_centerVertical="true" 38 android:orientation="vertical" 39 android:paddingLeft="30.0dip" > 40 41 42 <TextView 43 android:id="@+id/control_header_refreshtip" 44 android:layout_width="wrap_content" 45 android:layout_height="wrap_content" 46 android:text="@string/str_header_refreshtip" 47 android:textColor="@color/cl_black" 48 android:textSize="12.0sp" /> 49 50 <TextView 51 android:id="@+id/control_header_timetip" 52 android:layout_width="wrap_content" 53 android:layout_height="wrap_content" 54 android:layout_marginTop="-7.0dip" 55 android:textColor="@color/cl_black" 56 android:textSize="12.0sp" /> 57 </LinearLayout> 58 </RelativeLayout> 59 60 </LinearLayout>
主界面布局activity_main.xml代码:
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" > 5 6 <com.view.RefreshableListView 7 android:id="@+id/control_main_listview" 8 android:layout_width="match_parent" 9 android:layout_height="match_parent" 10 android:cacheColorHint="@color/cl_transparent" /> 11 12 </RelativeLayout>
主界面MainActivity.java代码:
1 public class MainActivity extends Activity implements ListViewRefreshListener { 2 private RefreshableListView testList; 3 public static List<String> dataList; 4 public static ArrayAdapter<String> listAdapter; 5 6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.activity_main); 10 initView(); 11 } 12 13 private void initView() { 14 testList = (RefreshableListView) findViewById(R.id.control_main_listview); 15 testList.setListViewRefreshListener(this); 16 dataList = getData(); 17 listAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1, dataList); 18 testList.setAdapter(listAdapter); 19 } 20 21 private List<String> getData() { 22 dataList = new ArrayList<String>(); 23 for (int i = 0; i < 10; i++) { 24 dataList.add("This is a test data."); 25 } 26 return dataList; 27 } 28 29 @Override 30 public void refreshListView() { 31 // 延时两秒后显示两条新数据:This is a new data. 32 new Handler().postDelayed(new Runnable() { 33 public void run() { 34 for (int i = 0; i < 2; i++) { 35 dataList.add(0, "This is a new data."); 36 } 37 listAdapter.notifyDataSetChanged(); 38 testList.onRefreshComplete(); 39 } 40 }, 2000); 41 } 42 }