1. 程式人生 > >自定義ListView實現下拉重新整理和上拉載入

自定義ListView實現下拉重新整理和上拉載入

實現ListView的下拉重新整理和上拉載入,需要先新增headerView和footerView,通過在拖動的過程中,控制頭尾佈局的paddingTop實現。先把paddingTop設為負值,來隱藏header,在下拉的過程中,不斷改變headerView的paddingTop,實現下拉過程中headerView慢慢顯示的效果。

下拉重新整理,是先將listview的頭部隱藏,然後用手勢拖動螢幕重新整理。

那麼,listview的頭部如何隱藏呢?這裡用的是header.setPadding(0,-headerViewHeight,0,0)來隱藏頭部。

當MotionEvent.ACTION_DOWN(手勢按下)觸發時,需要一個downY記錄手勢按下時y的偏移值,
然後隨著手勢的滑動即 MotionEvent.ACTION_MOVE(手勢滑動)觸發時,又需要一個moveY記錄移動的偏移值,
然後dy=moveY- downY得到的就是手指滑動的距離。然後用 paddingTop = (int) (dy - mHeadViewHeight)得到頭部的頂部到螢幕的頂部距離。如果想paddingTop >= 0則說明頭部完全顯示,如果paddingTop <0 則頭部沒有完全顯示。

下拉重新整理的幾種狀態:

 /**
     * 下拉重新整理
     */
    public static final int STATE_PULL_TO_REFRESH = 1;

    /**
     * 釋放重新整理
     */
    public static final int STATE_RELASE_TO_REFRESH = 2;

    /**
     * 正在重新整理
     */
    public static final int STATE_REFRESHING = 3;

自定義包含下拉重新整理和上拉載入功能的的ListView:

