Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義控件實現ListView下拉刷新和上拉加載

自定義控件實現ListView下拉刷新和上拉加載

編輯:關於Android編程

下拉刷新和上拉加載時一個很常用的功能,剛好今天學了,好好的總結一把!

下拉刷新實現思路:

第一步:創建一個類繼承ListView 第二步:寫一個頭部,添加到listview中,先將其隱藏 第三步:設置監聽觸屏事件,判斷是否滑動到頂部 第四步:當到頂部的時候,通過下拉的位移來設置頭部的顯示高度,並根據頭部顯示的高度來設置對應的文字和動畫效果 第五步:當松手時,判斷需要進行哪一種更新並進行相應的操作

上拉加載實現思路:

由於兩個功能是寫在一個類裡面的,這裡就不需要再創建一個類了。

第一步:寫一個尾部,添加到listview中,先將其隱藏 第二步:設置監聽滾動監聽事件,判斷是否滑動到底部 第三部:加載數據

具體代碼實現

代碼裡面思路也很清楚,而且注釋很多,相信很容易看明白。
自定義控件類:

public class PullToRefresh extends ListView {

    private int downY;
    private View header;
    private int headerHeight;
    private ImageView ivArrow;
    private TextView tvTitle;
    private TextView tvTime;
    private RotateAnimation downAnin;
    private RotateAnimation upAnin;
    private boolean isLoading = false;
    private OnPullToRefreshListener onPullToRefreshListener;
    private static final int PULL_TO_REFRESH = 1; // 下拉刷新狀態
    private static final int RELEASE_TO_REFRESH = 2; // 釋放刷新狀態
    private static final int REFRESHING = 3; // 正在刷新狀態

    public PullToRefresh(Context context, AttributeSet attrs) {
        super(context, attrs);

        addHeader();
        initComponent();
        addFooter();
        // 設置滑動監聽事件

        setOnScrollListener(new OnScrollListener() {

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                int count = view.getAdapter().getCount(); // 獲取條目數

                switch (scrollState) {
                case OnScrollListener.SCROLL_STATE_FLING:

                    break;
                case OnScrollListener.SCROLL_STATE_IDLE:
                    // 在閒置狀態時判斷是否到達了底部
                    if (getLastVisiblePosition() == count - 1 && !isLoading) {
                        footer.setPadding(0, 0, 0, 0);
                        setSelection(count - 1);
                        isLoading = true;
                        if (onPullToRefreshListener != null) {
                            onPullToRefreshListener.loadMore();
                        }
                    }
                    break;
                case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:

                    break;

                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem,
                    int visibleItemCount, int totalItemCount) {

            }
        });

    }

    /**
     * 為控件添加一個尾部
     * */
    private void addFooter() {
        footer = View.inflate(getContext(), R.layout.footer, null);
        footer.measure(0, 0); // 手動測量尾部的高度,否則在這裡無法獲取到尾部的高度
        footerrHeight = header.getMeasuredHeight();
        footer.setPadding(0, -footerrHeight, 0, 0); // 通過設置尾部的paddingTop來將尾部先隱藏
        addFooterView(footer);
    }

    /**
     * 設置接口的引用
     * */
    public void setOnPullToRefreshListener(
            OnPullToRefreshListener onPullToRefreshListener) {
        this.onPullToRefreshListener = onPullToRefreshListener;
    }

