不废话,代码里面注释很详细,直接上代码:
自定义的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代码:
18 9 58 59 6013 14 23 24 32 33 40 41 42 49 50 57
主界面布局activity_main.xml代码:
15 6 11 12
主界面MainActivity.java代码:
1 public class MainActivity extends Activity implements ListViewRefreshListener { 2 private RefreshableListView testList; 3 public static ListdataList; 4 public static ArrayAdapter 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 (this, android.R.layout.simple_expandable_list_item_1, dataList);18 testList.setAdapter(listAdapter);19 }20 21 private List getData() {22 dataList = new ArrayList ();23 for (int i = 0; i < 10; i++) {24 dataList.add("This is a test data.");25 }26 return dataList;27 }28 29 @Override30 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 }