1. 程式人生 > >上拉載入更多,下拉重新整理的彈性ListView的實現

上拉載入更多,下拉重新整理的彈性ListView的實現

1.使用介紹 (1)首先在xml中定義
<cn.appleye.flexiblelistview.FlexibleListView
        android:id="@+id/flexible_list_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
(2)在程式碼中實現回撥就可以實現上拉和下拉功能
       mFlexibleListView = (FlexibleListView) findViewById(R.id.flexible_list_view);
       mFlexibleListView.setOnPullListener(new FlexibleListView.OnPullListener(){
            @Override
            public void onPullDown() {
                //下拉重新整理
            }

            @Override
            public void onPullUp() {
                //上拉載入更多
            }
        });
2.具體實現 拋開程式碼細節,要實現彈性效果和上拉以及下拉功能需要了解以下幾點 (1)什麼是彈性效果?列表滑到底部或者頂部之後,還可以繼續滑動一定距離,然後再慢慢的恢復到底部或者頂部,恢復的過程有一個彈性的效果。 (2)什麼時候觸發?上面可以看到,滑到底部或者頂部之後開始觸發 (3)滑動多少距離開始恢復?定義好一個距離,合適就好 (4)恢復的過程的彈性效果怎麼實現?網上都有很多彈性公式 (5)什麼時候呼叫上拉或下拉回調?當上拉或下拉到一定距離手指離開開始呼叫 下面看一下具體程式碼怎麼實現的。
/**
 * 彈性ListView,實現了上拉和下拉功能
 * @author newhope1106 2016-11-02
 */
public class FlexibleListView extends ListView implements OnTouchListener{
    /**初始可拉動Y軸方向距離*/
    private static final int MAX_Y_OVER_SCROLL_DISTANCE = 100;

    private Context mContext;

    /**實際可上下拉動Y軸上的距離*/
    private int mMaxYOverScrollDistance;

    private float mStartY = -1;
    /**開始計算的時候,第一個或者最後一個item是否可見的*/
    private boolean mCalcOnItemVisible = false;
    /**是否開始計算*/
    private boolean mStartCalc = false;

    /**使用者自定義的OnTouchListener類*/
    private OnTouchListener mTouchListener;

    /**上拉和下拉監聽事件*/
    private OnPullListener mPullListener;

    private int mScrollY = 0;
    private int mLastMotionY = 0;
    private int mDeltaY = 0;
    /**是否在進行動畫*/
    private boolean mIsAnimationRunning = false;
    /**手指是否離開螢幕*/
    private boolean mIsActionUp = false;

    public FlexibleListView(Context context){
        super(context);
        mContext = context;
        super.setOnTouchListener(this);
        initBounceListView();
    }

