android13#launcher3#workspace布局

78 阅读9分钟

1.简介

  • 平板模式使用的taskbar,我们改成了navBar,并且隐藏了hotseat,那么一些间距啥的需要调整
  • 所以学下workspace的布局参数,布局在父类8.2

2.Workspace

2.1.容器结构

public class Workspace<T extends View & PageIndicator> extends PagedView<T>

PagedView,自定义的容器,参考小节8

public abstract class PagedView<T extends View & PageIndicator> extends ViewGroup {

2.2.bindAndInitFirstWorkspaceScreen

  • launcher.java启动的时候会调用这个方法
    public void bindAndInitFirstWorkspaceScreen() {
        if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
        //第一页不需要搜索框,那么不用初始化第一页
            return;
        }

        //添加第一个页面,补充1
        CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
        //这个是搜索框
        if (mFirstPagePinnedItem == null) {
            mFirstPagePinnedItem = LayoutInflater.from(getContext())
                    .inflate(R.layout.search_container_workspace, firstPage, false);
        }

    //搜索框的跨度
        int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
        CellLayoutLayoutParams lp = new CellLayoutLayoutParams(0, 0, cellHSpan, 1);
        lp.canReorder = false;
        //把搜索框添加到CellLayout里,参考3.2
        if (!firstPage.addViewToCellLayout(
                mFirstPagePinnedItem, 0, R.id.search_container_workspace, lp, true)) {
            mFirstPagePinnedItem = null;
        }
    }

>1.insertNewWorkspaceScreen

    public CellLayout insertNewWorkspaceScreen(int screenId, int insertIndex) {

        DeviceProfile dp = mLauncher.getDeviceProfile();
        CellLayout newScreen;
        if (FOLDABLE_SINGLE_PAGE.get() && dp.isTwoPanels) {
            newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                    R.layout.workspace_screen_foldable, this, false /* attachToRoot */);
        } else {
            newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                    R.layout.workspace_screen, this, false /* attachToRoot */);
        }
//放入map
        mWorkspaceScreens.put(screenId, newScreen);
        mScreenOrder.add(insertIndex, screenId);
        //添加到容器里,也就是workspace里
        addView(newScreen, insertIndex);
        mStateTransitionAnimation.applyChildState(
                mLauncher.getStateManager().getState(), newScreen, insertIndex);

        updatePageScrollValues();
        //补充2
        updateCellLayoutPadding();
        return newScreen;
    }

>2.updateCellLayoutPadding

给所有的CellLayout设置padding

    private void updateCellLayoutPadding() {
    //数据参考4.1
        Rect padding = mLauncher.getDeviceProfile().cellLayoutPaddingPx;
        mWorkspaceScreens.forEach(
                s -> s.setPadding(padding.left, padding.top, padding.right, padding.bottom));
    }

2.3.insertNewWorkspaceScreenBeforeEmptyScreen

添加新的页面的时候走这里

    public void insertNewWorkspaceScreenBeforeEmptyScreen(int screenId) {
        //先获取要添加的索引
        int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
        if (insertIndex < 0) {
            insertIndex = mScreenOrder.size();
        }
        //参考2.2.1
        insertNewWorkspaceScreen(screenId, insertIndex);
    }

2.4.setInsets

  • edgeMarginPx的值参考4.1.1,读取的是配置里的值
    public void setInsets(Rect insets) {
        DeviceProfile grid = mLauncher.getDeviceProfile();

        mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();

        Rect padding = grid.workspacePadding;//数据参考4.1.2
        setPadding(padding.left, padding.top, padding.right, padding.bottom);
        mInsets.set(insets);

        if (mWorkspaceFadeInAdjacentScreens) {
            //横屏手机模式下(就是导航栏在两侧的),页面间距设置为默认值
            setPageSpacing(grid.edgeMarginPx);//参考8.5
        } else {
            //insets的值参考4.1.2
            int maxInsets = Math.max(insets.left, insets.right);
            int maxPadding = Math.max(grid.edgeMarginPx, padding.left + 1);
            setPageSpacing(Math.max(maxInsets, maxPadding));
        }

        updateCellLayoutPadding();
        updateWorkspaceWidgetsSizes();
        setPageIndicatorInset();
    }

