欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android launcher3 循环桌面

程序员文章站 2022-06-30 16:46:08
...

最近接了个需求,要在现有的桌面上加上循环桌面的功能,刚开始也是一头雾水,所以静下心来把PagedView.java 绘制看了一遍,然后又找一些资料;主要有以下几个修改点:
以下以android 7.1.1 的 launcher3代码为例 其它版本都一样
先看下效果:
Android launcher3 循环桌面

1、scrollTo方法 里面对滑动超过当前页数的前一页和后一页做了处理(绘制了超出效果)

 @Override
    public void scrollTo(int x, int y) {
        // In free scroll mode, we clamp the scrollX
        if (mFreeScroll) {
            // If the scroller is trying to move to a location beyond the maximum allowed
            // in the free scroll mode, we make sure to end the scroll operation.
            if (!mScroller.isFinished() &&
                    (x > mFreeScrollMaxScrollX || x < mFreeScrollMinScrollX)) {
                forceFinishScroller(false);
            }

            x = Math.min(x, mFreeScrollMaxScrollX);
            x = Math.max(x, mFreeScrollMinScrollX);
        }

        boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0);
        boolean isXAfterLastPage = mIsRtl ? (x < 0) : (x > mMaxScrollX);
        if (isXBeforeFirstPage) {
            super.scrollTo(mIsRtl ? mMaxScrollX : 0, y);
            if (mAllowOverScroll) {
                mWasInOverscroll = true;
                if (mIsRtl) {
                    overScroll(x - mMaxScrollX);
                } else {
                    overScroll(x);
                }
            }
        } else if (isXAfterLastPage) {
            super.scrollTo(mIsRtl ? 0 : mMaxScrollX, y);
            if (mAllowOverScroll) {
                mWasInOverscroll = true;
                if (mIsRtl) {
                    overScroll(x);
                } else {
                    overScroll(x - mMaxScrollX);
                }
            }
        } else {
            if (mWasInOverscroll) {
                overScroll(0);
                mWasInOverscroll = false;
            }
            super.scrollTo(x, y);
        }

        // Update the last motion events when scrolling
        if (isReordering(true)) {
            float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
            mLastMotionX = p[0];
            mLastMotionY = p[1];
            updateDragViewTranslationDuringDrag();
        }
    }

要循环就不能要边缘效果和限制滑动超过,改为:

@Override
    public void scrollTo(int x, int y) {
        // In free scroll mode, we clamp the scrollX
        if (mFreeScroll) {
            // If the scroller is trying to move to a location beyond the
            // maximum allowed
            // in the free scroll mode, we make sure to end the scroll
            // operation.
            if (!mScroller.isFinished() && (x > mFreeScrollMaxScrollX || x < mFreeScrollMinScrollX)) {
                forceFinishScroller(false);
            }

            x = Math.min(x, mFreeScrollMaxScrollX);
            x = Math.max(x, mFreeScrollMinScrollX);
        }

        if (mIsCycleSlide) {
            mOverScrollX = x;
            super.scrollTo(x, y);
        } else {

            boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0);
            boolean isXAfterLastPage = mIsRtl ? (x < 0) : (x > mMaxScrollX);
            if (isXBeforeFirstPage) {
                super.scrollTo(mIsRtl ? mMaxScrollX : 0, y);
                if (mAllowOverScroll) {
                    mWasInOverscroll = true;
                    if (mIsRtl) {
                        overScroll(x - mMaxScrollX);
                    } else {
                        overScroll(x);
                    }
                }
            } else if (isXAfterLastPage) {
                super.scrollTo(mIsRtl ? 0 : mMaxScrollX, y);
                if (mAllowOverScroll) {
                    mWasInOverscroll = true;
                    if (mIsRtl) {
                        overScroll(x);
                    } else {
                        overScroll(x - mMaxScrollX);
                    }
                }
            } else {
                if (mWasInOverscroll) {
                    overScroll(0);
                    mWasInOverscroll = false;
                }
                super.scrollTo(x, y);
            }
        }
        // Update the last motion events when scrolling
        if (isReordering(true)) {
            float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
            mLastMotionX = p[0];
            mLastMotionY = p[1];
            updateDragViewTranslationDuringDrag();
        }
    }

2、onTouchEvent事件处理

