该准备的东西都已经准备好了。在这篇文章里,我们就开始实现下拉刷新功能吧。
一、大体的逻辑分析
我们来简单分析一下需要做的逻辑吧。首先分析头布局有几种状态。不下拉时,为正常状态,此时头布局隐藏。下拉到一定高度,提示信息变为“下拉刷新”,箭头朝下,此为下拉状态。再往下拉,提示信息变为“松开刷新”,箭头朝上,此为提示刷新状态。而此时松开手指,则执行刷新操作,头布局变为进度条显示,箭头消失,此为正在刷新状态。相反的,其他状态下松开手指,都不执行刷新操作,应该将头布局恢复到正常状态。因为可确定头布局的状态有四种。
我们根据这四种状态,确定我们要做的事情。要监听ListView的滚动,故要实现OnScrollListener接口。还要监听手指触摸事件,根据手指的下拉移动来改变头布局的显示效果,根据手指的抬起来判断是否进行刷新操作,因为要实现onTouchEvent方法。也就是说,头布局状态的改变应该随着手指的移动而改变,因此在onTouchEvent里面我们要实现上面分析的四种状态的改变。当然,状态改变就意味着头布局显示效果的改变,这里可以嵌套在onTouchEvent方法里面。但考虑到避免方法臃肿,以及其他地方可能也需要改变头布局界面,比如数据加载完成后等情况,因此专门将头布局界面的改变抽取出来,凝聚为一个方法。
然后就是数据刷新,刷新操作要在MyListView里执行,但是数据要在MainActivity中获取。老规矩,用接口回调即可。
好了,基本上大体的逻辑就这么多了。下面我们将上面的分析转化为代码。
二、代码编写
废话我就不多说了,上面的分析很清楚了。继续完善MyListView即可。代码如下:
1 package com.fuly.load; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.view.LayoutInflater; 6 import android.view.MotionEvent; 7 import android.view.View; 8 import android.view.ViewGroup; 9 import android.widget.AbsListView; 10 import android.widget.AbsListView.OnScrollListener; 11 import android.widget.ImageView; 12 import android.widget.ListView; 13 import android.widget.ProgressBar; 14 import android.widget.TextView; 15 16 public class MyListView extends ListView implements OnScrollListener{ 17 18 private View header;//头布局 19 20 private int headerHeight;//头布局自身的高度 21 22 private int scrollState;//当前滚动状态 23 private int firstVisibleItem;//当前可见的第一个item 24 private int startY;//刚开始触摸屏幕时的Y值 25 26 private int curState = 0;//当前header状态,默认为0 27 private final int NORMAL = 0;//正常状态 28 private final int PULL = 1;//状态下拉 29 private final int RELEASE = 2;//提示刷新状态 30 private final int RELEASING = 3;//状态正在刷新 31 32 private boolean canPull = false;//是否可以执行下拉操作 33 34 private refresfListener mListener;//回调接口 35 36 37 //三个构造方法都要重写 38 public MyListView(Context context) { 39 super(context); 40 initView( context); 41 42 } 43 public MyListView(Context context, AttributeSet attrs) { 44 super(context, attrs); 45 initView( context); 46 47 } 48 public MyListView(Context context, AttributeSet attrs, int defStyle) { 49 super(context, attrs, defStyle); 50 initView( context); 51 52 } 53 54 //定义回调接口 55 public interface refresfListener{ 56 void refresh(); 57 } 58 public void setOnRefreshListener(refresfListener listener){ 59 this.mListener = listener; 60 } 61 62 63 public void initView(Context context){ 64 65 header = LayoutInflater.from(context).inflate(R.layout.header, null); 66 notifyView(header); 67 headerHeight = header.getMeasuredHeight();//获取header的高度 68 69 // headerHeight = header.getHeight(); 70 paddingTop(-headerHeight); 71 //将头布局加进去 72 this.addHeaderView(header); 73 74 this.setOnScrollListener(this); 75 } 76 77 78 79 80 /** 81 * 该方法为通知父布局,子布局view的宽度和高度 82 * @param view:子布局 83 */ 84 private void notifyView(View view){ 85 86 ViewGroup.LayoutParams p = view.getLayoutParams(); 87 88 if(p == null){ 89 p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 90 91 } 92 93 //spec表示当前子view左右边距,padding表示子view的左右内边距 94 //childDimension:子view的宽度 95 int width = ViewGroup.getChildMeasureSpec(0, 0, p.width); 96 97 int height; 98 int tempHeight = p.height; 99 if(tempHeight>0){ 100 //子布局高度不为空,需要填充这个布局 101 height = MeasureSpec.makeMeasureSpec(tempHeight, MeasureSpec.EXACTLY); 102 }else{ 103 //高度为0,则不需要填充 104 height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 105 } 106 107 //然后告诉父布局,子布局的高度和宽度 108 view.measure(width, height); 109 } 110 111 112 //该方法设定header的paddingTop 113 private void paddingTop(int pt){ 114 header.setPadding(header.getPaddingLeft(), pt, header.getPaddingRight(), header.getPaddingBottom()); 115 header.invalidate(); 116 } 117 118 119 120 121 /*** 122 * 监听当前滚动状态 123 * scrollState:当前滚动状态 124 */ 125 public void onScrollStateChanged(AbsListView view, int scrollState) { 126 //记录当前的滚动状态 127 this.scrollState = scrollState; 128 129 } 130 131 /*** 132 * 监听当前滚动的item 133 * firstVisibleItem:当前可见的第一个item 134 * visibleItemCount:当前共有多少个item可见 135 * totalItemCount:总共有多少个item 136 * 137 */ 138 public void onScroll(AbsListView view, int firstVisibleItem, 139 int visibleItemCount, int totalItemCount) { 140 141 this.firstVisibleItem = firstVisibleItem; 142 143 144 } 145 146 147 //触屏事件 148 public boolean onTouchEvent(MotionEvent ev) { 149 150 switch(ev.getAction()){ 151 //手指落到屏幕上时 152 case MotionEvent.ACTION_DOWN: 153 //如果当前可见的第一个item为第0号,说明ListView位于顶端,可以执行下拉刷新 154 if(firstVisibleItem == 0){ 155 canPull = true; 156 startY = (int) ev.getY(); 157 } 158 159 break; 160 //手指在屏幕上拖动时 161 case MotionEvent.ACTION_MOVE: 162 if(canPull){ 163 touchMove(ev); 164 } 165 break; 166 //手指离开屏幕时 167 case MotionEvent.ACTION_UP: 168 canPull = false; 169 if(curState == RELEASE){ 170 curState = RELEASING; 171 refreshHeaderByState(); 172 //这里添加刷新数据的逻辑 173 mListener.refresh(); 174 }else{ 175 curState = NORMAL; 176 refreshHeaderByState(); 177 paddingTop(-headerHeight); 178 } 179 180 break; 181 } 182 183 return super.onTouchEvent(ev); 184 } 185 186 /** 187 * 该方法根据触摸屏幕滑动来改变STATE,即改变当前状态 188 * @param ev 189 */ 190 private void touchMove(MotionEvent ev) { 191 192 int tempY = (int) ev.getY(); 193 int space = tempY -startY;//移动的距离 194 int topdding = space-headerHeight; 195 paddingTop(topdding);//即时设定头布局的隐藏高度 196 if(space>headerHeight&&space<headerHeight+50&&scrollState == SCROLL_STATE_TOUCH_SCROLL){ 197 curState = PULL;//设定为下拉状态 198 refreshHeaderByState(); 199 } 200 if(space>headerHeight+50){ 201 curState = RELEASE;//设定为提示刷新状态 202 refreshHeaderByState(); 203 } 204 205 if(space<headerHeight){ 206 curState = NORMAL;//设定为正常状态 207 refreshHeaderByState(); 208 } 209 210 211 } 212 213 214 /** 215 * 根据当前状态更改header的显示界面 216 * 217 */ 218 private void refreshHeaderByState( ){ 219 ProgressBar pb = (ProgressBar) header.findViewById(R.id.progress_bar); 220 ImageView img = (ImageView) header.findViewById(R.id.img_arrow); 221 TextView tv = (TextView) header.findViewById(R.id.textinfo); 222 223 switch(curState){ 224 225 case NORMAL: 226 pb.setVisibility(View.GONE); 227 img.setVisibility(View.VISIBLE); 228 img.setImageResource(R.drawable.down_arrow); 229 tv.setText("下拉刷新"); 230 break; 231 case PULL: 232 pb.setVisibility(View.GONE); 233 img.setVisibility(View.VISIBLE); 234 img.setImageResource(R.drawable.down_arrow); 235 tv.setText("下拉刷新"); 236 break; 237 case RELEASE: 238 pb.setVisibility(View.GONE); 239 img.setVisibility(View.VISIBLE); 240 img.setImageResource(R.drawable.up_arrow); 241 tv.setText("松开刷新"); 242 break; 243 case RELEASING: 244 pb.setVisibility(View.VISIBLE); 245 img.setVisibility(View.GONE); 246 tv.setText("正在刷新"); 247 break; 248 249 250 } 251 252 } 253 254 //数据刷新完成后的操作 255 public void refreshFinish(){ 256 257 curState = NORMAL; 258 paddingTop(-headerHeight); 259 refreshHeaderByState(); 260 } 261 262 263 264 }
接下来就是MainActivity中的代码了。如下:
1 package com.fuly.load; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 import com.fuly.load.MyListView.refresfListener; 7 8 import android.os.Bundle; 9 import android.os.Handler; 10 import android.app.Activity; 11 12 public class MainActivity extends Activity implements refresfListener{ 13 14 private MyListView lv; 15 private List<MyData> mDatas = new ArrayList<MyData>(); 16 private MyAdapter mAdapter; 17 18 19 protected void onCreate(Bundle savedInstanceState) { 20 super.onCreate(savedInstanceState); 21 setContentView(R.layout.activity_main); 22 23 initData();//该方法初始化数据 24 lv = (MyListView) findViewById(R.id.list_view); 25 lv.setOnRefreshListener(this);//设定回调接口 26 mAdapter = new MyAdapter(this, mDatas); 27 lv.setAdapter(mAdapter); 28 29 30 } 31 32 33 /** 34 * 该方法初始化数据,即提供初始的素材 35 */ 36 private void initData() { 37 for(int i = 0;i<12;i++){ 38 MyData md = new MyData("你好,我是提前设定的"); 39 mDatas.add(md); 40 } 41 42 } 43 /** 44 * 提供刷新数据 45 */ 46 private void getRefreshData() { 47 for(int i = 0;i<3;i++){ 48 MyData md = new MyData("你好,我是刷新进来的"); 49 mDatas.add(i, md); 50 } 51 52 } 53 54 55 //重写回调方法 56 public void refresh() { 57 58 //在这里之所以使用Handler,是想让操作延迟,这样子效果看起来更 59 //清晰,实际项目中,是 不需要的 60 Handler mHandler = new Handler(); 61 mHandler.postDelayed(new Runnable(){ 62 63 @Override 64 public void run() { 65 //获得刷新数据 66 getRefreshData(); 67 //刷新ListView 68 mAdapter.notifyDataSetChanged(); 69 //lv.setSelection(mDatas.size()-1); 70 71 //刷新后 72 lv.refreshFinish(); 73 74 } 75 76 }, 5000 ); 77 78 79 80 } 81 }
好了,快快运行下程序,体验下拉刷新的效果吧。至此,ListView实现下拉刷新,我们讲解完毕了。