    public FlexibleListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        super.setOnTouchListener(this);
        initBounceListView();
    }

    public FlexibleListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
        initBounceListView();
    }

    private void initBounceListView(){
        final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
        final float density = metrics.density;
        mMaxYOverScrollDistance = (int) (density * MAX_Y_OVER_SCROLL_DISTANCE);
    }

    /**
     * 覆蓋父類的方法,設定OnTouchListener監聽物件
     * @param listener 使用者自定義的OnTouchListener監聽物件
     * */
    public void setOnTouchListener(OnTouchListener listener) {
        mTouchListener = listener;
    }

    /**
     * 設定上拉和下拉監聽物件
     * @param listener 上拉和下拉監聽物件
     * */
    public void setOnPullListener(OnPullListener listener){
        mPullListener = listener;
    }

    public void scrollTo(int x, int y) {
        super.scrollTo(x, y);

        mScrollY = y;
    }

    /**
     * 在滑動的過程中onTouch的ACTION_DOWN事件可能丟失,在這裡進行初始值設定
     * */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mIsActionUp = false;
                resetStatus();
                if(getFirstVisiblePosition() == 0 || (getLastVisiblePosition() == getAdapter().getCount()-1)) {
                    mStartY = event.getY();
                    mStartCalc = true;
                    mCalcOnItemVisible = true;
                }else{
                    mStartCalc = false;
                    mCalcOnItemVisible = false;
                }

                mLastMotionY = (int)event.getY();
                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        /*使用者自定義的觸控監聽物件消費了事件,則不執行下面的上拉和下拉功能*/
        if(mTouchListener!=null && mTouchListener.onTouch(v, event)) {
            return true;
        }

        /*在做動畫的時候禁止滑動列表*/
        if(mIsAnimationRunning) {
            return true;//需要消費掉事件,否者會出現連續很快下拉或上拉無法回到初始位置的情況
        }

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:{
                mIsActionUp = false;
                resetStatus();
                if(getFirstVisiblePosition() == 0 || (getLastVisiblePosition() == getAdapter().getCount()-1)) {
                    mStartY = event.getY();
                    mStartCalc = true;
                    mCalcOnItemVisible = true;
                }else{
                    mStartCalc = false;
                    mCalcOnItemVisible = false;
                }

                mLastMotionY = (int)event.getY();
            }
            case MotionEvent.ACTION_MOVE:{
                if(!mStartCalc && (getFirstVisiblePosition() == 0|| (getLastVisiblePosition() == getAdapter().getCount()-1))) {
                    mStartCalc = true;
                    mCalcOnItemVisible = false;
                    mStartY = event.getY();
                }

                final int y = (int) event.getY();
                mDeltaY = mLastMotionY - y;
                mLastMotionY = y;

                if(Math.abs(mScrollY) >= mMaxYOverScrollDistance) {
                    if(mDeltaY * mScrollY > 0) {
                        mDeltaY = 0;
                    }
                }

                break;
            }
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:{
                mIsActionUp = true;
                float distance = event.getY() - mStartY;
                checkIfNeedRefresh(distance);

                startBoundAnimate();
            }
        }

        return false;
    }

    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX,
                                  boolean clampedY) {
        if(mDeltaY == 0 || mIsActionUp) {
            return;
        }
        scrollBy(0, mDeltaY/2);
    }
    /**彈性動畫*/
    private void startBoundAnimate() {
        mIsAnimationRunning = true;
        final int scrollY = mScrollY;
        int time = Math.abs(500*scrollY/mMaxYOverScrollDistance);
        ValueAnimator animator = ValueAnimator.ofInt(0,1).setDuration(time);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                float fraction = animator.getAnimatedFraction();
                scrollTo(0, scrollY - (int) (scrollY * fraction));

                if((int)fraction == 1) {
                    scrollTo(0, 0);
                    resetStatus();
                    animator.removeUpdateListener(this);
                }
            }
        });
        animator.start();
    }

    private void resetStatus() {
        mIsAnimationRunning = false;
        mStartCalc = false;
        mCalcOnItemVisible = false;
    }

    /**
     * 根據滑動的距離判斷是否需要回調上拉或者下拉事件
     * @param distance 滑動的距離
     * */
    private void checkIfNeedRefresh(float distance) {
        if(distance > 0 && getFirstVisiblePosition() == 0) { //下拉
            View view = getChildAt(0);
            if(view == null) {
                return;
            }

            float realDistance = distance;
            if(!mCalcOnItemVisible) {
                realDistance = realDistance - view.getHeight();//第一個item的高度不計算在內容
            }
            if(realDistance > mMaxYOverScrollDistance) {
                if(mPullListener != null){
                    mPullListener.onPullDown();
                }
            }
        } else if(distance < 0 && getLastVisiblePosition() == getAdapter().getCount()-1) {//上拉
            View view = getChildAt(getChildCount()-1);
            if(view == null) {
                return;
            }

            float realDistance = -distance;
            if(!mCalcOnItemVisible) {
                realDistance = realDistance - view.getHeight();//最後一個item的高度不計算在內容
            }
            if(realDistance > mMaxYOverScrollDistance) {
                if(mPullListener != null){
                    mPullListener.onPullUp();
                }
            }
        }
    }

    public interface OnPullListener{
        /**
         * 下拉
         * */
        void onPullDown();
        /**
         * 上拉
         * */
        void onPullUp();
    }
}
程式碼不長,只有200多行,比較簡單,也不涉及資源問題。