2.5.onLayout

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (mUnlockWallpaperFromDefaultPageOnLayout) {
            mWallpaperOffset.setLockToDefaultPage(false);
            mUnlockWallpaperFromDefaultPageOnLayout = false;
        }
        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
            mWallpaperOffset.syncWithScroll();
            mWallpaperOffset.jumpToFinal();
        }
        super.onLayout(changed, left, top, right, bottom);
        updatePageAlphaValues();//补充1
    }

>1.updatePageAlphaValues

更新各个页面的透明度

    private void updatePageAlphaValues() {
        //
        if (!workspaceInModalState() && !mIsSwitchingState && !mDragController.isDragging()) {
        //已滚动的距离加上屏幕宽度的一半
            int screenCenter = getScrollX() + getMeasuredWidth() / 2;
            for (int i = 0; i < getChildCount(); i++) {
                CellLayout child = (CellLayout) getChildAt(i);
                if (child != null) {
                    float scrollProgress = getScrollProgress(screenCenter, child, i);
                    float alpha = 1 - Math.abs(scrollProgress);
                    if (mWorkspaceFadeInAdjacentScreens) {
                        child.getShortcutsAndWidgets().setAlpha(alpha);
                    } else {
                        // Pages that are off-screen aren't important for accessibility.
                        child.getShortcutsAndWidgets().setImportantForAccessibility(
                                alpha > 0 ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
                                        : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
                    }
                }
            }
        }
    }

3.CellLayout

public class CellLayout extends ViewGroup {

3.1.构造方法

默认添加了一个容器ShortcutAndWidgetContainer,最终所有的图标都是添加到这个容器里的,参考3.2

//..
        mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
                mBorderSpace);
        addView(mShortcutsAndWidgets);
    }

3.2.addViewToCellLayout

    public boolean addViewToCellLayout(View child, int index, int childId,
            CellLayoutLayoutParams params, boolean markCells) {
        final CellLayoutLayoutParams lp = params;

        // Hotseat icons - remove text
        if (child instanceof BubbleTextView) {
            BubbleTextView bubbleChild = (BubbleTextView) child;
            bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
        }

        child.setScaleX(mChildScale);
        child.setScaleY(mChildScale);


        if (lp.getCellX() >= 0 && lp.getCellX() <= mCountX - 1
                && lp.getCellY() >= 0 && lp.getCellY() <= mCountY - 1) {
            if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
            if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;

            child.setId(childId);
//可以看到,child都是添加到ShortcutAndWidgetContainer里的
            mShortcutsAndWidgets.addView(child, index, lp);

            if (markCells) markCellsAsOccupiedForView(child);

            return true;
        }
        return false;
    }

4.DeviceProfile.java

4.1.cellLayoutPaddingPx

    public Rect cellLayoutPaddingPx = new Rect();

>1.构造方法

默认的padding,读取的是配置文件的值,

        int cellLayoutPadding =
                isTwoPanels ? cellLayoutBorderSpacePx.x / 2 : res.getDimensionPixelSize(
                        R.dimen.cell_layout_padding);
        cellLayoutPaddingPx = new Rect(cellLayoutPadding, cellLayoutPadding, cellLayoutPadding,
                cellLayoutPadding);

//..
        edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);

>2.updateWorkspacePadding

这里修改了cellLayoutPaddingPx的值

    private void updateWorkspacePadding() {
        Rect padding = workspacePadding;
        if (isVerticalBarLayout()) {//横屏,导航栏在两侧的情况
            padding.top = 0;
            padding.bottom = edgeMarginPx;
            if (isSeascape()) {
                padding.left = hotseatBarSizePx;
                padding.right = hotseatBarSidePaddingStartPx;
            } else {
                padding.left = hotseatBarSidePaddingStartPx;
                padding.right = hotseatBarSizePx;
            }
        } else {
            //正常平板,taskbar固定在底部的,会走这里
            int paddingBottom = hotseatBarSizePx + workspaceBottomPadding
                    + workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace
                    - mInsets.bottom;
            int paddingTop = workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx);
            int paddingSide = desiredWorkspaceHorizontalMarginPx;

            padding.set(paddingSide, paddingTop, paddingSide, paddingBottom);
        }
        //这里修改了cellLayoutPaddingPx的值
        insetPadding(workspacePadding, cellLayoutPaddingPx);
    }

