Android launcher3 循环桌面

最近接了个需求,要在现有的桌面上加上循环桌面的功能,刚开始也是一头雾水,所以静下心来把PagedView.java 绘制看了一遍,然后又找一些资料;主要有以下几个修改点:
以下以android 7.1.1 的 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那点事】

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值