app直播原始碼,列表越界後自動彈回原樣的效果

zhibo系統開發發表於2022-01-11

app直播原始碼,列表越界後自動彈回原樣的效果實現的相關程式碼

一、越界回彈效果的例子

二、效果拆分

下拉、上拉越界回彈:已到達列表頂部,執行下拉操作,此時會進行越界回彈

慣性滑動 越界回彈:快速滑動列表,列表已到達底部,但由於慣性會繼續前進一部分,此時會根據速度決定越界的距離以及回彈的速度

三、下拉、上拉越界回彈實現方式及程式碼

在實際專案中可能在多個頁面都需要回彈的效果,所以需要儘可能的減少與需要回彈view的耦合程式碼。所以可以自定義一個ViewGroup來包裹需要回彈的view。

public class PullOverLayout extends RelativeLayout {
    //允許的越界回彈的高度
    protected float mOverScrollHeight;
    //被包裹的子View
    private View mChildView;
    //~~~~~
    public PullOverLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mOverScrollHeight = DensityUtil.dp2px(context, 240);
    }
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        //獲得子控制元件,也就是被包裹的recycleview等
        mChildView = getChildAt(0);
    }
}


重寫事件攔截方法onInterceptTouchEvent()

通過豎直和水平方向Move的距離來判斷是否攔截該事件

dy大於0為手指向下滑動,dy小於0為手指向上滑動,通過setStatePTD、setStatePBU方法記錄Down或者Up

當被包裹的子view無法滑動時即發生越界時攔截並響應該Move事件

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mTouchX = ev.getX();
            mTouchY = ev.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            float dx = ev.getX() - mTouchX;
            float dy = ev.getY() - mTouchY;
            if (Math.abs(dx) <= Math.abs(dy)) {
                if (dy > 0 && !ScrollingUtil.canChildScrollUp(mChildView)) {
                    //1、下拉操作
                    LogUtil.d(TAG, "setStatePTD");
                    cp.setStatePTD();
                    return true;
                } else if (dy < 0 && !ScrollingUtil.canChildScrollDown(cp.getContent())) {
                    //2、上拉操作
                    LogUtil.d(TAG, "setStatePBU");
                    cp.setStatePBU();
                    return true;
                }
            }
            break;
    }
    return super.onInterceptTouchEvent(ev);
}


重寫onTouchEvent()方法響應攔截的事件

onTouchEvent用來響應事件,當move和up時,做相應的“越界”操作和“回彈”操作

下拉和上拉操作分別執行對應的越界和回彈操作即可

@Override
public boolean onTouchEvent(MotionEvent e) {
    switch (e.getAction()) {
        //當發生越界時由viewGroup響應Move事件
        case MotionEvent.ACTION_MOVE:
            float dy = e.getY() - mTouchY;
            if (cp.isStatePTD()) {
                //1、如果是下拉操作的move事件,則執行頂部越界操作
                dy = Math.min(cp.getOsHeight() * 2, dy);
                dy = Math.max(0, dy);
                cp.getAnimProcessor().scrollHeadByMove(dy);
            } else if (cp.isStatePBU()) {
                //2、如果是上拉操作的move事件,則執行底部越界的操作
                dy = Math.min(cp.getOsHeight() * 2, Math.abs(dy));
                dy = Math.max(0, dy);
                cp.getAnimProcessor().scrollBottomByMove(dy);
            }
            return true;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            if (cp.isStatePTD()) {
                //3、如果是下拉操作,當收到Up事件時,進行釋放操作下拉操作,即執行下拉回彈操作
                cp.getAnimProcessor().dealPullDownRelease();
            } else if (cp.isStatePBU()) {
                //4、如果是上拉操作,當收到up事件時,進行釋放上拉操作,執行上拉回彈操作
                cp.getAnimProcessor().dealPullUpRelease();
            }
            return true;
    }
    return super.onTouchEvent(e);
}


第三步onTouchEvent中的下拉越界方法scrollHeadByMove(float moveY)和下拉釋放的回彈方法dealPullDownRelease()

public void scrollHeadByMove(float moveY) {
    float offsetY = decelerateInterpolator.getInterpolation(moveY / cp.getOsHeight() / 2) * moveY / 2;
    LogUtil.d(TAG, "scrollHeadByMove ", "offsetY:", String.valueOf(offsetY));
    cp.getHeader().setVisibility(GONE);
    cp.getHeader().getLayoutParams().height = (int) Math.abs(offsetY);
    cp.getHeader().requestLayout();
    cp.getContent().setTranslationY(offsetY);
}
public void dealPullDownRelease() {
    int start = getVisibleHeadHeight();
    int end = 0;
    LogUtil.d(TAG, "dealPullDownRelease  ", "start:", String.valueOf(start), "end:", String.valueOf(end));
    ValueAnimator va = ValueAnimator.ofInt(start, end);
    va.setInterpolator(new DecelerateInterpolator());
    va.addUpdateListener(animHeadUpListener);
    va.setDuration((int) (Math.abs(start - end) * animFraction));
    va.start();
}



四、慣性滑動 越界回彈實現方式及程式碼

給被包裹的子view設定監聽

給定一個最低速度的閾值,當滑動速度超過這個值時根據慣性執行越界回彈操作

下方程式碼只實現了recycleview的越界回彈,listview類似。即被包裹的子view必須是recycleview

public void initChildViewFlingListener() {
    final View mChildView = cp.getContent();
    //1、給被包裹的子view設定監聽事件,監聽fling操作,並記錄速度
    final GestureDetector gestureDetector = new GestureDetector(cp.getContext(), new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            LogUtil.d(TAG, "onScroll  ", "distanceY:", String.valueOf(distanceY));
            return super.onScroll(e1, e2, distanceX, distanceY);
        }
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            LogUtil.d(TAG, "onFling  ", "velocityY:", String.valueOf(velocityY));
            mVelocityY = velocityY;
            return false;
        }
    });
    mChildView.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            //手勢監聽的兩個任務:1.監聽fling動作,獲取速度  2.監聽滾動狀態變化
            return gestureDetector.onTouchEvent(event);
        }
    });
    if (mChildView instanceof RecyclerView) {
        ((RecyclerView) mChildView).addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    //2、到達越界速度閾值後,執行fling的頂部越界操作
                    if (mVelocityY >= OVER_SCROLL_MIN_VX && ScrollingUtil.isRecyclerViewToTop((RecyclerView) mChildView)) {
                        cp.getAnimProcessor().animOverScrollTop(mVelocityY, cur_delay_times);
                        mVelocityY = 0;
                        cur_delay_times = ALL_DELAY_TIMES;
                    }
                    if (mVelocityY <= -OVER_SCROLL_MIN_VX && ScrollingUtil.isRecyclerViewToBottom((RecyclerView) mChildView)) {
                        cp.getAnimProcessor().animOverScrollBottom(mVelocityY, cur_delay_times);
                        mVelocityY = 0;
                        cur_delay_times = ALL_DELAY_TIMES;
                    }
                }
                super.onScrollStateChanged(recyclerView, newState);
            }
        });
    }
}


執行越界動畫

底部越界和頂部越界程式碼類似,此處只講解頂部越界即可

/**
 * 執行頂部越界
 * 越界高度height ∝ vy/computeTimes,此處採用的模型是height=A*(vy + B)/computeTimes
 */
public void animOverScrollTop(float vy, int computeTimes) {
    LogUtil.d(TAG, "animOverScrollTop ", "vy:", String.valueOf(vy), "computeTimes:", String.valueOf(computeTimes));
    if (cp.isOsTopLocked()) return;
    cp.lockOsTop();
    cp.setStatePTD();
    //1、計算越界高度,最大不超過設定的越界高度
    int oh = (int) Math.abs(vy / computeTimes / 2);
    final int overHeight = Math.min(oh, cp.getOsHeight());
    //2、計算越界時間
    final int time = overHeight <= 50 ? 115 : (int) (0.3 * overHeight + 100);
    //3、執行越界操作
    animLayoutByTime(0, overHeight, time, overScrollTopUpListener, new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            //4、執行回彈操作
            animLayoutByTime(overHeight, 0, 2 * time, overScrollTopUpListener, new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    cp.releaseOsTopLock();
                }
            });
        }
    });
}
//最終執行的就是個屬性動畫
public void animLayoutByTime(int start, int end, long time, AnimatorUpdateListener listener, AnimatorListener animatorListener) {
    ValueAnimator va = ValueAnimator.ofInt(start, end);
    va.setInterpolator(new DecelerateInterpolator());
    va.addUpdateListener(listener);
    va.addListener(animatorListener);
    va.setDuration(time);
    va.start();
}


以上就是 app直播原始碼,列表越界後自動彈回原樣的效果實現的相關程式碼,更多內容歡迎關注之後的文章


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69978258/viewspace-2851690/,如需轉載,請註明出處,否則將追究法律責任。

相關文章