//用的是paddings和自己的最小值
    private void insetPadding(Rect paddings, Rect insets) {
        insets.left = Math.min(insets.left, paddings.left);
        paddings.left -= insets.left;

        insets.top = Math.min(insets.top, paddings.top);
        paddings.top -= insets.top;

        insets.right = Math.min(insets.right, paddings.right);
        paddings.right -= insets.right;

        insets.bottom = Math.min(insets.bottom, paddings.bottom);
        paddings.bottom -= insets.bottom;
    }

4.2.mInsets

>1.构造方法

        mInsets.set(windowBounds.insets);

>2.updateInsets

    public void updateInsets(Rect insets) {
        mInsets.set(insets);
    }

4.3.shouldFadeAdjacentWorkspaceScreens

是否应该淡出相邻的工作空间,参考补充代码,横屏手机模式下才为true

    public boolean shouldFadeAdjacentWorkspaceScreens() {
        return isVerticalBarLayout();
    }

>1.isVerticalBarLayout

    public boolean isVerticalBarLayout() {
        return isLandscape && transposeLayoutWithOrientation;
    }

>2.mTransposeLayoutWithOrientation

如果没有设置,那么非平板的时候为true

            if (mTransposeLayoutWithOrientation == null) {
                mTransposeLayoutWithOrientation = !mInfo.isTablet(mWindowBounds);
            }

也可以设置setTransposeLayoutWithOrientation

        public Builder setTransposeLayoutWithOrientation(boolean transposeLayoutWithOrientation) {
            mTransposeLayoutWithOrientation = transposeLayoutWithOrientation;
            return this;
        }

5.LauncherRootView.java

这个是launcher的根容器,workspace,hotseat,allapps,recents等都是在这个容器里的

public class LauncherRootView extends InsettableFrameLayout {

5.1.onApplyWindowInsets

系统方法重写,应用insets

    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        mActivity.handleConfigurationChanged(mActivity.getResources().getConfiguration());
//参考6.1
        insets = WindowManagerProxy.INSTANCE.get(getContext())
                .normalizeWindowInsets(getContext(), insets, mTempRect);
        handleSystemWindowInsets(mTempRect);
        return insets;
    }

>1.handleSystemWindowInsets

    private void handleSystemWindowInsets(Rect insets) {
        // 参考4.2.2
        mActivity.getDeviceProfile().updateInsets(insets);
        boolean resetState = !insets.equals(mInsets);
        setInsets(insets);

        if (resetState) {
            mActivity.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
        }
    }

>2.setInsets

    public void setInsets(Rect insets) {
        //发生变化的时候再处理
        if (!insets.equals(mInsets)) {
            super.setInsets(insets);//参考5.2.1
            mSysUiScrim.onInsetsChanged(insets);
        }
    }

5.2.父类InsettableFrameLayout

public class InsettableFrameLayout extends FrameLayout implements Insettable {

>1.setInsets

循环所有的child,应用insets

    public void setInsets(Rect insets) {
        final int n = getChildCount();
        for (int i = 0; i < n; i++) {
            final View child = getChildAt(i);
            //补充2
            setFrameLayoutChildInsets(child, insets, mInsets);
        }
        mInsets.set(insets);
    }

>2.setFrameLayoutChildInsets

    public void setFrameLayoutChildInsets(View child, Rect newInsets, Rect oldInsets) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        //实现了这个接口的,直接调用对应的方法,比如2.4
        if (child instanceof Insettable) {
            ((Insettable) child).setInsets(newInsets);
        } else if (!lp.ignoreInsets) {
            lp.topMargin += (newInsets.top - oldInsets.top);
            lp.leftMargin += (newInsets.left - oldInsets.left);
            lp.rightMargin += (newInsets.right - oldInsets.right);
            lp.bottomMargin += (newInsets.bottom - oldInsets.bottom);
        }
        child.setLayoutParams(lp);
    }

6.WindowManagerProxy.java

