Android自定義下拉刷新、上拉加載炫酷動畫效果

目    錄(本篇字數:2120)

介紹html

實現思路和代碼java

佈局文件android

下拉刷新實現ide

上拉加載實現佈局

飛機轉頭和動畫代碼post

博文續篇動畫

爲以前的自定義View添加DrawerLayout(側拉抽屜),爲自定義View系列畫上完美句號。spa


  • 介紹

    繼續我上篇文章的內容:一步步實現ListView的Item側滑刪除菜單效果,仿QQ的聊天頁面側滑刪除,這篇我將給ListView加上上拉刷新、下拉加載的動畫效果。.net

    其實,這篇內容和上篇內容用到的原理、邏輯、思路及實現等基本都相似。所謂一通百通啊,真的是這樣,你只要掌握自定義View的一些套路,其實也不是很難嘛。code

    主要解決問題(ListView 與下拉刷新、上拉加載的滑動衝突)

    先來看看我實現的效果,首先是上拉刷新的效果:

  • 效果圖

  • 實現思路和代碼

    那麼看這樣實現,若是你沒作過的話,是否是以爲這個很複雜呢?其實並否則。首先,依然是咱們的佈局,佈局分上、中、下三部分。上爲上拉刷新內容、中爲ListView、下爲下拉加載內容。只要你清楚了這樣的佈局,那麼實現起來輕輕鬆鬆啊,有沒有?

  • 佈局文件

    看一下咱們的佈局文件:

<listview.example.x.slidelistview.RefreshLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="180dp"
            android:background="@android:color/holo_red_dark"
            android:gravity="center_horizontal">

            <ProgressBar
                android:id="@+id/refresh_progress"
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:layout_gravity="bottom|right"
                tools:ignore="RtlHardcoded" />

            <TextView
                android:id="@+id/tv_refresh_state"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom|center_horizontal"
                android:layout_marginBottom="32dp"
                android:layout_marginTop="8dp"
                android:textColor="@android:color/white" />

            <ImageView
                android:id="@+id/iv_refreshing"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:layout_gravity="center_horizontal|bottom"
                android:src="@drawable/ic_flight_black_24dp" />
        </FrameLayout>

        <ListView
            android:id="@+id/lv_contact"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:background="@android:color/holo_orange_dark"
            android:gravity="center">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center_horizontal"
                android:orientation="vertical">

                <ProgressBar
                    android:id="@+id/load_progress"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />

                <TextView
                    android:id="@+id/tv_load_state"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="正在加載"
                    android:textColor="@android:color/white" />
            </LinearLayout>

        </RelativeLayout>
    </listview.example.x.slidelistview.RefreshLayout>

    佈局裏的內容元素我就不作多的說明了,也沒什麼好說明的。咱們看最外層這個控件,是我自定義的繼承FrameLayout的一個RefreshLayout類。爲何用FrameLayout?我在上篇文章已經作了說明了,不清楚的依然能夠在上面推薦連接點進去查看。首先,咱們將這三個傢伙進行佈局,固然是從上到下的那種。來看看代碼:

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mHeaderView.layout(0, -mHeaderHeight, mHeaderWidth, 0);
        mContentView.layout(0, 0, mContentWidth, mContentHeight);
        mFooterView.layout(0, mContentHeight, mFooterWidth, mContentHeight + mFooterHeight);
    }
  • 下拉刷新實現

    這就完成了我從上至下的佈局。既然,咱們把它佈局到了屏幕上方,顯然是看不見的。如今只能經過手指將它滑動下來顯示,那麼咱們在touch事件作滑動處理,來看看代碼。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = x;
                startY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                if (isTop) {
                    if (firstDownTag == 0) {
                        /**
                         * 若是是第一次的話,由於事件傳遞緣由
                         * onInterceptTouchEvent()執行了 ACTION_DOWN事件
                         * 標記了startY的值(這個值也許很是大,是根據手指按下的y座標來定的)
                         * 關鍵是onTouchEvent的ACTION_DOWN沒法獲得執行,因此 scrollTo(0, disY);將直接移動到startY的位置
                         * 效果就是致使第一次向下拉,瞬間移動了很是多
                         */
                        firstDownTag++;
                    } else {
                        final float dy = y - startY;
                        int disY = (int) (getScrollY() - dy);
                        if (-disY <= 0) {
                            disY = 0;
                        }

                        if (-disY < mHeaderHeight) {
                            scrollTo(0, disY);
                            mRefreshProgress.setVisibility(INVISIBLE);
                            if (-disY < mRefreshHeight) {
                                tvRefreshText.setText("準備起飛");
                                startRefreshIcon();
                            } else {
                                tvRefreshText.setText("加速中");
                                stopRefreshIcon();
                            }
                        }
                    }
                }
                startX = x;
                startY = y;
                break;
            case MotionEvent.ACTION_UP:
                isIntercept = false;
                if (-getScrollY() > mRefreshHeight) {
                    startRefreshing();
                } else {
                    stopRefreshing();
                }
                break;
        }
        return true;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final float x = ev.getX();
        final float y = ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isIntercept = false;
                upX = x;
                upY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                if (isTop) {
                    if (upY - y < 0) {
                        isIntercept = true;
                    } else if (y - upY < 0) {
                        isIntercept = false;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                upY = 0;
                upX = 0;
                break;
        }
        return isIntercept;
    }

    這裏有一個大坑咱們得爬,就是在RefreshLayout不攔截事件的時候,它默認會分發事件給ListView,致使ListView把touch事件給消費了,因此不攔截的狀況下,儘管你怎麼往下拉,它始終是拉不出來的。哈哈,那麼解決方法就是咱們攔截它。可是攔截老是有條件的,這個條件有兩點:

