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