6.1.normalizeWindowInsets

    public WindowInsets normalizeWindowInsets(Context context, WindowInsets oldInsets,
            Rect outInsets) {
        if (!Utilities.ATLEAST_R || !mTaskbarDrawnInProcess) {
        //R也就是android11以下的
            outInsets.set(oldInsets.getSystemWindowInsetLeft(), oldInsets.getSystemWindowInsetTop(),
                    oldInsets.getSystemWindowInsetRight(), oldInsets.getSystemWindowInsetBottom());
            return oldInsets;
        }

        WindowInsets.Builder insetsBuilder = new WindowInsets.Builder(oldInsets);
        //获取导航栏的inset
        Insets navInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars());

        Resources systemRes = context.getResources();
        Configuration config = systemRes.getConfiguration();

        boolean isTablet = config.smallestScreenWidthDp > MIN_TABLET_WIDTH;
        boolean isGesture = isGestureNav(context);
        boolean isPortrait = config.screenHeightDp > config.screenWidthDp;

        int bottomNav = isTablet//平板模式的话为0
                ? 0
                : (isPortrait
                        ? getDimenByName(systemRes, NAVBAR_HEIGHT)//竖屏高度
                        : (isGesture
                                ? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE)//手势模式
                                : 0));
         //根据平板模式与否,手势导航与否,修改bottomNav的值                       
        Insets newNavInsets = Insets.of(navInsets.left, navInsets.top, navInsets.right, bottomNav);
        insetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
        insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets);
//获取状态栏的inset
        Insets statusBarInsets = oldInsets.getInsets(WindowInsets.Type.statusBars());

        Insets newStatusBarInsets = Insets.of(
                statusBarInsets.left,
                getStatusBarHeight(context, isPortrait, statusBarInsets.top),//修正高度值,补充1
                statusBarInsets.right,
                statusBarInsets.bottom);
        insetsBuilder.setInsets(WindowInsets.Type.statusBars(), newStatusBarInsets);
        insetsBuilder.setInsetsIgnoringVisibility(
                WindowInsets.Type.statusBars(), newStatusBarInsets);

        //手势导航,inset的bottom为0
        if (isGesture) {
            Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement());
            Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top,
                    oldTappableInsets.right, 0);
            insetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets);
        }

        WindowInsets result = insetsBuilder.build();
        //systemBars包含状态栏,导航栏,以及tappableElement
        Insets systemWindowInsets = result.getInsetsIgnoringVisibility(
                WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
        //设置最终的结果
        outInsets.set(systemWindowInsets.left, systemWindowInsets.top, systemWindowInsets.right,
                systemWindowInsets.bottom);
        return result;
    }

>1.getStatusBarHeight

    protected int getStatusBarHeight(Context context, boolean isPortrait, int statusBarInset) {
        Resources systemRes = context.getResources();
        int statusBarHeight = getDimenByName(systemRes,
       //横竖屏读取不同的配置
                isPortrait ? STATUS_BAR_HEIGHT_PORTRAIT : STATUS_BAR_HEIGHT_LANDSCAPE,
                STATUS_BAR_HEIGHT);

        return Math.max(statusBarInset, statusBarHeight);
    }

7.PagedOrientationHandler.java

有3种,竖屏是一种,横屏有两种

public interface PagedOrientationHandler {
//默认用的这个,recents列表页面才可能用其他的
    PagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
    PagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
    PagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();

7.1.PortraitPagedViewHandler

>1.getCenterForPage

    public int getCenterForPage(View view, Rect insets) {
        return (view.getPaddingTop() + view.getMeasuredHeight() + insets.top
            - insets.bottom - view.getPaddingBottom()) / 2;
    }

>2.getScrollOffsetStart

    public int getScrollOffsetStart(View view, Rect insets) {
        return insets.left + view.getPaddingLeft();
    }

>3.getScrollOffsetEnd

    public int getScrollOffsetEnd(View view, Rect insets) {
        return view.getWidth() - view.getPaddingRight() - insets.right;
    }

>4.getChildBounds

默认的位置:垂直居中,横向就是起始位置,竖向居中。

    public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
        boolean layoutChild) {
        final int childWidth = child.getMeasuredWidth();
        final int childRight = childStart + childWidth;
        final int childHeight = child.getMeasuredHeight();
        final int childTop = pageCenter - childHeight / 2;
        if (layoutChild) {
            child.layout(childStart, childTop, childRight, childTop + childHeight);
        }
        return new ChildBounds(childWidth, childHeight, childRight, childTop);
    }

>5.setPrimary

    public <T> void setPrimary(T target, Int2DAction<T> action, int param) {
        action.call(target, param, 0);
    }

8.PagedView

8.1.onMeasure

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (getChildCount() == 0) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }

        if (widthSize <= 0 || heightSize <= 0) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }

        int myWidthSpec = MeasureSpec.makeMeasureSpec(
                getPageWidthSize(widthSize), MeasureSpec.EXACTLY);//宽度参考补充1,
        int myHeightSpec = MeasureSpec.makeMeasureSpec(
                heightSize - mInsets.top - mInsets.bottom, MeasureSpec.EXACTLY);

        //child的测量使用的宽高是减去了insets的值的。
        measureChildren(myWidthSpec, myHeightSpec);
        setMeasuredDimension(widthSize, heightSize);
    }

>1.getPageWidthSize

  • getPanelCount正常就是1,折叠屏可能是2.
    private int getPageWidthSize(int widthSize) {
        return (widthSize - mInsets.left - mInsets.right - getPaddingLeft() - getPaddingRight())
                / getPanelCount() + getPaddingLeft() + getPaddingRight();
    }

8.2.onLayout

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        mIsLayoutValid = true;
        final int childCount = getChildCount();
        int[] pageScrolls = mPageScrolls;
        boolean pageScrollChanged = false;
        //补充1,
        if (!pageScrollsInitialized()) {
        //child个数变化了,重新设置数组
            pageScrolls = new int[childCount];
            pageScrollChanged = true;
        }
//参考补充2
        pageScrollChanged |= getPageScrolls(pageScrolls, true, SIMPLE_SCROLL_LOGIC);
        mPageScrolls = pageScrolls;

        if (childCount == 0) {
            onPageScrollsInitialized();
            return;
        }

        final LayoutTransition transition = getLayoutTransition();
        //布局正在转换中,等待结束后再更新滚动值
        if (transition != null && transition.isRunning()) {
            transition.addTransitionListener(new LayoutTransition.TransitionListener() {

                @Override
                public void startTransition(LayoutTransition transition, ViewGroup container,
                        View view, int transitionType) { }

                @Override
                public void endTransition(LayoutTransition transition, ViewGroup container,
                        View view, int transitionType) {
                    if (!transition.isRunning()) {
                        transition.removeTransitionListener(this);
                        updateMinAndMaxScrollX();
                    }
                }
            });
        } else {
            updateMinAndMaxScrollX();//参考8.3
        }

        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < childCount) {
        //补充3
            updateCurrentPageScroll();
            mFirstLayout = false;
        }

        if (mScroller.isFinished() && pageScrollChanged) {
        //参考8.4
            setCurrentPage(getNextPage());//补充5
        }
        onPageScrollsInitialized();
    }

>1.pageScrollsInitialized

看是否需要重新初始化,为空,或者数组长度和child个数不一样。

    protected boolean pageScrollsInitialized() {
        return mPageScrolls != null && mPageScrolls.length == getChildCount();
    }

>2.getPageScrolls

