• (转)android UI进阶之实现listview的下拉加载


     关于listview的操作五花八门,有下拉刷新,分级显示,分页列表,逐页加载等,以后会陆续和大家分享这些技术,今天讲下下拉加载这个功能的实现。

    最初的下拉加载应该是ios上的效果,现在很多应用如新浪微博等都加入了这个操作。即下拉listview刷新列表,这无疑是一个非常友好的操作。今天就和大家分享下这个操作的实现。

    先看下运行效果:


       

     

       

    代码参考国外朋友Johan Nilsson的实现,http://johannilsson.com/2011/03/13/android-pull-to-refresh-update.html

    主要原理为监听触摸和滑动操作,在listview 头部加载一个视图。那要做的其实很简单:1.写好加载到listview头部的view 2.重写listview,实现onTouchEvent方法和onScroll方法,监听滑动状态。计算headview全部显示出来即可实行加载动 作,加载完成即刷新列表。重新隐藏headview。

    首先写下headview的xml代码:

    1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     android:layout_width="fill_parent"  
    3.     android:layout_height="fill_parent"  
    4.     android:paddingTop="10dip"  
    5.     android:paddingBottom="15dip"  
    6.     android:gravity="center"  
    7.         android:id="@+id/pull_to_refresh_header"  
    8.     >  
    9.     <ProgressBar   
    10.         android:id="@+id/pull_to_refresh_progress"  
    11.         android:indeterminate="true"  
    12.         android:layout_width="wrap_content"  
    13.         android:layout_height="wrap_content"  
    14.         android:layout_marginLeft="30dip"  
    15.         android:layout_marginRight="20dip"  
    16.         android:layout_marginTop="10dip"  
    17.         android:visibility="gone"  
    18.         android:layout_centerVertical="true"  
    19.         style="?android:attr/progressBarStyleSmall"  
    20.         />  
    21.     <ImageView  
    22.         android:id="@+id/pull_to_refresh_image"  
    23.         android:layout_width="wrap_content"  
    24.         android:layout_height="wrap_content"  
    25.         android:layout_marginLeft="30dip"  
    26.         android:layout_marginRight="20dip"  
    27.         android:visibility="gone"  
    28.         android:layout_gravity="center"  
    29.         android:gravity="center"  
    30.         android:src="@drawable/ic_pulltorefresh_arrow"  
    31.         />  
    32.     <TextView  
    33.         android:id="@+id/pull_to_refresh_text"  
    34.         android:textAppearance="?android:attr/textAppearanceMedium"  
    35.         android:textStyle="bold"  
    36.         android:paddingTop="5dip"  
    37.         android:layout_width="fill_parent"  
    38.         android:layout_height="wrap_content"  
    39.         android:layout_gravity="center"  
    40.         android:gravity="center"  
    41.         />  
    42.     <TextView  
    43.         android:id="@+id/pull_to_refresh_updated_at"  
    44.         android:layout_below="@+id/pull_to_refresh_text"  
    45.         android:visibility="gone"  
    46.         android:textAppearance="?android:attr/textAppearanceSmall"  
    47.         android:layout_width="fill_parent"  
    48.         android:layout_height="wrap_content"  
    49.         android:layout_gravity="center"  
    50.         android:gravity="center"  
    51.         />  
    52. </RelativeLayout>  

    代码比较简单,即headview包括一个进度条一个箭头和两段文字(一个显示加载状态,另一个显示最后刷新时间,本例就不设置了)。

    而后重写listview,代码如下:

    1. package com.notice.pullrefresh;  
    2.   
    3.   
    4. import android.content.Context;  
    5. import android.util.AttributeSet;  
    6. import android.view.LayoutInflater;  
    7. import android.view.MotionEvent;  
    8. import android.view.View;  
    9. import android.view.ViewGroup;  
    10. import android.view.animation.LinearInterpolator;  
    11. import android.view.animation.RotateAnimation;  
    12. import android.widget.AbsListView;  
    13. import android.widget.AbsListView.OnScrollListener;  
    14. import android.widget.ImageView;  
    15. import android.widget.ListAdapter;  
    16. import android.widget.ListView;  
    17. import android.widget.ProgressBar;  
    18. import android.widget.RelativeLayout;  
    19. import android.widget.TextView;  
    20.   
    21.    
    22.   
    23. public class PullToRefreshListView extends ListView implements OnScrollListener {  
    24.   
    25.     // 状态  
    26.     private static final int TAP_TO_REFRESH = 1;  
    27.     private static final int PULL_TO_REFRESH = 2;  
    28.     private static final int RELEASE_TO_REFRESH = 3;  
    29.     private static final int REFRESHING = 4;  
    30.   
    31.   
    32.     private OnRefreshListener mOnRefreshListener;  
    33.   
    34.   
    35.     // 监听对listview的滑动动作  
    36.     private OnScrollListener mOnScrollListener;  
    37.     private LayoutInflater mInflater;  
    38.   
    39.     //顶部刷新时出现的控件  
    40.     private RelativeLayout mRefreshView;  
    41.     private TextView mRefreshViewText;  
    42.     private ImageView mRefreshViewImage;  
    43.     private ProgressBar mRefreshViewProgress;  
    44.     private TextView mRefreshViewLastUpdated;  
    45.   
    46.     // 当前滑动状态  
    47.     private int mCurrentScrollState;  
    48.     // 当前刷新状态  
    49.     private int mRefreshState;  
    50.       
    51.     // 箭头动画效果  
    52.     private RotateAnimation mFlipAnimation;  
    53.     private RotateAnimation mReverseFlipAnimation;  
    54.   
    55.     private int mRefreshViewHeight;  
    56.     private int mRefreshOriginalTopPadding;  
    57.     private int mLastMotionY;  
    58.   
    59.     private boolean mBounceHack;  
    60.   
    61.     public PullToRefreshListView(Context context) {  
    62.         super(context);  
    63.         init(context);  
    64.     }  
    65.   
    66.     public PullToRefreshListView(Context context, AttributeSet attrs) {  
    67.         super(context, attrs);  
    68.         init(context);  
    69.     }  
    70.   
    71.     public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {  
    72.         super(context, attrs, defStyle);  
    73.         init(context);  
    74.     }  
    75.   
    76.     /** 
    77.      * 初始化控件和箭头动画(这里直接在代码中初始化动画而不是通过xml) 
    78.      */  
    79.     private void init(Context context) {  
    80.         mFlipAnimation = new RotateAnimation(0, -180,  
    81.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,  
    82.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);  
    83.         mFlipAnimation.setInterpolator(new LinearInterpolator());  
    84.         mFlipAnimation.setDuration(250);  
    85.         mFlipAnimation.setFillAfter(true);  
    86.         mReverseFlipAnimation = new RotateAnimation(-1800,  
    87.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,  
    88.                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);  
    89.         mReverseFlipAnimation.setInterpolator(new LinearInterpolator());  
    90.         mReverseFlipAnimation.setDuration(250);  
    91.         mReverseFlipAnimation.setFillAfter(true);  
    92.   
    93.         mInflater = (LayoutInflater) context.getSystemService(  
    94.                 Context.LAYOUT_INFLATER_SERVICE);  
    95.   
    96.         mRefreshView = (RelativeLayout) mInflater.inflate(  
    97.                 R.layout.pull_to_refresh_header, thisfalse);  
    98.         mRefreshViewText =  
    99.             (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_text);  
    100.         mRefreshViewImage =  
    101.             (ImageView) mRefreshView.findViewById(R.id.pull_to_refresh_image);  
    102.         mRefreshViewProgress =  
    103.             (ProgressBar) mRefreshView.findViewById(R.id.pull_to_refresh_progress);  
    104.         mRefreshViewLastUpdated =  
    105.             (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_updated_at);  
    106.   
    107.         mRefreshViewImage.setMinimumHeight(50);  
    108.         mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();  
    109.   
    110.         mRefreshState = TAP_TO_REFRESH;  
    111.           
    112.         //为listview头部增加一个view  
    113.         addHeaderView(mRefreshView);  
    114.   
    115.         super.setOnScrollListener(this);  
    116.   
    117.         measureView(mRefreshView);  
    118.         mRefreshViewHeight = mRefreshView.getMeasuredHeight();  
    119.     }  
    120.   
    121.     @Override  
    122.     protected void onAttachedToWindow() {  
    123.         setSelection(1);  
    124.     }  
    125.   
    126.     @Override  
    127.     public void setAdapter(ListAdapter adapter) {  
    128.         super.setAdapter(adapter);  
    129.   
    130.         setSelection(1);  
    131.     }  
    132.   
    133.     /** 
    134.      * 设置滑动监听器 
    135.      *  
    136.      */  
    137.     @Override  
    138.     public void setOnScrollListener(AbsListView.OnScrollListener l) {  
    139.         mOnScrollListener = l;  
    140.     }  
    141.   
    142.     /** 
    143.      * 注册一个list需要刷新时的回调接口 
    144.      *  
    145.      */  
    146.     public void setOnRefreshListener(OnRefreshListener onRefreshListener) {  
    147.         mOnRefreshListener = onRefreshListener;  
    148.     }  
    149.   
    150.     /** 
    151.      * 设置标签显示何时最后被刷新 
    152.      *  
    153.      * @param lastUpdated 
    154.      *            Last updated at. 
    155.      */  
    156.     public void setLastUpdated(CharSequence lastUpdated) {  
    157.         if (lastUpdated != null) {  
    158.             mRefreshViewLastUpdated.setVisibility(View.VISIBLE);  
    159.             mRefreshViewLastUpdated.setText(lastUpdated);  
    160.         } else {  
    161.             mRefreshViewLastUpdated.setVisibility(View.GONE);  
    162.         }  
    163.     }  
    164.   
    165.     // 实现该方法处理触摸  
    166.     @Override  
    167.     public boolean onTouchEvent(MotionEvent event) {  
    168.         final int y = (int) event.getY();  
    169.         mBounceHack = false;  
    170.   
    171.         switch (event.getAction()) {  
    172.   
    173.             case MotionEvent.ACTION_UP:  
    174.                 if (!isVerticalScrollBarEnabled()) {  
    175.                     setVerticalScrollBarEnabled(true);  
    176.                 }  
    177.                 if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {  
    178.                 // 拖动距离达到刷新需要  
    179.                     if ((mRefreshView.getBottom() >= mRefreshViewHeight  
    180.                             || mRefreshView.getTop() >= 0)  
    181.                             && mRefreshState == RELEASE_TO_REFRESH) {  
    182.                     // 把状态设置为正在刷新  
    183.                         mRefreshState = REFRESHING;  
    184.                     // 准备刷新  
    185.                         prepareForRefresh();  
    186.                     // 刷新  
    187.                         onRefresh();  
    188.                     } else if (mRefreshView.getBottom() < mRefreshViewHeight  
    189.                             || mRefreshView.getTop() <= 0) {  
    190.                     // 中止刷新  
    191.                         resetHeader();  
    192.                         setSelection(1);  
    193.                     }  
    194.                 }  
    195.                 break;  
    196.             case MotionEvent.ACTION_DOWN:  
    197.             // 获得按下y轴位置  
    198.                 mLastMotionY = y;  
    199.                 break;  
    200.             case MotionEvent.ACTION_MOVE:  
    201.             // 计算边距  
    202.                 applyHeaderPadding(event);  
    203.                 break;  
    204.         }  
    205.         return super.onTouchEvent(event);  
    206.     }  
    207.   
    208.     // 获得header的边距  
    209.     private void applyHeaderPadding(MotionEvent ev) {  
    210.   
    211.         int pointerCount = ev.getHistorySize();  
    212.   
    213.         for (int p = 0; p < pointerCount; p++) {  
    214.             if (mRefreshState == RELEASE_TO_REFRESH) {  
    215.                 if (isVerticalFadingEdgeEnabled()) {  
    216.                     setVerticalScrollBarEnabled(false);  
    217.                 }  
    218.   
    219.                 int historicalY = (int) ev.getHistoricalY(p);  
    220.   
    221.                 // 计算申请的边距,除以1.7使得拉动效果更好  
    222.                 int topPadding = (int) (((historicalY - mLastMotionY)  
    223.                         - mRefreshViewHeight) / 1.7);  
    224.   
    225.                 mRefreshView.setPadding(  
    226.                         mRefreshView.getPaddingLeft(),  
    227.                         topPadding,  
    228.                         mRefreshView.getPaddingRight(),  
    229.                         mRefreshView.getPaddingBottom());  
    230.             }  
    231.         }  
    232.     }  
    233.   
    234.     /** 
    235.      * 将head的边距重置为初始的数值 
    236.      */  
    237.     private void resetHeaderPadding() {  
    238.         mRefreshView.setPadding(  
    239.                 mRefreshView.getPaddingLeft(),  
    240.                 mRefreshOriginalTopPadding,  
    241.                 mRefreshView.getPaddingRight(),  
    242.                 mRefreshView.getPaddingBottom());  
    243.     }  
    244.   
    245.     /** 
    246.      * 重置header为之前的状态 
    247.      */  
    248.     private void resetHeader() {  
    249.         if (mRefreshState != TAP_TO_REFRESH) {  
    250.             mRefreshState = TAP_TO_REFRESH;  
    251.   
    252.             resetHeaderPadding();  
    253.   
    254.             // 将刷新图标换成箭头  
    255.             mRefreshViewImage.setImageResource(R.drawable.ic_pulltorefresh_arrow);  
    256.             // 清除动画  
    257.             mRefreshViewImage.clearAnimation();  
    258.             // 隐藏图标和进度条  
    259.             mRefreshViewImage.setVisibility(View.GONE);  
    260.             mRefreshViewProgress.setVisibility(View.GONE);  
    261.         }  
    262.     }  
    263.   
    264.     // 估算headview的width和height  
    265.     private void measureView(View child) {  
    266.         ViewGroup.LayoutParams p = child.getLayoutParams();  
    267.         if (p == null) {  
    268.             p = new ViewGroup.LayoutParams(  
    269.                     ViewGroup.LayoutParams.FILL_PARENT,  
    270.                     ViewGroup.LayoutParams.WRAP_CONTENT);  
    271.         }  
    272.   
    273.         int childWidthSpec = ViewGroup.getChildMeasureSpec(0,  
    274.                 0 + 0, p.width);  
    275.         int lpHeight = p.height;  
    276.         int childHeightSpec;  
    277.         if (lpHeight > 0) {  
    278.             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);  
    279.         } else {  
    280.             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);  
    281.         }  
    282.         child.measure(childWidthSpec, childHeightSpec);  
    283.     }  
    284.   
    285.     @Override  
    286.     public void onScroll(AbsListView view, int firstVisibleItem,  
    287.             int visibleItemCount, int totalItemCount) {  
    288.   
    289.         // 在refreshview完全可见时,设置文字为松开刷新,同时翻转箭头  
    290.         if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL  
    291.                 && mRefreshState != REFRESHING) {  
    292.             if (firstVisibleItem == 0) {  
    293.                 mRefreshViewImage.setVisibility(View.VISIBLE);  
    294.                 if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20  
    295.                         || mRefreshView.getTop() >= 0)  
    296.                         && mRefreshState != RELEASE_TO_REFRESH) {  
    297.                     mRefreshViewText.setText("松开加载...");  
    298.                     mRefreshViewImage.clearAnimation();  
    299.                     mRefreshViewImage.startAnimation(mFlipAnimation);  
    300.                     mRefreshState = RELEASE_TO_REFRESH;  
    301.                 } else if (mRefreshView.getBottom() < mRefreshViewHeight + 20  
    302.                         && mRefreshState != PULL_TO_REFRESH) {  
    303.                     mRefreshViewText.setText("下拉刷新...");  
    304.                     if (mRefreshState != TAP_TO_REFRESH) {  
    305.                         mRefreshViewImage.clearAnimation();  
    306.                         mRefreshViewImage.startAnimation(mReverseFlipAnimation);  
    307.                     }  
    308.                     mRefreshState = PULL_TO_REFRESH;  
    309.                 }  
    310.             } else {  
    311.                 mRefreshViewImage.setVisibility(View.GONE);  
    312.                 resetHeader();  
    313.             }  
    314.         } else if (mCurrentScrollState == SCROLL_STATE_FLING  
    315.                 && firstVisibleItem == 0  
    316.                 && mRefreshState != REFRESHING) {  
    317.             setSelection(1);  
    318.             mBounceHack = true;  
    319.         } else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {  
    320.             setSelection(1);  
    321.         }  
    322.   
    323.         if (mOnScrollListener != null) {  
    324.             mOnScrollListener.onScroll(view, firstVisibleItem,  
    325.                     visibleItemCount, totalItemCount);  
    326.         }  
    327.     }  
    328.   
    329.     @Override  
    330.     public void onScrollStateChanged(AbsListView view, int scrollState) {  
    331.         mCurrentScrollState = scrollState;  
    332.   
    333.         if (mCurrentScrollState == SCROLL_STATE_IDLE) {  
    334.             mBounceHack = false;  
    335.         }  
    336.   
    337.         if (mOnScrollListener != null) {  
    338.             mOnScrollListener.onScrollStateChanged(view, scrollState);  
    339.         }  
    340.     }  
    341.   
    342.     public void prepareForRefresh() {  
    343.         resetHeaderPadding();// 恢复header的边距  
    344.   
    345.         mRefreshViewImage.setVisibility(View.GONE);  
    346.         // 注意加上,否则仍然显示之前的图片  
    347.         mRefreshViewImage.setImageDrawable(null);  
    348.         mRefreshViewProgress.setVisibility(View.VISIBLE);  
    349.   
    350.         // 设置文字  
    351.         mRefreshViewText.setText("加载中...");  
    352.   
    353.         mRefreshState = REFRESHING;  
    354.     }  
    355.   
    356.     public void onRefresh() {  
    357.   
    358.         if (mOnRefreshListener != null) {  
    359.             mOnRefreshListener.onRefresh();  
    360.         }  
    361.     }  
    362.   
    363.     /** 
    364.      * 重置listview为普通的listview,该方法设置最后更新时间 
    365.      *  
    366.      * @param lastUpdated 
    367.      *            Last updated at. 
    368.      */  
    369.     public void onRefreshComplete(CharSequence lastUpdated) {  
    370.         setLastUpdated(lastUpdated);  
    371.         onRefreshComplete();  
    372.     }  
    373.   
    374.     /** 
    375.      * 重置listview为普通的listview,不设置最后更新时间 
    376.      */  
    377.     public void onRefreshComplete() {          
    378.   
    379.         resetHeader();  
    380.   
    381.         // 如果refreshview在加载结束后可见,下滑到下一个条目  
    382.         if (mRefreshView.getBottom() > 0) {  
    383.             invalidateViews();  
    384.             setSelection(1);  
    385.         }  
    386.     }  
    387.   
    388.   
    389.   
    390.     /** 
    391.      * 刷新监听器接口 
    392.      */  
    393.     public interface OnRefreshListener {  
    394.         /** 
    395.          * list需要被刷新时调用 
    396.          */  
    397.         public void onRefresh();  
    398.     }  
    399. }  

    相信我注释已经写的比较详细了,主要注意 onTouchEvent和onScroll方法,在这里面计算头部边距,从而通过用户的手势实现“下拉刷新”到“松开加载”以及“加载”三个状态的切 换。其中还有一系列和header有关的方法,用来设置header的显示以及取得header的边距。于此同时,代码留出了接口以供调用。

    那么现在写一个测试Activity来试验下效果:

    1. package com.notice.pullrefresh;  
    2.   
    3. import java.util.Arrays;  
    4. import java.util.LinkedList;  
    5.   
    6. import android.app.ListActivity;  
    7. import android.os.AsyncTask;  
    8. import android.os.Bundle;  
    9. import android.widget.ArrayAdapter;  
    10.   
    11. import com.notice.pullrefresh.PullToRefreshListView.OnRefreshListener;  
    12.   
    13.   
    14. public class PullrefreshActivity extends ListActivity {  
    15.     private LinkedList<String> mListItems;  
    16.     ArrayAdapter<String> adapter;  
    17.   
    18.     /** Called when the activity is first created. */  
    19.     @Override  
    20.     public void onCreate(Bundle savedInstanceState) {  
    21.         super.onCreate(savedInstanceState);  
    22.         setContentView(R.layout.pull_to_refresh);  
    23.   
    24.         // list需要刷新时调用  
    25.         ((PullToRefreshListView) getListView())  
    26.                 .setOnRefreshListener(new OnRefreshListener() {  
    27.                     @Override  
    28.                     public void onRefresh() {  
    29.                         // 在这执行后台工作  
    30.                         new GetDataTask().execute();  
    31.                     }  
    32.                 });  
    33.   
    34.   
    35.   
    36.         mListItems = new LinkedList<String>();  
    37.         mListItems.addAll(Arrays.asList(mStrings));  
    38.   
    39.         adapter = new ArrayAdapter<String>(this,  
    40.                 android.R.layout.simple_list_item_1, mListItems);  
    41.   
    42.         setListAdapter(adapter);  
    43.     }  
    44.   
    45.   
    46.     private class GetDataTask extends AsyncTask<Void, Void, String[]> {  
    47.   
    48.         @Override  
    49.         protected String[] doInBackground(Void... params) {  
    50.             // 在这里可以做一些后台工作  
    51.             try {  
    52.                 Thread.sleep(2000);  
    53.             } catch (InterruptedException e) {  
    54.                 e.printStackTrace();  
    55.             }  
    56.             return mStrings;  
    57.         }  
    58.   
    59.         @Override  
    60.         protected void onPostExecute(String[] result) {  
    61.             // 下拉后增加的内容  
    62.             mListItems.addFirst("Added after refresh...");  
    63.   
    64.             // 刷新完成调用该方法复位  
    65.             ((PullToRefreshListView) getListView()).onRefreshComplete();  
    66.   
    67.             super.onPostExecute(result);  
    68.         }  
    69.     }  
    70.   
    71.     private String[] mStrings = { "normal data1""normal data2",  
    72.             "nomal data3""normal data4""norma data5""normal data6" };  
    73. }  

    代码通过asyncTask实现一个异步操作,并通过设置onRefreshListener监听器调用onRefresh方法实现下拉时刷新,并在刷新完成后调用onRefreshComplete做复位处理。

  • 相关阅读:
    mongodb studio 3t 破解无限使用脚本
    香港低价linux虚拟主机,
    c#4.0 Task.Factory.StartNew 用法
    系统进不去怎么办?教你利用bootice工具引导修复系统
    用一个URL加一个JAVA壳做成一个安卓应用
    ANDROID开机动画分析
    你的Android不好用,都是因为这几点原因
    横竖屏切换时不销毁当前activity 和 锁定屏幕
    APP流氓大法之apk 静默安装
    设备管理器勾选后不能再取消了
  • 原文地址:https://www.cnblogs.com/xingmeng/p/2650826.html
Copyright © 2020-2023  润新知