package
com.example.a01_pulltorefreshview.widget; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.AbsListView; import android.widget.ImageView; import
android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import com.example.a01_pulltorefreshview.R; import java.text.SimpleDateFormat; import java.util.Date; /** * 自定義包含下拉重新整理和上拉載入功能的的ListView * Created by xiaoyehai on 2016/11/24. */ public class PullToRefreshListView extends ListView implements AbsListView.OnScrollListener { /** * 下拉重新整理 */ public static final int STATE_PULL_TO_REFRESH = 1; /** * 釋放重新整理 */ public static final int STATE_RELASE_TO_REFRESH = 2; /** * 正在重新整理 */ public static final int STATE_REFRESHING = 3; /** * 當前狀態 */ private int mCurrentState = STATE_PULL_TO_REFRESH; /** * 下拉重新整理頭佈局 */ private View mHeadView; /** * 下拉重新整理狀態 */ private TextView tvState; /** * 下拉重新整理時間 */ private TextView tvTime; /** * 下拉重新整理旋轉箭頭圖片 */ private ImageView ivArrow; /** * 下拉重新整理ProgressBar */ private ProgressBar pbProgress; /** * 下拉重新整理按下時Y抽座標 */ private float startY; //private int startY = -1; /** * 下拉重新整理移動時Y抽座標 */ private float endY; /** * 下拉重新整理頭佈局的高度 */ private int mHeadViewHeight; /** * 下拉重新整理箭頭旋轉動畫 */ private RotateAnimation animUp; /** * 下拉重新整理箭頭旋轉動畫 */ private RotateAnimation animDown; /** * 下拉重新整理過程中和頂部的padding */ private int paddingTop; /** * 上拉載入底部佈局 */ private View mFooterView; /** * 上拉載入底部佈局高度 */ private int mFooterViewHeight; /** * 是否正在載入更多 */ private boolean isLoadMore; /** * 下拉重新整理和上拉載入事件監聽 */ private onRefreshListener onRefreshListener; public PullToRefreshListView(Context context) { this(context, null); } public PullToRefreshListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } /** * 初始化頭佈局,腳佈局 */ private void init() { initHeadView(); initFoodView(); initAnimation(); } /** * 初始化頭佈局 */ private void initHeadView() { mHeadView = View.inflate(getContext(), R.layout.pull_to_refresh_head, null); this.addHeaderView(mHeadView); ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow); tvState = (TextView) mHeadView.findViewById(R.id.tv_state); tvTime = (TextView) mHeadView.findViewById(R.id.tv_time); pbProgress = (ProgressBar) mHeadView.findViewById(R.id.pd_loading); //按照設定的規則測量寬高 mHeadView.measure(0, 0); //控制元件高度 mHeadViewHeight = mHeadView.getMeasuredHeight(); //隱藏頭佈局 mHeadView.setPadding(0, -mHeadViewHeight, 0, 0); } /** * 初始化底部,載入更多 */ private void initFoodView() { mFooterView = View.inflate(getContext(), R.layout.pull_to_refresh_footer, null); this.addFooterView(mFooterView); //測量控制元件 mFooterView.measure(0, 0); //控制元件的高度 mFooterViewHeight = mFooterView.getMeasuredHeight(); //隱藏控制元件 mFooterView.setPadding(0, -mFooterViewHeight, 0, 0); //設定監聽,載入更多 this.setOnScrollListener(this); } /** * 初始化頭佈局箭頭動畫 */ private void initAnimation() { animUp = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); animUp.setDuration(200); animUp.setFillAfter(true); animDown = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); animDown.setDuration(200); animDown.setFillAfter(true); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //記錄起點按下的Y座標 startY = ev.getY(); break; case MotionEvent.ACTION_MOVE: //添加了廣告輪播頭佈局: //當用戶按住廣告圖片進行下拉時ACTION_DOWN會被viewpager消費掉, //導致startY沒有被賦值,此處需要重新獲取一下 // if (startY == -1) { // startY = (int) ev.getY(); // } //如果此時正在重新整理,跳出迴圈,不讓再次重新整理 if (mCurrentState == STATE_REFRESHING) { break; //return super.onTouchEvent(ev); //或者這樣寫,執行父類的處理 } endY = ev.getY(); //手指滑動偏移量 float dy = endY - startY; //當前顯示第一個條目的位置 int firstVisiblePosition = getFirstVisiblePosition(); //往下拉,並且顯示的是第一個可見item時第一個的時候,才能劃出控制元件 if (dy > 0 && firstVisiblePosition == 0) { //計算當前控制元件的padding paddingTop = (int) (dy - mHeadViewHeight); mHeadView.setPadding(0, paddingTop, 0, 0); //當控制元件拉至完全顯示,改為鬆開重新整理 if (paddingTop >= 0 && mCurrentState != STATE_RELASE_TO_REFRESH) { mCurrentState = STATE_RELASE_TO_REFRESH; //鬆開重新整理狀態 //重新整理頭佈局 refreshState(); } else if (paddingTop < 0 && mCurrentState != STATE_PULL_TO_REFRESH) { //不完全顯示,下拉重新整理 mCurrentState = STATE_PULL_TO_REFRESH;//下拉重新整理狀態 //重新整理頭佈局 refreshState(); } return true; //當前事件被消費,攔截TouchMove,不讓listview處理該次move事件,會造成listview無法滑動 } break; case MotionEvent.ACTION_UP: // startY = -1; if (mCurrentState == STATE_PULL_TO_REFRESH) { //下拉的時候沒完全顯示就鬆開或者回去的時候,就恢復 mHeadView.setPadding(0, -mHeadViewHeight, 0, 0); } else if (mCurrentState == STATE_RELASE_TO_REFRESH) { //鬆開重新整理狀態的時候擡起手要變成正在重新整理 mCurrentState = STATE_REFRESHING; refreshState(); //正在重新整理時完整展示頭佈局 mHeadView.setPadding(0, 0, 0, 0); //介面回撥,通知載入資料 if (onRefreshListener != null) { onRefreshListener.onRefresh(); } } break; } return super.onTouchEvent(ev); } /** * 根據當前狀態重新整理控制元件 */ private void refreshState() { switch (mCurrentState) { case STATE_PULL_TO_REFRESH: // TODO: 2016/11/24 下拉重新整理狀態 tvState.setText("下拉重新整理"); pbProgress.setVisibility(INVISIBLE); ivArrow.setVisibility(VISIBLE); ivArrow.startAnimation(animDown); break; case STATE_RELASE_TO_REFRESH: // TODO: 2016/11/24 鬆開重新整理狀態 tvState.setText("釋放重新整理"); pbProgress.setVisibility(INVISIBLE); ivArrow.setVisibility(VISIBLE); ivArrow.startAnimation(animUp); break; case STATE_REFRESHING: // TODO: 2016/11/24 正在重新整理狀態 tvState.setText("正在重新整理..."); pbProgress.setVisibility(VISIBLE); ivArrow.setVisibility(INVISIBLE); ivArrow.clearAnimation(); //清除箭頭動畫,否則無法隱藏 break; } } /** * 重新整理結束,收起控制元件 * * @param success:是否下拉重新整理成功 */ public void onRefreshComplete(boolean success) { if (isLoadMore) { //載入更多 //載入更多完成後,收起控制元件 mFooterView.setPadding(0, -mFooterViewHeight, 0, 0); isLoadMore = false; } else { //下拉重新整理 mHeadView.setPadding(0, -mHeadViewHeight, 0, 0); mCurrentState = STATE_PULL_TO_REFRESH; tvState.setText("下拉重新整理"); pbProgress.setVisibility(INVISIBLE); ivArrow.setVisibility(VISIBLE); //設定重新整理時間,只有重新整理成功之後才更新時間 if (success) { tvTime.setText("最後重新整理時間:" + getCurrentTime()); } } } /** * 設定下拉重新整理裡面的時間 */ private String getCurrentTime() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String time = sdf.format(new Date()); return time; } /** * SCROLL_STATE_IDLE:當螢幕停止滾動時 * SCROLL_STATE_TOUCH_SCROLL:當螢幕以觸屏方式滾動並且手指還在螢幕 * SCROLL_STATE_FLING:當用戶之前滑動螢幕並擡起手指,螢幕以慣性滾動 * * @param view * @param scrollState */ @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // TODO: 2016/11/19 滑動狀態發生變化的回撥 if (scrollState == SCROLL_STATE_IDLE) { //空閒狀態 int lastVisiblePosition = getLastVisiblePosition(); //顯示最後一個item並且沒有載入更多 if (lastVisiblePosition == getCount() - 1 && !isLoadMore) { isLoadMore = true; mFooterView.setPadding(0, 0, 0, 0); //顯示 //setSelection(getCount() - 1); //顯示在最後一個item上 setSelection(getCount()); //顯示在最後一個item上 //通知主介面載入下一頁資料 if (onRefreshListener != null) { onRefreshListener.onLoadMore(); } } } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // TODO: 2016/11/19 滑動過程的回撥 } /** * 下拉重新整理的回撥介面 */ public interface onRefreshListener { //通知重新整理(正在重新整理) void onRefresh(); //通知載入更多 void onLoadMore(); } /** * 暴露介面,設定監聽 * * @param onRefreshListener */ public void setOnRefreshListener(PullToRefreshListView.onRefreshListener onRefreshListener) { this.onRefreshListener = onRefreshListener; } }

頭佈局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="10dp">

        <ImageView
            android:id="@+id/iv_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:src="@mipmap/common_listview_headview_red_arrow" />

        <ProgressBar
            android:id="@+id/pd_loading"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:indeterminateDrawable="@drawable/custom_progress"
            android:visibility="invisible" />
    </FrameLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下拉重新整理"
            android:textColor="#f00"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="3dp"
            android:text="最後重新整理時間:2015-10-21 09:00:30"
            android:textColor="#a000"
            android:textSize="16sp" />
    </LinearLayout>

</LinearLayout>

custom_progress:

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="360">

    <shape
        android:innerRadius="18dp"
        android:shape="ring"
        android:thickness="5dp"
        android:useLevel="false">

        <gradient
            android:centerColor="#9f00"
            android:endColor="#f00"
            android:startColor="#fff"
            android:type="sweep" />
    </shape>
</rotate>

底部佈局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:gravity="center"
    android:layout_height="match_parent">

    <ProgressBar
        android:id="@+id/pd_loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminateDrawable="@drawable/custom_progress" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="載入中..."
        android:textColor="#f00"
        android:layout_marginLeft="10dp"
        android:textSize="18sp" />

</LinearLayout>

用法:

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context="com.example.a01_pulltorefreshview.MainActivity">

    <com.example.a01_pulltorefreshview.widget.PullToRefreshListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>
package com.example.a01_pulltorefreshview;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Window;

import com.example.a01_pulltorefreshview.widget.PullToRefreshListView;

import java.util.ArrayList;
import java.util.List;

/**
 * 自定義下拉重新整理和上拉載入的ListView
 */
public class MainActivity extends AppCompatActivity {

    private PullToRefreshListView mListView;

    private List<String> list;

    private ListViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        mListView = (PullToRefreshListView) findViewById(R.id.listview);
        list = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            list.add("這是一條ListView資料:" + i);
        }

        //新增頭部,要在setAdapter之前新增
        adapter = new ListViewAdapter(this, list);
        mListView.setAdapter(adapter);

        mListView.setOnRefreshListener(new PullToRefreshListView.onRefreshListener() {
            @Override
            public void onRefresh() {
                //TODO: 2016/11/24 下拉重新整理的時候回撥該方法,載入資料
                loadData();
            }

            @Override
            public void onLoadMore() {
                // TODO: 2016/11/24 上拉載入下一頁資料的回撥
                loadMoreData();
            }
        });
    }

    private void loadMoreData() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //下拉重新整理的資料
                list.add("上拉載入的資料");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        adapter.notifyDataSetChanged();
                        //載入結束,收起控制元件
                        mListView.onRefreshComplete(true);
                    }
                });
            }
        }).start();
    }

    private void loadData() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //下拉重新整理的資料
                list.add(0, "下拉重新整理的資料");

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        adapter.notifyDataSetChanged();
                        //載入結束,收起控制元件
                        mListView.onRefreshComplete(true);
                    }
                });
            }
        }).start();
    }
}