一、ListView的子項在第一個,也就是到達最頂部。

二、若是在ListView到達頂部前提下,手指還繼續往下滑動,那麼就是下拉刷新的動做了,在此時攔截它。

   上面代碼就是作了這兩件事情,還有就是滑動動畫。固然,這得在咱們RefreshLayout中實現對ListView的滑動監聽的接口,判斷是否處於頂部和底部:​​​​​​。還有一個就是咱們的飛機動畫了,這比較簡單了。

  • 上拉加載實現

    既然說完了下拉刷新,下面咱們來看看上拉加載動畫吧。

  • 上拉加載效果

    其實,上拉加載只是和咱們的下拉刷新方向相反的。既然咱們已經實現了下拉刷新,那麼上拉加載還不是手到擒來嘛。由於咱們前面已經處理過了事件衝突,因此能夠一路向前,通暢無阻。

    咱們看一下關鍵代碼,最主要的仍是咱們的touch事件的代碼,添加上拉加載的邏輯代碼,其餘都很是簡單了:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = x;
                startY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                if (isTop) {
                    if (firstDownTag == 0) {
                        /**
                         * 若是是第一次的話,由於事件傳遞緣由
                         * onInterceptTouchEvent()執行了 ACTION_DOWN事件
                         * 標記了startY的值(這個值也許很是大,是根據手指按下的y座標來定的)
                         * 關鍵是onTouchEvent的ACTION_DOWN沒法獲得執行,因此 scrollTo(0, disY);將直接移動到startY的位置
                         * 效果就是致使第一次向下拉,瞬間移動了很是多
                         */
                        firstDownTag++;
                    } else {
                        final float dy = y - startY;
                        int disY = (int) (getScrollY() - dy);
                        if (-disY <= 0) {
                            disY = 0;
                        }

                        if (-disY < mHeaderHeight) {
                            scrollTo(0, disY);
                            mRefreshProgress.setVisibility(INVISIBLE);
                            if (-disY < mRefreshHeight) {
                                tvRefreshText.setText("準備起飛");
                                startRefreshIcon();
                            } else {
                                tvRefreshText.setText("加速中");
                                stopRefreshIcon();
                            }
                        }
                    }
                } else if (isBottom) {/** 在ListView底部,繼續上拉 **/
                    final float dy = y - startY;
                    int disY = (int) (getScrollY() - dy);
                    if (disY < 0) {
                        disY = 0;
                        ivLoadingIcon.setVisibility(VISIBLE);
                        mLoadingProgress.setVisibility(INVISIBLE);
                    } else if (disY >= mLoadingHeight) {
                        disY = mLoadingHeight + 5;
                    }
                    scrollTo(getScrollX(), disY);

//                    if (dy < 0) {
//                        startLoadingIcon();
//                    } else {
//                        stopLoadingIcon();
//                    }
                }
                startX = x;
                startY = y;
                break;
            case MotionEvent.ACTION_UP:
                isIntercept = false;
                if (isTop) {
                    if (-getScrollY() > mRefreshHeight) {
                        startRefreshing();
                    } else {
                        stopRefreshing();
                    }
                } else if (isBottom) {
                    if (getScrollY() > mLoadingHeight) {
                        startLoading();
                    } else {
                        stopLoading();
                    }
                }
                break;
        }
        return true;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final float x = ev.getX();
        final float y = ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isIntercept = false;
                upX = downX = x;
                upY = downY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                if (isTop) {
                    /** 下拉刷新攔截 **/
                    if (upY - y < 0) {
                        isIntercept = true;
                    } else if (y - upY < 0) {
                        isIntercept = false;
                    }
                } else if (isBottom) {
                    /** 上拉加載攔截 **/
                    if (y - downY < 0) {
                        isIntercept = true;
                    } else if (y - downY > 0) {
                        isIntercept = false;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                downX = upY = 0;
                downX = upX = 0;
                break;
        }
        return isIntercept;
    }
  • 飛機轉頭和動畫代碼

