zoukankan      html  css  js  c++  java
  • 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 }
  • 相关阅读:
    java 基础 01 变量和注释、数据类型
    js虚拟数字小键盘
    好看的table样式
    Java8的lambda表达式和Stream API
    设计模式-模板
    【转】Git使用教程之基础篇
    Linux安装redis和部署
    【原】DjianGo Windows7下的安装
    【转】七牛云加速域名配置
    【原】Solr入门之概念和安装
  • 原文地址:https://www.cnblogs.com/blog-wzy/p/5313189.html
Copyright © 2011-2022 走看看