获取所有page的滚动距离,类似线性布局,一个挨着一个往后排

    protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren,
            ComputePageScrollsLogic scrollLogic) {
        final int childCount = getChildCount();

        final int startIndex = mIsRtl ? childCount - 1 : 0;
        final int endIndex = mIsRtl ? -1 : childCount;
        final int delta = mIsRtl ? -1 : 1;
//参考7.1.1
        final int pageCenter = mOrientationHandler.getCenterForPage(this, mInsets);
//容器左边的位置,除去insets和padding
        final int scrollOffsetStart = mOrientationHandler.getScrollOffsetStart(this, mInsets);
  //容器右边的位置,除去insets和padding
        final int scrollOffsetEnd = mOrientationHandler.getScrollOffsetEnd(this, mInsets);
        boolean pageScrollChanged = false;
        int panelCount = getPanelCount();

        for (int i = startIndex, childStart = scrollOffsetStart; i != endIndex; i += delta) {
            final View child = getPageAt(i);
            //可见性不是GONE的话就是true
            if (scrollLogic.shouldIncludeView(child)) {
            //参考7.1.4,获取尺寸并根据是否需要layout进行布局
                ChildBounds bounds = mOrientationHandler.getChildBounds(child, childStart,
                    pageCenter, layoutChildren);
                    //宽
                final int primaryDimension = bounds.primaryDimension;
                //右侧位置
                final int childPrimaryEnd = bounds.childPrimaryEnd;

                //计算child的滚动距离,
                final int pageScroll =
                        mIsRtl ? childPrimaryEnd - scrollOffsetEnd : childStart - scrollOffsetStart;
                        //记录滚动距离
                if (outPageScrolls[i] != pageScroll) {
                    pageScrollChanged = true;
                    outPageScrolls[i] = pageScroll;
                }
                //计算下一个起始位置,初始位置 + child的宽
                //getChildGap:默认是0,两个child之间额外需要添加的间距
                childStart += primaryDimension + getChildGap(i, i + delta);

                //非最后一页
                int lastPanel = mIsRtl ? 0 : panelCount - 1;
                if (i % panelCount == lastPanel) {
                //两个child之间的间隔距离,数据来源参考2.4,子类设置的
                    childStart += mPageSpacing;
                }
            }
        }
//不看,我们的panel是1
        if (panelCount > 1) {
            for (int i = 0; i < childCount; i++) {
                // In case we have multiple panels, always use left most panel's page scroll for all
                // panels on the screen.
                int adjustedScroll = outPageScrolls[getLeftmostVisiblePageForIndex(i)];
                if (outPageScrolls[i] != adjustedScroll) {
                    outPageScrolls[i] = adjustedScroll;
                    pageScrollChanged = true;
                }
            }
        }
        return pageScrollChanged;
    }

>3.updateCurrentPageScroll

    protected void updateCurrentPageScroll() {
        //默认是0
        int newPosition = 0;
        if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
            newPosition = getScrollForPage(mCurrentPage) + mCurrentPageScrollDiff;
        }
        //就是x移动newPosition的位置,就相当于this.scrollTo(newPosition,0)
        mOrientationHandler.setPrimary(this, VIEW_SCROLL_TO, newPosition);
        //同样设置Scroller的数据,
        mScroller.startScroll(mScroller.getCurrX(), 0, newPosition - mScroller.getCurrX(), 0);
        //补充5,立马结束滚动
        forceFinishScroller();
    }

变量

    Int2DAction<View> VIEW_SCROLL_BY = View::scrollBy;
    Int2DAction<View> VIEW_SCROLL_TO = View::scrollTo;

>4.forceFinishScroller

结束滚动

    public void forceFinishScroller() {
        mScroller.forceFinished(true);
        mNextPage = INVALID_PAGE;
        pageEndTransition();
    }

>5.getNextPage

    public int getNextPage() {
        return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
    }

8.3.updateMinAndMaxScrollX

获取最小和最大的滚动距离

    protected void updateMinAndMaxScrollX() {
        mMinScroll = computeMinScroll();
        mMaxScroll = computeMaxScroll();
    }

>1.computeMinScroll

    protected int computeMinScroll() {
        return 0;
    }

>2.computeMaxScroll

其实就是获取最后一个child的滚动数据

    protected int computeMaxScroll() {
        int childCount = getChildCount();
        if (childCount > 0) {
            final int index = mIsRtl ? 0 : childCount - 1;
            return getScrollForPage(index);//补充3
        } else {
            return 0;
        }
    }

>3.getScrollForPage

8.2里给数组设置的数据,这里根据child的索引读取对应的值

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