在手指抬起时修改不要对最后一页或第一次时 往后或 往前滑动处理原生代码:

case MotionEvent.ACTION_UP:
            if (mTouchState == TOUCH_STATE_SCROLLING) {
                final int activePointerId = mActivePointerId;
                final int pointerIndex = ev.findPointerIndex(activePointerId);
                final float x = ev.getX(pointerIndex);
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
                final int deltaX = (int) (x - mDownMotionX);
                final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
                boolean isSignificantMove = Math.abs(deltaX) > pageWidth * SIGNIFICANT_MOVE_THRESHOLD;

                mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);

                boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING && Math.abs(velocityX) > mFlingThresholdVelocity;

                if (!mFreeScroll) {
                    // In the case that the page is moved far to one direction
                    // and then is flung
                    // in the opposite direction, we use a threshold to
                    // determine whether we should
                    // just return to the starting page, or if we should skip
                    // one further.
                    boolean returnToOriginalPage = false;
                    if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD
                            && Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
                        returnToOriginalPage = true;
                    }

                    int finalPage;
                    // We give flings precedence over large moves, which is why
                    // we short-circuit our
                    // test for a large move if a fling has been registered.
                    // That is, a large
                    // move to the left and fling to the right will register as
                    // a fling to the right.
                    boolean isDeltaXLeft = mIsRtl ? deltaX > 0 : deltaX < 0;
                    boolean isVelocityXLeft = mIsRtl ? velocityX > 0 : velocityX < 0;
                    if (((isSignificantMove && !isDeltaXLeft && !isFling) || (isFling && !isVelocityXLeft))
                            && mCurrentPage > 0) {
                        finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
                        snapToPageWithVelocity(finalPage, velocityX);
                    } else if (((isSignificantMove && isDeltaXLeft && !isFling) || (isFling && isVelocityXLeft))
                            && mCurrentPage < getChildCount() - 1) {
                        finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
                        snapToPageWithVelocity(finalPage, velocityX);
                    } else {
                        snapToDestination();
                    }
                } else {
                    if (!mScroller.isFinished()) {
                        abortScrollerAnimation(true);
                    }

                    float scaleX = getScaleX();
                    int vX = (int) (-velocityX * scaleX);
                    int initialScrollX = (int) (getScrollX() * scaleX);

                    mScroller.setInterpolator(mDefaultInterpolator);
                    mScroller.fling(initialScrollX, getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
                    mNextPage = getPageNearestToCenterOfScreen((int) (mScroller.getFinalX() / scaleX));
                    invalidate();
                }
                onScrollInteractionEnd();
            } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
                // at this point we have not moved beyond the touch slop
                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
                // we can just page
                int nextPage = Math.max(0, mCurrentPage - 1);
                if (nextPage != mCurrentPage) {
                    snapToPage(nextPage);
                } else {
                    snapToDestination();
                }
            } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
                // at this point we have not moved beyond the touch slop
                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
                // we can just page
                int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
                if (nextPage != mCurrentPage) {
                    snapToPage(nextPage);
                } else {
                    snapToDestination();
                }
            } else if (mTouchState == TOUCH_STATE_REORDERING) {
                // Update the last motion position
                mLastMotionX = ev.getX();
                mLastMotionY = ev.getY();

                // Update the parent down so that our zoom animations take this
                // new movement into
                // account
                float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
                mParentDownMotionX = pt[0];
                mParentDownMotionY = pt[1];
                updateDragViewTranslationDuringDrag();
            } else {
                if (!mCancelTap) {
                    onUnhandledTap(ev);
                }
            }

            // Remove the callback to wait for the side page hover timeout
            removeCallbacks(mSidePageHoverRunnable);
            // End any intermediate reordering states
            resetTouchState();
            break;

修改原生判断条件