private void stopRefreshing() {
        mScroller.startScroll(getScrollX(), getScrollY(), 0, -getScrollY());
        /**
         * ListView子項移動到第一個
         */
        mListView.setSelection(0);
        invalidate();
    }

    private void startRefreshing() {
        mScroller.startScroll(getScrollX(), getScrollY(), 0, -mRefreshHeight - getScrollY());
        tvRefreshText.setText("起飛咯~");
        mRefreshProgress.setVisibility(VISIBLE);
        startIconAnimation();
        invalidate();
        /**
         * 模擬刷新完成,延遲關閉
         */
        handler.postDelayed(() -> stopRefreshing(), 2000);
    }

    private void startLoading() {
        mScroller.startScroll(getScrollX(), getScrollY(), 0, mFooterHeight - getScrollY());
        ivLoadingIcon.setVisibility(INVISIBLE);
        mLoadingProgress.setVisibility(VISIBLE);
        invalidate();
        handler.postDelayed(() -> stopLoading(), 1500);
    }

    private void stopLoading() {
        mScroller.startScroll(getScrollX(), getScrollY(), 0, -getScrollY(),1500);
        ivLoadingIcon.setVisibility(VISIBLE);
        mLoadingProgress.setVisibility(INVISIBLE);
        ivLoadingIcon.setPivotX(ivLoadingIcon.getWidth() / 2);
        ivLoadingIcon.setPivotY(ivLoadingIcon.getHeight() / 2);
        ivLoadingIcon.setRotation(180);
        invalidate();
    }

    private void startIconAnimation() {
        TranslateAnimation animation = new TranslateAnimation(0, 0,
                getScaleY(), -mRefreshHeight);
        animation.setFillAfter(false);
        animation.setDuration(2000);
        ivRefreshIcon.startAnimation(animation);
    }

    private void startRefreshIcon() {
        ivRefreshIcon.setPivotX(ivRefreshIcon.getWidth() / 2);
        ivRefreshIcon.setPivotY(ivRefreshIcon.getHeight() / 2);
        ivRefreshIcon.setRotation(180);
    }

    private void stopRefreshIcon() {
        ivRefreshIcon.setPivotX(ivRefreshIcon.getWidth() / 2);
        ivRefreshIcon.setPivotY(ivRefreshIcon.getHeight() / 2);
        ivRefreshIcon.setRotation(360);
    }

    那麼,咱們整個下拉刷新、上拉加載的最終效果:

博文續篇

爲以前的自定義View添加DrawerLayout(側拉抽屜),爲自定義View系列畫上完美句號。

©原文連接:https://blog.csdn.net/smile_Running/article/details/81950872

@做者博客:_Xu2WeI

@更多博文:查看做者的更多博文

相關文章
相關標籤/搜索