8.4.setCurrentPage

    public void setCurrentPage(int currentPage) {
        setCurrentPage(currentPage, INVALID_PAGE);
    }

    public void setCurrentPage(int currentPage, int overridePrevPage) {
        if (!mScroller.isFinished()) {
            abortScrollerAnimation(true);
        }
        if (getChildCount() == 0) {
            return;
        }
        int prevPage = overridePrevPage != INVALID_PAGE ? overridePrevPage : mCurrentPage;
        mCurrentPage = validateNewPage(currentPage);
        mCurrentScrollOverPage = mCurrentPage;
        updateCurrentPageScroll();//8.2.3
        notifyPageSwitchListener(prevPage);
        invalidate();
    }

8.5.setPageSpacing

父类方法

    public void setPageSpacing(int pageSpacing) {
        mPageSpacing = pageSpacing;
        requestLayout();
    }

mPageSpacing使用的地方参考补充1和2

>1.getPageScrolls

  • mOrientationHandler用的是PortraitPagedViewHandler
    protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren,
            ComputePageScrollsLogic scrollLogic) {
        final int childCount = getChildCount();

        final int startIndex = mIsRtl ? childCount - 1 : 0;
        final int endIndex = mIsRtl ? -1 : childCount;
        final int delta = mIsRtl ? -1 : 1;
//参考小节7.1
        final int pageCenter = mOrientationHandler.getCenterForPage(this, mInsets);

        final int scrollOffsetStart = mOrientationHandler.getScrollOffsetStart(this, mInsets);
        final int scrollOffsetEnd = mOrientationHandler.getScrollOffsetEnd(this, mInsets);
        boolean pageScrollChanged = false;
        int panelCount = getPanelCount();

        for (int i = startIndex, childStart = scrollOffsetStart; i != endIndex; i += delta) {
            final View child = getPageAt(i);
            //child的可见性不是GONE的话就为true
            if (scrollLogic.shouldIncludeView(child)) {
                ChildBounds bounds = mOrientationHandler.getChildBounds(child, childStart,
                    pageCenter, layoutChildren);
                final int primaryDimension = bounds.primaryDimension;
                final int childPrimaryEnd = bounds.childPrimaryEnd;

                // In case the pages are of different width, align the page to left edge for non-RTL
                // or right edge for RTL.
                final int pageScroll =
                        mIsRtl ? childPrimaryEnd - scrollOffsetEnd : childStart - scrollOffsetStart;
                if (outPageScrolls[i] != pageScroll) {
                    pageScrollChanged = true;
                    outPageScrolls[i] = pageScroll;
                }
                childStart += primaryDimension + getChildGap(i, i + delta);

                // 非最后一个,那么添加页面间距
                int lastPanel = mIsRtl ? 0 : panelCount - 1;
                if (i % panelCount == lastPanel) {
                    childStart += mPageSpacing;
                }
            }
        }

        if (panelCount > 1) {
            for (int i = 0; i < childCount; i++) {
                // In case we have multiple panels, always use left most panel's page scroll for all
                // panels on the screen.
                int adjustedScroll = outPageScrolls[getLeftmostVisiblePageForIndex(i)];
                if (outPageScrolls[i] != adjustedScroll) {
                    outPageScrolls[i] = adjustedScroll;
                    pageScrollChanged = true;
                }
            }
        }
        return pageScrollChanged;
    }

>2.getScrollProgress

    protected float getScrollProgress(int screenCenter, View v, int page) {
        final int halfScreenSize = getMeasuredWidth() / 2;
        int delta = screenCenter - (getScrollForPage(page) + halfScreenSize);
        int panelCount = getPanelCount();
        int pageCount = getChildCount();

        int adjacentPage = page + panelCount;
        if ((delta < 0 && !mIsRtl) || (delta > 0 && mIsRtl)) {
            adjacentPage = page - panelCount;
        }

        final int totalDistance;
        if (adjacentPage < 0 || adjacentPage > pageCount - 1) {
            totalDistance = (v.getMeasuredWidth() + mPageSpacing) * panelCount;
        } else {
            totalDistance = Math.abs(getScrollForPage(adjacentPage) - getScrollForPage(page));
        }

        float scrollProgress = delta / (totalDistance * 1.0f);
        scrollProgress = Math.min(scrollProgress, MAX_SCROLL_PROGRESS);
        scrollProgress = Math.max(scrollProgress, -MAX_SCROLL_PROGRESS);
        return scrollProgress;
    }