    /**
     * 初始化組件找到它們
     * */
    private void initComponent() {
        ivArrow = (ImageView) findViewById(R.id.iv_arrow);
        ivLeft = (ImageView) findViewById(R.id.iv_left);
        tvTitle = (TextView) findViewById(R.id.tv_title);
        tvTime = (TextView) findViewById(R.id.tv_time);

        upAnin = new RotateAnimation(0, 180, RotateAnimation.RELATIVE_TO_SELF,
                0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        upAnin.setDuration(200);
        upAnin.setFillAfter(true);// 動畫完成時停留在那裡
        downAnin = new RotateAnimation(180, 0,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        downAnin.setDuration(200);
        downAnin.setFillAfter(true);// 動畫完成時停留在那裡
        loadingAnim = new RotateAnimation(0, 360,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        loadingAnim.setRepeatCount(RotateAnimation.INFINITE);
        loadingAnim.setRepeatMode(RotateAnimation.RESTART);
        loadingAnim.setInterpolator(new LinearInterpolator());
        loadingAnim.setDuration(1000);
    }

    /**
     * 第一步:為listview添加一個頭部
     * */
    private void addHeader() {
        header = View.inflate(getContext(), R.layout.header, null);
        header.measure(0, 0); // 手動測量頭部的高度,否則在這裡無法獲取到頭部的高度
        headerHeight = header.getMeasuredHeight();
        header.setPadding(0, -headerHeight, 0, 0); // 通過設置頭部的paddingTop來將頭部先隱藏
        addHeaderView(header);
    }

    // 第二步:監聽觸屏事件,判斷是否滑動到頂部並進行相應的操作
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downY = (int) ev.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            // 記錄下當前滑動的垂直坐標
            int moveY = (int) ev.getY();
            int dy = moveY - downY; // 垂直位移
            downY = moveY;
            int paddingTop = header.getPaddingTop();
            // 不是正在刷新狀態、到達頭部並且頭部在顯示的時候
            if (state != REFRESHING // 不是正在刷新狀態
                    && getFirstVisiblePosition() == 0 // 到達頭部
                    && (dy > 0 // 下滑
                    || paddingTop > -headerHeight)) { // 上滑,頭部在顯示
                header.setPadding(0, paddingTop + dy, 0, 0); // 設置頭部顯示狀態
                /**
                 * 第三步:通過下拉的高度來判斷進行何種刷新動作 1.頭部沒有完全露出來,進行下拉刷新 2.頭部完全露出來,則進行釋放刷新
                 * */
                if (paddingTop >= 0) {
                    // 進入釋放刷新狀態
                    setState(RELEASE_TO_REFRESH);
                } else {
                    // 進入下拉刷新狀態

                    setState(PULL_TO_REFRESH);
                }
                return true;
            }
            break;
        case MotionEvent.ACTION_UP:
            /**
             * 第四步:當松開手之後,判斷需要進行哪種刷新 這裡我們在resetHeader()方法裡面進行判斷
             * 
             * */
            resetHeader();
            break;

        default:
            break;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 設置刷新狀態
     * */
    private int state = PULL_TO_REFRESH; // 記錄當前狀態
    private ImageView ivLeft;
    private RotateAnimation loadingAnim;
    private View footer;
    private int footerrHeight;

    private void setState(int state) {
        if (this.state != state) {
            if (state == PULL_TO_REFRESH) {
                tvTitle.setText("下拉刷新");
                ivArrow.startAnimation(downAnin);
            } else if (state == RELEASE_TO_REFRESH) {
                tvTitle.setText("釋放刷新");
                ivArrow.startAnimation(upAnin);
            } else if (state == REFRESHING) {
                tvTitle.setText("正在刷新");
                tvTime.setText("最後刷新時間:"
                        + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                ivArrow.clearAnimation();// 清除箭頭動畫
                ivArrow.setVisibility(View.GONE);// 將箭頭隱藏

                ivLeft.setVisibility(View.VISIBLE); // 將正在刷新圖標顯示出來
                ivLeft.startAnimation(loadingAnim);// 開始刷新動畫

            }
            this.state = state;

        }
    }

    /**
     * 下拉刷新操作完成
     * */
    public void refreshFinish() {
        ivLeft.clearAnimation();
        ivLeft.setVisibility(View.GONE);
        ivArrow.setVisibility(View.VISIBLE);
        setState(PULL_TO_REFRESH);
        header.setPadding(0, -headerHeight, 0, 0);// 重新隱藏頭
    }
    /**
     * 上拉加載操作完成
     * */
    public void loadMoreFinish() {
        isLoading = false;
        footer.setPadding(0, -footerrHeight, 0, 0);
    }

    /**
     * 重新設置頭部
     * 
     * */
    private void resetHeader() {
        if (state == PULL_TO_REFRESH) {
            header.setPadding(0, -headerHeight, 0, 0); // 重新將頭部隱藏
        } else if (state == RELEASE_TO_REFRESH) {
            header.setPadding(0, 0, 0, 0); // 剛好將頭部完全顯示出來
            setState(REFRESHING); // 設置為正在刷新狀態
            // 開始加載數據
            if (onPullToRefreshListener != null) {
                onPullToRefreshListener.refresh();
            }

        }
    }

    public interface OnPullToRefreshListener {
        public void refresh(); // 刷新
        public void loadMore(); // 加載更多
    }

}

總結一下寫的過程中碰到的坑:

由於我們添加頭部和尾部的操作是在構造方法裡面進行的,系統還沒來得及為我們測量控件的高寬,因此我們在獲取控件高寬的時候必須自己調用measure()方法來測量,否則獲取到的控件高寬始終為0; 在監聽觸屏事件中,我們做了一個操作:downY = moveY;這樣我們所獲取到的垂直位移才是實時移動的位移。 在if (state != REFRESHING && getFirstVisiblePosition() == 0 && (dy > 0 || paddingTop > -headerHeight))這語句中,paddingTop > -headerHeight這句是用來判斷向上滑動並且頭部處於顯示狀態。如果沒有這個判斷,我們高頻率的小幅度滑動屏幕時,會發現頭部上面的空白區域會不斷的增大。 在setState(int state)方法處,我們需要設置一個變量來記錄當前控件所處的狀態,並通過這個變量狀態來決定是否執行方法裡面的代碼,否則你會發現,當你下拉控件的時候,那個指示圖標會不停的上下轉動,那是它在不斷的反復執行動畫的效果。 加載數據屬於耗時操作,必須在子線程中進行,否則會出現“卡卡”的現象,反正我由於忘記了這個東東找了好長時間。 在刷新完成之後,即在refreshFinish()方法裡面,我們需要進行動畫清除,不然再次下拉的時候會出現圖標“殘影”,還要將控件狀態還原到下拉刷新狀態setState(PULL_TO_REFRESH),不然保證你拉不了第二次,就這麼給力。 最後一點,為了降低代碼之間的耦合性,用到了接口回調,那麼,我們在需要加載數據的時候(控件類代碼中)必須先進行非空判斷,不然空指針異常等著你。

header.xml



    
    
    
        
        
    

footer.xml




    
    

在其他項目裡面測試的代碼:
布局文件activity_main.xml



    

    


MainActivity

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final PullToRefresh ptr = (PullToRefresh) findViewById(R.id.ptrlist);
        final List data = getData();
        final ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1,data);
        ptr.setAdapter(adapter);
        /**
         * 刷新加載數據,接口回調,大大降低了代碼間的耦合性
         * */
        ptr.setOnPullToRefreshListener(new OnPullToRefreshListener() {

            @Override
            public void refresh() {
                //加載數據屬於耗時操作,需要開啟子線程
                new Thread(new Runnable() {
                    public void run() {
                        SystemClock.sleep(1000);    //模擬數據
                        String newData = "下拉刷新更多的數據";
                        data.add(0,newData);
                        //更新UI的動作只能在主線程中完成
                        runOnUiThread(new Runnable() {
                            public void run() {
                                adapter.notifyDataSetChanged();
                                ptr.refreshFinish();
                            }
                        });

                    }
                }).start();
            }

            @Override
            public void loadMore() {
                // TODO Auto-generated method stub
                new Thread(new Runnable() {
                    public void run() {
                        SystemClock.sleep(1000);
                        String newData = "上拉加載更多的數據";
                        data.add(newData);
                        //更新UI的動作只能在主線程中完成
                        runOnUiThread(new Runnable() {
                            public void run() {
                                adapter.notifyDataSetChanged();
                                ptr.loadMoreFinish();
                            }
                        });
                    }
                }).start();
            }

        });
    }
    /**
     * 初始化數據
     * */
    private List getData() {
        List list = new ArrayList();
        for (int i = 0; i < 15; i++) {
            list.add("原始數據"+i);
        }
        return list;
    }

}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved