Android launcher3 循环桌面
程序员文章站
2022-06-30 16:46:08
...
最近接了个需求,要在现有的桌面上加上循环桌面的功能,刚开始也是一头雾水,所以静下心来把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那点事】