首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Android 下拉刷新與上拉載入列表元件 PullToLoadListView 完整實作

     編輯:Java 相關知識

自訂 ListView 實現雙向下拉刷新

PullToLoadListView 繼承 ListView 並實作 OnScrollListener,支援頂部下拉刷新、底部上拉載入、狀態機制管理、指示器動畫、彈性回彈效果。

狀態機設計

private static final int STATE_NON = 0;           // 閒置
private static final int STATE_PULL_TO_REFRESH = 1; // 下拉/上拉中
private static final int STATE_RELEASE_TO_REFRESH = 2; // 釋放觸發
private static final int STATE_REFRESHING = 3;      // 載入中

核心架構

  • HeaderView / FooterView:包含 ProgressBar、箭頭圖示、提示文字
  • 動畫系統:RotateAnimation 箭頭旋轉、ResetAnimation (Thread) 回彈平滑隱藏
  • 觸控事件處理:onTouchEvent 計算位移向量,根據 firstVisibleItem、lastVisibleItem 判斷觸發區域

關鍵方法節錄

初始化 Header/Footer

private void init(Context context) {
    LayoutInflater inflater = LayoutInflater.from(context);
    headerView = inflater.inflate(R.layout.view_pull_header, null);
    footerView = inflater.inflate(R.layout.view_pull_footer, null);
    
    headerProgressBar = headerView.findViewById(R.id.progressbar);
    headerImageArrow = headerView.findViewById(R.id.arrow);
    headerText = headerView.findViewById(R.id.text);
    // 箭頭旋轉動畫
    headerArrowAnim = new RotateAnimation(0, -180, 
        Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    headerArrowAnim.setDuration(300);
    headerArrowAnim.setFillAfter(true);
    
    measureView(headerView);
    measureView(footerView);
    headerHeight = headerView.getMeasuredHeight();
    footerHeight = footerView.getMeasuredHeight();
    
    // 初始隱藏在頂部/底部外
    headerView.setPadding(0, -headerHeight, 0, 0);
    footerView.setPadding(0, -footerHeight, 0, 0);
    addHeaderView(headerView, null, false);
    addFooterView(footerView, null, false);
    super.setOnScrollListener(this);
}

觸控移動處理 (核心邏輯)

private void handleMoveHeaderEvent(MotionEvent ev) {
    headerIsHandling = true;
    float tempY = ev.getRawY();
    float vector = (tempY - prevY) / 2; // 阻尼係數 0.5
    prevY = tempY;
    
    if (vector > 0) { // 向下拉
        int newPadding = Math.min((int)(headerView.getPaddingTop() + vector), headerHeight / 2);
        headerView.setPadding(0, newPadding, 0, 0);
        if (state != STATE_REFRESHING) {
            changeHeaderViewByState(newPadding > 0 ? STATE_RELEASE_TO_REFRESH : STATE_PULL_TO_REFRESH);
        }
    } else { // 向上推回
        if (state == STATE_RELEASE_TO_REFRESH || state == STATE_PULL_TO_REFRESH) {
            int newPadding = Math.max((int)(headerView.getPaddingTop() + vector), -headerHeight);
            headerView.setPadding(0, newPadding, 0, 0);
            if (newPadding <= -headerHeight) {
                changeHeaderViewByState(STATE_NON);
                headerIsHandling = false;
            } else if (newPadding <= 0) {
                changeHeaderViewByState(STATE_PULL_TO_REFRESH);
            }
        }
    }
}

釋放觸發載入

@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            prevY = ev.getRawY();
            break;
        case MotionEvent.ACTION_UP:
            if (state == STATE_RELEASE_TO_REFRESH) {
                if (headerIsHandling) {
                    changeHeaderViewByState(STATE_REFRESHING);
                    if (onLoadingListener != null) onLoadingListener.onLoadNew();
                }
                if (footerIsHandling) {
                    changeFooterViewByState(STATE_REFRESHING);
                    if (onLoadingListener != null) onLoadingListener.onLoadMore();
                }
            } else if (state == STATE_PULL_TO_REFRESH) {
                // 未達閾值,回彈重置
                resetHeader(); resetFooter();
                headerIsHandling = footerIsHandling = false;
            }
            break;
    }
    return super.onTouchEvent(ev);
}

回彈動畫執行緒

static class ResetAnimation extends Thread {
    static final int DURATION = 600, INTERVAL = 5;
    View view; int originalHeight, paddingTop; boolean run = false;
    
    public void run() {
        run = true;
        int total = originalHeight * 2 + paddingTop;
        int steps = DURATION / INTERVAL;
        int piece = total / steps;
        for (int t = 0; t < steps; t++) {
            final int nextPadding = paddingTop - t * piece;
            view.post(() -> { view.setPadding(0, nextPadding, 0, 0); view.postInvalidate(); });
            try { sleep(INTERVAL); } catch (InterruptedException ignored) {}
        }
        run = false;
    }
}

介面定義與使用方式

public interface OnLoadingListener {
    void onLoadNew();  // 下拉刷新
    void onLoadMore(); // 上拉載入
}

// Activity 中
PullToLoadListView listView = findViewById(R.id.list);
listView.setOnLoadingListener(new PullToLoadListView.OnLoadingListener() {
    @Override public void onLoadNew() {
        // 請求第一頁資料,完成後呼叫 listView.loadCompleted()
    }
    @Override public void onLoadMore() {
        // 請求下一頁資料,完成後呼叫 listView.loadCompleted()
    }
});

Layout XML 參考

<!-- view_pull_header.xml -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
    android:orientation="vertical" android:gravity="center" android:padding="10dp">
    <ProgressBar android:id="@+id/progressbar" android:layout_width="20dp" android:layout_height="20dp" />
    <ImageView android:id="@+id/arrow" android:layout_width="20dp" android:layout_height="20dp" android:src="@drawable/arrow_down" />
    <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉刷新" />
</LinearLayout>

現代替代方案

AndroidX 推薦使用 SwipeRefreshLayout (下拉刷新) 搭配 RecyclerView + OnScrollListener (上拉載入),或採用 SmartRefreshLayout 第三方庫功能更豐富。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved