ListView自定义View上拉加载和下拉刷新的实现


该方法,我们通过完全自定义的方式实现,不添加任何依赖库和jar包。纯原生。

首先看一下我们实现的效果

实现的关键点:

  • ListView添加头布局和底布局。
  • 通过改变头布局的paddingTop值,来控制控件的显示和隐藏
  • 根据我们滑动的状态,动态修改头部布局和底部布局。

看一下代码:

  1 public class CustomRefreshListView extends ListView implements OnScrollListener{
  2 
  3     /**
  4      * 头布局
  5      */
  6     private View headerView;
  7 
  8     /**
  9      * 头部布局的高度
 10      */
 11     private int headerViewHeight;
 12 
 13     /**
 14      * 头部旋转的图片
 15      */
 16     private ImageView iv_arrow;
 17 
 18     /**
 19      * 头部下拉刷新时状态的描述
 20      */
 21     private TextView tv_state;
 22 
 23     /**
 24      * 下拉刷新时间的显示控件
 25      */
 26     private TextView tv_time;
 27 
 28 
 29     /**
 30      * 底部布局
 31      */
 32     private View footerView;
 33 
 34     /**
 35      * 底部旋转progressbar
 36      */
 37     private ProgressBar pb_rotate;
 38 
 39 
 40     /**
 41      * 底部布局的高度
 42      */
 43     private int footerViewHeight;
 44 
 45 
 46     /**
 47      * 按下时的Y坐标
 48      */
 49     private int downY;
 50 
 51     private final int PULL_REFRESH = 0;//下拉刷新的状态
 52     private final int RELEASE_REFRESH = 1;//松开刷新的状态
 53     private final int REFRESHING = 2;//正在刷新的状态
 54 
 55     /**
 56      * 当前下拉刷新处于的状态
 57      */
 58     private int currentState = PULL_REFRESH;
 59 
 60     /**
 61      * 头部布局在下拉刷新改变时,图标的动画
 62      */
 63     private RotateAnimation upAnimation,downAnimation;
 64 
 65     /**
 66      * 当前是否在加载数据
 67      */
 68     private boolean isLoadingMore = false;
 69 
 70     public CustomRefreshListView(Context context) {
 71         this(context,null);
 72     }
 73 
 74     public CustomRefreshListView(Context context, AttributeSet attrs) {
 75         super(context, attrs);
 76         init();
 77     }
 78 
 79     private void init(){
 80         //设置滑动监听
 81         setOnScrollListener(this);
 82         //初始化头布局
 83         initHeaderView();
 84         //初始化头布局中图标的旋转动画
 85         initRotateAnimation();
 86         //初始化为尾布局
 87         initFooterView();
 88     }
 89 
 90 
 91     /**
 92      * 初始化headerView
 93      */
 94     private void initHeaderView() {
 95         headerView = View.inflate(getContext(), R.layout.head_custom_listview, null);
 96         iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
 97         pb_rotate = (ProgressBar) headerView.findViewById(R.id.pb_rotate);
 98         tv_state = (TextView) headerView.findViewById(R.id.tv_state);
 99         tv_time = (TextView) headerView.findViewById(R.id.tv_time);
100 
101         //测量headView的高度
102         headerView.measure(0, 0);
103         //获取高度,并保存
104         headerViewHeight = headerView.getMeasuredHeight();
105         //设置paddingTop = -headerViewHeight;这样,该控件被隐藏
106         headerView.setPadding(0, -headerViewHeight, 0, 0);
107         //添加头布局
108         addHeaderView(headerView);
109     }
110 
111     /**
112      * 初始化旋转动画
113      */
114     private void initRotateAnimation() {
115 
116         upAnimation = new RotateAnimation(0, -180, 
117                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,
118                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);
119         upAnimation.setDuration(300);
120         upAnimation.setFillAfter(true);
121 
122         downAnimation = new RotateAnimation(-180, -360, 
123                 RotateAnimation.RELATIVE_TO_SELF, 0.5f,
124                 RotateAnimation.RELATIVE_TO_SELF, 0.5f);
125         downAnimation.setDuration(300);
126         downAnimation.setFillAfter(true);
127     }
128 
129     //初始化底布局,与头布局同理
130     private void initFooterView() {
131         footerView = View.inflate(getContext(), R.layout.foot_custom_listview, null);
132         footerView.measure(0, 0);
133         footerViewHeight = footerView.getMeasuredHeight();
134         footerView.setPadding(0, -footerViewHeight, 0, 0);
135         addFooterView(footerView);
136     }
137 
138     @Override
139     public boolean onTouchEvent(MotionEvent ev) {
140         switch (ev.getAction()) {
141         case MotionEvent.ACTION_DOWN:
142             //获取按下时y坐标
143             downY = (int) ev.getY();
144             break;
145         case MotionEvent.ACTION_MOVE:
146 
147             if(currentState==REFRESHING){
148                 //如果当前处在滑动状态,则不做处理
149                 break;
150             }
151             //手指滑动偏移量
152             int deltaY = (int) (ev.getY() - downY);
153 
154             //获取新的padding值
155             int paddingTop = -headerViewHeight + deltaY;
156             if(paddingTop>-headerViewHeight && getFirstVisiblePosition()==0){
157                 //向下滑,且处于顶部,设置padding值,该方法实现了顶布局慢慢滑动显现
158                 headerView.setPadding(0, paddingTop, 0, 0);
159 
160                 if(paddingTop>=0 && currentState==PULL_REFRESH){
161                     //从下拉刷新进入松开刷新状态
162                     currentState = RELEASE_REFRESH;
163                     //刷新头布局
164                     refreshHeaderView();
165                 }else if (paddingTop<0 && currentState==RELEASE_REFRESH) {
166                     //进入下拉刷新状态
167                     currentState = PULL_REFRESH;
168                     refreshHeaderView();
169                 }
170 
171 
172                 return true;//拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动
173             }
174 
175 
176             break;
177         case MotionEvent.ACTION_UP:
178             if(currentState==PULL_REFRESH){
179                 //仍处于下拉刷新状态,未滑动一定距离,不加载数据,隐藏headView
180                 headerView.setPadding(0, -headerViewHeight, 0, 0);
181             }else if (currentState==RELEASE_REFRESH) {
182                 //滑倒一定距离,显示无padding值得headcView
183                 headerView.setPadding(0, 0, 0, 0);
184                 //设置状态为刷新
185                 currentState = REFRESHING;
186 
187                 //刷新头部布局
188                 refreshHeaderView();
189 
190                 if(listener!=null){
191                     //接口回调加载数据
192                     listener.onPullRefresh();
193                 }
194             }
195             break;
196         }
197         return super.onTouchEvent(ev);
198     }
199 
200     /**
201      * 根据currentState来更新headerView
202      */
203     private void refreshHeaderView(){
204         switch (currentState) {
205         case PULL_REFRESH:
206             tv_state.setText("下拉刷新");
207             iv_arrow.startAnimation(downAnimation);
208             break;
209         case RELEASE_REFRESH:
210             tv_state.setText("松开刷新");
211             iv_arrow.startAnimation(upAnimation);
212             break;
213         case REFRESHING:
214             iv_arrow.clearAnimation();//因为向上的旋转动画有可能没有执行完
215             iv_arrow.setVisibility(View.INVISIBLE);
216             pb_rotate.setVisibility(View.VISIBLE);
217             tv_state.setText("正在刷新...");
218             break;
219         }
220     }
221 
222     /**
223      * 完成刷新操作,重置状态,在你获取完数据并更新完adater之后,去在UI线程中调用该方法
224      */
225     public void completeRefresh(){
226         if(isLoadingMore){
227             //重置footerView状态
228             footerView.setPadding(0, -footerViewHeight, 0, 0);
229             isLoadingMore = false;
230         }else {
231             //重置headerView状态
232             headerView.setPadding(0, -headerViewHeight, 0, 0);
233             currentState = PULL_REFRESH;
234             pb_rotate.setVisibility(View.INVISIBLE);
235             iv_arrow.setVisibility(View.VISIBLE);
236             tv_state.setText("下拉刷新");
237             tv_time.setText("最后刷新:"+getCurrentTime());
238         }
239     }
240 
241     /**
242      * 获取当前系统时间,并格式化
243      * @return
244      */
245     private String getCurrentTime(){
246         SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
247         return format.format(new Date());
248     }
249 
250     private OnRefreshListener listener;
251     public void setOnRefreshListener(OnRefreshListener listener){
252         this.listener = listener;
253     }
254     public interface OnRefreshListener{
255         void onPullRefresh();
256         void onLoadingMore();
257     }
258 
259     /**
260      * SCROLL_STATE_IDLE:闲置状态,就是手指松开
261      * SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动,就是按着来滑动
262      * SCROLL_STATE_FLING:快速滑动后松开
263      */
264     @Override
265     public void onScrollStateChanged(AbsListView view, int scrollState) {
266         if(scrollState==OnScrollListener.SCROLL_STATE_IDLE 
267                 && getLastVisiblePosition()==(getCount()-1) &&!isLoadingMore){
268             isLoadingMore = true;
269 
270             footerView.setPadding(0, 0, 0, 0);//显示出footerView
271             setSelection(getCount());//让listview最后一条显示出来,在页面完全显示出底布局
272 
273             if(listener!=null){
274                 listener.onLoadingMore();
275             }
276         }
277     }
278 
279 
280     @Override
281     public void onScroll(AbsListView view, int firstVisibleItem,
282             int visibleItemCount, int totalItemCount) {
283     }
284 
285 }

代码的注释写的比较详细,在这里只说明主要的逻辑。

下拉刷新

下拉刷新是通过设置setOnTouchListener()方法,监听触摸事件,通过手指滑动的不同处理实现相应逻辑。

实现比较复杂,分为了三个情况,初始状态(显示下拉刷新),释放刷新状态,刷新状态。

其中下拉刷新状态和释放状态的改变,是由于手指滑动的不同距离,是在MotionEvent.ACTION_MOVE中进行判断,该判断不处理任何数据逻辑,只是根据手指滑动的偏移量该表UI的显示。

刷新状态的判断是在MotionEvent.ACTION_UP手指抬起时判断的。这很好理解,因为最终下拉刷新是否加载数据的确定,是由我们手指离开屏幕时与初始值的偏移量确定的。如果我们的偏移量小于了头布局的高度,代表不刷新,继续隐藏头布局。如果偏移量大于了头布局的高度,代表刷新,修改UI,同时通过接口回调,让其持有者进行加载数据。

上拉加载

上拉加载和下拉刷新不同,他的视线较为简单,我们通过ListView的滚动监听进行处理相应逻辑。即setOnScrollListener(this);

该方法需要实现两个回调方法

  • public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount):滚动监听的调用。
  • public void onScrollStateChanged(AbsListView view, int scrollState):滑动状态改变的回调。其中scrollState为回调的状态,可能值为 
    • SCROLL_STATE_IDLE:闲置状态,手指松开后的状态回调
    • SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动的状态回调
    • SCROLL_STATE_FLING:手指松开后惯性滑动的回调

我们在onScrollStateChanged中进行判断,主要判断一下条件;

  • 是否是停止状态
  • 是否滑倒最后
  • 是否正在加载数据

如果符合条件,则开始加载数据,通过接口回调。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM