Android之下拉刷新的ListView
2016-03-23 21:30
483 查看
不废话,代码里面注释很详细,直接上代码:
自定义的RefreshableListView代码:
header布局界面sideworks_layout_header.xml代码:
主界面布局activity_main.xml代码:
主界面MainActivity.java代码:
自定义的RefreshableListView代码:
public class RefreshableListView extends ListView implements OnScrollListener { private View header; // ListView顶部布局 private LayoutInflater inflater; private int headerHeight; // 顶部布局Header的高度 private int firstVisisblePosition; // 当前第一个可见的Item的位置 private int scrollState; // ListView当前的滚动状态 private boolean remarkTop; // 标记,当前是在ListView的最顶端按下的 private int startY; // 手指按下时的Y值 private int state; // 指示当前的状态 private final int STATE_NORMAL = 0; // 正常状态 private final int STATE_PULL = 1; // 提示“下拉可以刷新”的状态 private final int STATE_TOREFRESH = 2; // 提示“松开手指刷新”的状态 private final int STATE_REFRESHING = 3; // 正在刷新的状态 // Header布局中的四个控件 private TextView refreshTip; // 显示“下拉可以刷新”/“松开手指刷新”的TextView private TextView timeTip; // 显示上次刷新的时间的TextView private ImageView arrowImg; // 向上/向下的箭头的ImageView private ProgressBar progressBar; // 刷新数据时用到的ProgressBar private ListViewRefreshListener listener; // 刷新数据的接口 // 自定义控件都必须实现以下三个构造方法(一个参数、两个参数、三个参数的构造方法) // 我们在一个参数的构造方法中调用两个参数的构造方法,在两个参数的构造方法中调用三个参数的构造方法,这样不管我们用哪个构造方法,最终的调用代码是一样的 // 一个参数的构造方法:这个方法是在Activity中根据上下文环境直接生成控件时调用的 public RefreshableListView(Context context) { this(context, null); } // 两个参数的构造方法:这个方法是在使用了系统属性,没有使用自定义属性时调用的 public RefreshableListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } // 三个参数的构造方法:这个方法是在使用了自定义属性时调用的 public RefreshableListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); // 找到Header中的控件 refreshTip = (TextView) header.findViewById(R.id.control_header_refreshtip); timeTip = (TextView) header.findViewById(R.id.control_header_timetip); arrowImg = (ImageView) header.findViewById(R.id.control_header_refresharrow); progressBar = (ProgressBar) header.findViewById(R.id.control_header_progressbar); } // 初始化界面,添加顶部布局文件到ListView中 private void initView(Context context) { inflater = LayoutInflater.from(context); header = inflater.inflate(R.layout.sideworks_layout_header, null); // 测量顶部布局header的高度 measureView(context); headerHeight = header.getMeasuredHeight(); setViewTopPadding(-headerHeight); // 设置ListView的上缩进:是负值,表示将header布局缩到屏幕外面去 // 把顶部布局添加到ListView的最上面 this.addHeaderView(header); // 设置向下滑动时逐渐显示顶部布局(接口回掉方法) this.setOnScrollListener(this); } // 测量控件的宽高(通知父佈局:我佔用的寬和高) private void measureView(Context context) { ViewGroup.LayoutParams lp = header.getLayoutParams(); // 获取header布局的宽高属性 if (lp == null) { lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } int width = ViewGroup.getChildMeasureSpec(0, 0, lp.width); int height; // 不能用getChildMeasureSpec方法获取高度的原因是ListView的高度不确定,而宽度是确定的 int tempHeight = lp.height; if (tempHeight > 0) { // 大于0说明定义了ListView的高度,所以我们用精确布局模式EXACTLY height = MeasureSpec.makeMeasureSpec(tempHeight, MeasureSpec.EXACTLY); } else { // 如果不大于0,则表示没有定义ListView的高度,即UNSPECIFIED height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } header.measure(width, height); // 这行代码很容易报错:NullPointerException,所以SDK17以前的版本必须将布局的最外层设置为LinearLayout } // 设置ListView的TopPadding属性 private void setViewTopPadding(int topPadding) { this.setPadding(this.getPaddingLeft(), topPadding, this.getPaddingRight(), this.getPaddingBottom()); this.invalidate(); // invalidate()方法的作用是请求对该控件进行重绘 } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { this.firstVisisblePosition = firstVisibleItem; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { this.scrollState = scrollState; } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: if (firstVisisblePosition == 0) { remarkTop = true; startY = (int) ev.getY(); // 如果按下时是处在ListView最上面的Item,则记录当前的Y坐标值 } break; case MotionEvent.ACTION_MOVE: onMove(ev); break; case MotionEvent.ACTION_UP: if (state == STATE_TOREFRESH) { // 滑动到了“松开手指刷新数据”的高度 state = STATE_REFRESHING; refreshViewByState(); listener.refreshListView(); // 调用接口,刷新数据 } else if (state == STATE_PULL) { // 还是处在“下拉刷新数据”的高度 state = STATE_NORMAL; remarkTop = false; refreshViewByState(); } break; } return super.onTouchEvent(ev); } // 判断移动过程中的操作 private void onMove(MotionEvent ev) { if (!remarkTop) { // 如果按下地点不是ListView的第一个Item,则不做处理,正常滑动 return; } int tempY = (int) ev.getY(); // 当前移动到了什么位置(Y坐标值) int space = tempY - startY; // 判断当前移动了多大距离(即header布局被拉下来多少),向下拉时是正值 int topPadding = space - headerHeight; // 当前还在屏幕外面的header布局的高度 switch (state) { case STATE_NORMAL: if (space > 0) { state = STATE_PULL; refreshViewByState(); } break; case STATE_PULL: setViewTopPadding(topPadding); if (space > headerHeight && scrollState == SCROLL_STATE_TOUCH_SCROLL) { // 滑动过header高度的一半并且仍然在滑动 state = STATE_TOREFRESH; refreshViewByState(); } break; case STATE_TOREFRESH: setViewTopPadding(topPadding); if (space < headerHeight) { state = STATE_PULL; refreshViewByState(); } else if (space <= 0) { state = STATE_NORMAL; remarkTop = false; refreshViewByState(); } break; } } // 根据当前状态,改变界面显示 private void refreshViewByState() { // 箭头反转的两个动画 RotateAnimation anim1 = new RotateAnimation(0, 180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); anim1.setDuration(500); anim1.setFillAfter(true); RotateAnimation anim2 = new RotateAnimation(180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); anim2.setDuration(500); anim2.setFillAfter(true); switch (state) { case STATE_NORMAL: setViewTopPadding(-headerHeight); arrowImg.clearAnimation(); break; case STATE_PULL: arrowImg.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); refreshTip.setText("下拉可以刷新!"); arrowImg.clearAnimation(); arrowImg.setAnimation(anim2); break; case STATE_TOREFRESH: arrowImg.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); refreshTip.setText("松开立即刷新!"); arrowImg.clearAnimation(); arrowImg.setAnimation(anim1); break; case STATE_REFRESHING: setViewTopPadding(0); arrowImg.setVisibility(View.GONE); progressBar.setVisibility(View.VISIBLE); refreshTip.setText("正在刷新......"); arrowImg.clearAnimation(); break; } } public void onRefreshComplete() { state = STATE_NORMAL; remarkTop = false; refreshViewByState(); String time = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()); timeTip.setText(time); } // 刷新数据的接口,要通过接口回掉的方式更新数据 public interface ListViewRefreshListener { public void refreshListView(); } public void setListViewRefreshListener(ListViewRefreshListener listener) { this.listener = listener; } }
header布局界面sideworks_layout_header.xml代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="50.0dip" android:background="@color/cl_header_bg" android:gravity="center" android:padding="10.0dip" > <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/cl_transparent" > <ImageView android:id="@+id/control_header_refresharrow" android:layout_width="wrap_content" android:layout_height="35.0dip" android:layout_centerVertical="true" android:layout_marginRight="15.0dip" android:contentDescription="@string/app_name" android:src="@drawable/refresh_arrow" /> <ProgressBar android:id="@+id/control_header_progressbar" style="?android:attr/progressBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginRight="15.0dip" android:visibility="gone" /> <LinearLayout android:id="@+id/position_header_tips" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:orientation="vertical" android:paddingLeft="30.0dip" > <TextView android:id="@+id/control_header_refreshtip" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/str_header_refreshtip" android:textColor="@color/cl_black" android:textSize="12.0sp" /> <TextView android:id="@+id/control_header_timetip" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="-7.0dip" android:textColor="@color/cl_black" android:textSize="12.0sp" /> </LinearLayout> </RelativeLayout> </LinearLayout>
主界面布局activity_main.xml代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <com.view.RefreshableListView android:id="@+id/control_main_listview" android:layout_width="match_parent" android:layout_height="match_parent" android:cacheColorHint="@color/cl_transparent" /> </RelativeLayout>
主界面MainActivity.java代码:
public class MainActivity extends Activity implements ListViewRefreshListener { private RefreshableListView testList; public static List<String> dataList; public static ArrayAdapter<String> listAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView() { testList = (RefreshableListView) findViewById(R.id.control_main_listview); testList.setListViewRefreshListener(this); dataList = getData(); listAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1, dataList); testList.setAdapter(listAdapter); } private List<String> getData() { dataList = new ArrayList<String>(); for (int i = 0; i < 10; i++) { dataList.add("This is a test data."); } return dataList; } @Override public void refreshListView() { // 延时两秒后显示两条新数据:This is a new data. new Handler().postDelayed(new Runnable() { public void run() { for (int i = 0; i < 2; i++) { dataList.add(0, "This is a new data."); } listAdapter.notifyDataSetChanged(); testList.onRefreshComplete(); } }, 2000); } }
相关文章推荐
- Android的材料设计兼容库(Design Support Library)
- 《50 Android Hacks》学习心得一:延迟加载和布局重用
- Android——布局(线性布局linearLayout,表格布局TableLayout,帧布局FrameLayout)
- android:layout_gravity和android:gravity的区别
- 解决Android中的SQLite数据库并发访问
- Android TabLayout、ViewPager实现顶部和底部Tab导航 点击滑动切换Tab页面
- Android之ListView使用总结
- 理解Android系统的四大组件
- Android框架设计模式(三)——Observer Method
- [android] 隐式意图激活另外一个activity
- Android Studio SVN的使用
- Android中的5中数据存储方式之SharedPreferences存储
- Android手势源码浅析-----手势绘制(GestureOverlayView)
- Android Studio 设置内存大小及原理
- android 开发技巧(10,11)
- 常用的监听事件(android)
- Android Stability [转]
- [Android Studio] Android Studio常用快捷键
- android无效代码,资源文件,原生文件对apk大小的影响
- android 开发技巧(12)--更改 Toast 显示位置的技巧