case MotionEvent.ACTION_UP:
            if (mTouchState == TOUCH_STATE_SCROLLING) {
                final int activePointerId = mActivePointerId;
                final int pointerIndex = ev.findPointerIndex(activePointerId);
                final float x = ev.getX(pointerIndex);
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
                final int deltaX = (int) (x - mDownMotionX);
                final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
                boolean isSignificantMove = Math.abs(deltaX) > pageWidth * SIGNIFICANT_MOVE_THRESHOLD;

                mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);

                boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING && Math.abs(velocityX) > mFlingThresholdVelocity;

                if (!mFreeScroll) {
                    // In the case that the page is moved far to one direction
                    // and then is flung
                    // in the opposite direction, we use a threshold to
                    // determine whether we should
                    // just return to the starting page, or if we should skip
                    // one further.
                    boolean returnToOriginalPage = false;
                    if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD
                            && Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
                        returnToOriginalPage = true;
                    }

                    int finalPage;
                    // We give flings precedence over large moves, which is why
                    // we short-circuit our
                    // test for a large move if a fling has been registered.
                    // That is, a large
                    // move to the left and fling to the right will register as
                    // a fling to the right.
                    boolean isDeltaXLeft = mIsRtl ? deltaX > 0 : deltaX < 0;
                    boolean isVelocityXLeft = mIsRtl ? velocityX > 0 : velocityX < 0;
                    if (((isSignificantMove && !isDeltaXLeft && !isFling) || (isFling && !isVelocityXLeft))
                            && (mIsCycleSlide? (mCurrentPage >= 0) : (mCurrentPage > 0))) {
                        finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
                        /**add by lmjsjj begin*/
                        if(finalPage==-1){
                            finalPage=-2;
                        }
                        /**add by lmjsjj end*/
                        snapToPageWithVelocity(finalPage, velocityX);
                    } else if (((isSignificantMove && isDeltaXLeft && !isFling) || (isFling && isVelocityXLeft))
                            && (mIsCycleSlide ? (mCurrentPage < getChildCount()) : (mCurrentPage < getChildCount() - 1))) {
                        finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
                        snapToPageWithVelocity(finalPage, velocityX);
                    } else {
                        snapToDestination();
                    }
                } else {
                    if (!mScroller.isFinished()) {
                        abortScrollerAnimation(true);
                    }

                    float scaleX = getScaleX();
                    int vX = (int) (-velocityX * scaleX);
                    int initialScrollX = (int) (getScrollX() * scaleX);

                    mScroller.setInterpolator(mDefaultInterpolator);
                    mScroller.fling(initialScrollX, getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
                    mNextPage = getPageNearestToCenterOfScreen((int) (mScroller.getFinalX() / scaleX));
                    invalidate();
                }
                onScrollInteractionEnd();
            } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
                // at this point we have not moved beyond the touch slop
                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
                // we can just page
                int nextPage = Math.max(0, mCurrentPage - 1);
                if (nextPage != mCurrentPage) {
                    snapToPage(nextPage);
                } else {
                    snapToDestination();
                }
            } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
                // at this point we have not moved beyond the touch slop
                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
                // we can just page
                int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
                if (nextPage != mCurrentPage) {
                    snapToPage(nextPage);
                } else {
                    snapToDestination();
                }
            } else if (mTouchState == TOUCH_STATE_REORDERING) {
                // Update the last motion position
                mLastMotionX = ev.getX();
                mLastMotionY = ev.getY();

                // Update the parent down so that our zoom animations take this
                // new movement into
                // account
                float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
                mParentDownMotionX = pt[0];
                mParentDownMotionY = pt[1];
                updateDragViewTranslationDuringDrag();
            } else {
                if (!mCancelTap) {
                    onUnhandledTap(ev);
                }
            }

            // Remove the callback to wait for the side page hover timeout
            removeCallbacks(mSidePageHoverRunnable);
            // End any intermediate reordering states
            resetTouchState();
            break;

3、修改dispatchDraw方法

在dispatchDraw后面添加如下代码
当从最后一页再向后滑动时是第一屏 当第一屏滑动时是最后一屏是通过平移canvas来实现


        boolean isXBeforeFirstPage = mIsRtl ? (mOverScrollX > mMaxScrollX) : (mOverScrollX < 0);
        boolean isXAfterLastPage = mIsRtl ? (mOverScrollX < 0) : (mOverScrollX > mMaxScrollX);
        if (isXBeforeFirstPage || isXAfterLastPage) {
            long drawingTime = getDrawingTime();
            int width = mViewport.width();
            int childCount = getChildCount();
            canvas.save();
            canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
                    getScrollY() + getBottom() - getTop());
            // here we assume that a page's horizontal padding plus it's measured width
            // equals to ViewPort's width
            int offset = (mIsRtl ? - childCount : childCount) * (width + mPageSpacing);
            if (isXBeforeFirstPage) {
                canvas.translate(- offset, 0);
                drawChild(canvas, getPageAt(childCount - 1), drawingTime);
                canvas.translate(+ offset, 0);
            } else if (isXAfterLastPage) {
                canvas.translate(+ offset, 0);
                drawChild(canvas, getPageAt(0), drawingTime);
                canvas.translate(- offset, 0);
            }

            canvas.restore();
        }

复位滚动

    protected boolean computeScrollHelper(boolean shouldInvalidate) {
        if (mScroller.computeScrollOffset()) {
            // Don't bother scrolling if the page does not need to be moved
            if (getScrollX() != mScroller.getCurrX() || getScrollY() != mScroller.getCurrY()) {
                float scaleX = mFreeScroll ? getScaleX() : 1f;
                int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
                scrollTo(scrollX, mScroller.getCurrY());
            }
            if (shouldInvalidate) {
                invalidate();
            }
            return true;
        } else if (mNextPage != INVALID_PAGE && shouldInvalidate) {
            sendScrollAccessibilityEvent();

             if (!mIsCycleSlide || (mNextPage != -2 && mNextPage != getPageCount())
                        || (mPageScrolls == null)) {
                    mCurrentPage = validateNewPage(mNextPage);
                } else {
                    if (mNextPage == -2) {
                        mCurrentPage = getPageCount() - 1;
                        scrollTo(mPageScrolls[mCurrentPage], getScrollY());
                    } else if (mNextPage == getPageCount()) {
                        mCurrentPage = 0;
                        scrollTo(mPageScrolls[mCurrentPage], getScrollY());
                    }
                }

            //mCurrentPage = validateNewPage(mNextPage);
            mNextPage = INVALID_PAGE;
            notifyPageSwitchListener();

            // We don't want to trigger a page end moving unless the page has
            // settled
            // and the user has stopped scrolling
            if (mTouchState == TOUCH_STATE_REST) {
                pageEndMoving();
            }

            onPostReorderingAnimationCompleted();
            AccessibilityManager am = (AccessibilityManager) getContext()
                    .getSystemService(Context.ACCESSIBILITY_SERVICE);
            if (am.isEnabled()) {
                // Notify the user when the page changes
                announceForAccessibility(getCurrentPageDescription());
            }
            return true;
        }
        return false;
    }

存储前一屏和后一屏位置

private int[] mPageScrollsForCircleSlide = new int[] { 0, 0 };
在onlayout中
mPageScrollsForCircleSlide[0] = -(mPageSpacing + mViewport.width());
 mPageScrollsForCircleSlide[1] = (mViewport.width() + mPageSpacing) * childCount;
public int getScrollForPage(int index) {

        if (mIsCycleSlide) {
            if (index == -2) {
                return mPageScrollsForCircleSlide[0];
            } else if (index == getChildCount()){
                return mPageScrollsForCircleSlide[1];
            }
        }


        if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
            return 0;
        } else {
            return mPageScrolls[index];
        }
    }

在snapToPage 和 snapToPageWithVelocity中 修改

if(mIsCycleSlide){
            whichPage = validateNewPageWithRcy(whichPage);
        }else{
            whichPage = validateNewPage(whichPage);
        }
 protected int validateNewPageWithRcy(int newPage) {
            int validatedPage = newPage;
            if (mIsCycleSlide) {
                // whichPage can be OVER_FIRST_PAGE_INDEX or [0, count]
                if (!(validatedPage == -2)) {
                    validatedPage = Math.max(0, Math.min(validatedPage, getPageCount()));
                }
            } else {
                validatedPage = validateNewPage(newPage);
            }
            return validatedPage;
        }
    public int getNextPage() {
        int page = mNextPage;
        if (mNextPage == getChildCount()) {
            page = 0;
        } else if (mNextPage == FIRST_CYCLE_PAGE_INDEX) {
            page = getChildCount() - 1;
        }
        return (mNextPage != INVALID_PAGE) ? page : mCurrentPage;
    }

主要以前三点修改点 还有一些细节不罗列.

交流QQ群:196040873
点击链接加入群【Android那点事】