//--------------------------------------------------
        // 公有方法
        //--------------------------------------------------

        // 直接设置指定索引的页面到视口
        public void SetTo(int index)
        {
            // Disabled 状态下留待 enable 后执行
            if (!isActiveAndEnabled)
            {
                newIndex = index;
                return;
            }

            SetTargetIndex(index);

            if (isDragging)
            {
                return;
            }

            // 当前位置 layout 和 viewport 左上角之间的距离(有方向)
            Vector2 cornerDist = UILayoutUtility.GetLayoutLT2ViewportLT(layoutRectTransform, viewport);

            if (direction == Direction.Horizontal && !Mathf.Approximately(cornerDist.x, targetCornerDistance))
            {
                SetLayoutAnchoredPosition(layoutRectTransform.anchoredPosition
                                          + new Vector2(cornerDist.x - targetCornerDistance, 0));
            }
            else if (direction == Direction.Vertical && !Mathf.Approximately(cornerDist.y, targetCornerDistance))
            {
                SetLayoutAnchoredPosition(layoutRectTransform.anchoredPosition
                                          + new Vector2(0, cornerDist.y - targetCornerDistance));
            }
        }
        /// <summary>
        /// 获取页面中心到 viewport 中心在滚动方向上的距离(带符号)。
        /// </summary>
        private float GetPageCenterToViewportCenter(int pageIndex)
        {
            Vector2 delta = UILayoutUtility.GetItemCenterToViewportCenter(layoutRectTransform, viewport,
                                                                          scrolltoableLayout, pageIndex);

            return(direction == Direction.Horizontal ? delta.x : delta.y);
        }
        /// <summary>
        /// 将页面惯性滚动到目标位置。
        /// </summary>
        private void ScrollToTargetPosition(float deltaTime)
        {
            // 当前位置 layout 和 viewport 左上角之间的距离(有方向)
            Vector2 cornerDist = UILayoutUtility.GetLayoutLT2ViewportLT(layoutRectTransform, viewport);

            // 进行横向滚动
            if (direction == Direction.Horizontal && !Mathf.Approximately(cornerDist.x, targetCornerDistance))
            {
                float speed = Mathf.Max(minScrollSpeed, Mathf.Abs(smoothVelocity.x)); // 滚动速度不低于下限
                float delta = cornerDist.x - targetCornerDistance;
                float step  = Mathf.Abs(speed) * deltaTime * Mathf.Sign(delta);

                if ((delta < 0 && step < delta) || (delta > 0 && step > delta))
                {
                    step = delta;
                }

                SetLayoutAnchoredPosition(layoutRectTransform.anchoredPosition + new Vector2(step, 0));
            }
            // 进行纵向滚动
            else if (direction == Direction.Vertical && !Mathf.Approximately(cornerDist.y, targetCornerDistance))
            {
                float speed = Mathf.Max(minScrollSpeed, Mathf.Abs(smoothVelocity.y));
                float delta = cornerDist.y - targetCornerDistance;
                float step  = Mathf.Abs(speed) * deltaTime * Mathf.Sign(delta);

                if ((delta < 0 && step < delta) || (delta > 0 && step > delta))
                {
                    step = delta;
                }

                SetLayoutAnchoredPosition(layoutRectTransform.anchoredPosition + new Vector2(0, step));
            }
        }
        private void UpdatePagesVisibilities()
        {
            // 视口宽度
            float viewportWidth = viewport.rect.width;
            // 视口左边缘相对 Layout 左上角的 x 坐标(通常正值)
            float layoutViewLeft = UILayoutUtility.GetLayoutLT2ViewportLT(rectTransform, viewport).x;

            // 视口左侧最近的不可见 page
            // pageWidth + (pageWidth + pageSpace) * minIndex <= layoutViewLeft;
            // 0.0001f 的作用:
            //   - 消除浮点误差的影响,如 2.0f 的实际结果可能是 2.0000001f
            //   - 解决 CeilToInt(2.0) == 2.0 的问题,保证 2.0 不被显示
            // 视口内最左列
            int minShownIndex = Mathf.CeilToInt((layoutViewLeft - pageSize.x) / (pageSize.x + pageSpace) + 0.0001f);

            minShownIndex = Mathf.Max(minShownIndex, 0);

            // 视口右侧最近的不可见 page
            // (pageWidth + pageSpace) * minIndex => layoutViewLeft + viewportWidth;
            // 视口内最右列
            int maxShownIndex = Mathf.FloorToInt((layoutViewLeft + viewportWidth) / (pageSize.x + pageSpace) - 0.0001f);

            maxShownIndex = Mathf.Min(maxShownIndex, pages.Count - 1);

            if (currentMinShownIndex == -1)
            {
                for (int i = minShownIndex; i <= maxShownIndex; i++)
                {
                    pages[i].ShowItem();
                }
            }
            else if (currentMinShownIndex < minShownIndex || currentMaxShownIndex < maxShownIndex)
            {
                for (int i = currentMinShownIndex; i <= Mathf.Min(minShownIndex - 1, currentMaxShownIndex); i++)
                {
                    pages[i].HideItem();
                }

                for (int i = Mathf.Max(minShownIndex, currentMaxShownIndex + 1); i <= maxShownIndex; i++)
                {
                    pages[i].ShowItem();
                }
            }
            else if (currentMinShownIndex > minShownIndex || currentMaxShownIndex > maxShownIndex)
            {
                for (int i = Mathf.Max(currentMinShownIndex, maxShownIndex + 1); i <= currentMaxShownIndex; i++)
                {
                    pages[i].HideItem();
                }

                for (int i = minShownIndex; i <= Mathf.Min(maxShownIndex, currentMinShownIndex - 1); i++)
                {
                    pages[i].ShowItem();
                }
            }

            currentMinShownIndex = minShownIndex;
            currentMaxShownIndex = maxShownIndex;
        }
        /// <summary>
        /// 更新所有 row 的可见性,显示可见 rows 的元素,隐藏(回收)不可见 row 的元素。
        /// </summary>
        private void UpdateRowsVisibilities()
        {
            // 视口上边缘相对 Layout 左上角的 y 坐标(通常负值)
            float layoutViewTop    = UILayoutUtility.GetLayoutLT2ViewportLT(rectTransform, viewport).y;
            float layoutViewBottom = layoutViewTop - viewport.rect.height;

            // 视口上部最近的不可见 row
            // -topPadding - rowSize.y - (rowSpace + rowSize.y) * minIndex < layoutViewTop;
            // 视口内最上排
            int minShownIndex = Mathf.CeilToInt(-(layoutViewTop + topPadding + rowSize.y) / (rowSpace + rowSize.y));

            minShownIndex = Mathf.Max(minShownIndex, 0);

            // 视口下部最近的不可见 row
            // -topPadding - (rowSpace + rowSize.y) * maxIndex > layoutViewBottom;
            // 视口内最下排
            int maxShownIndex = Mathf.FloorToInt(-(layoutViewBottom + topPadding) / (rowSpace + rowSize.y));

            maxShownIndex = Mathf.Min(maxShownIndex, rows.Count - 1);

            if (lastMinShownIndex == -1)
            {
                for (int i = minShownIndex; i <= maxShownIndex; i++)
                {
                    rows[i].ShowItems();
                }
            }
            else if (lastMinShownIndex < minShownIndex || lastMaxShownIndex < maxShownIndex)
            {
                for (int i = lastMinShownIndex; i <= Mathf.Min(minShownIndex - 1, lastMaxShownIndex); i++)
                {
                    rows[i].HideItems();
                }

                for (int i = Mathf.Max(minShownIndex, lastMaxShownIndex + 1); i <= maxShownIndex; i++)
                {
                    rows[i].ShowItems();
                }
            }
            else if (lastMinShownIndex > minShownIndex || lastMaxShownIndex > maxShownIndex)
            {
                for (int i = Mathf.Max(lastMinShownIndex, maxShownIndex + 1); i <= lastMaxShownIndex; i++)
                {
                    rows[i].HideItems();
                }

                for (int i = minShownIndex; i <= Mathf.Min(maxShownIndex, lastMinShownIndex - 1); i++)
                {
                    rows[i].ShowItems();
                }
            }

            lastMinShownIndex = minShownIndex;
            lastMaxShownIndex = maxShownIndex;
        }
        /// <summary>
        /// 计算如果 layout 被移动到指定 anchoredPosition 时超出 viewport 的向量。
        /// 用于约束 layout 在限定的范围内(不要跟随手指超出 viewport 的约束范围)。
        /// </summary>
        /// <param name="anchoredPosition">指定的 layout 锚定位置。</param>
        /// <returns>偏移量。如果未超出则返回零值。</returns>
        private Vector2 GetOutOffset(Vector2 anchoredPosition)
        {
            // 计入 viewport 和 page 尺寸差异
            Vector2 deltaSize = (viewport.rect.size - scrolltoableLayout.GetItemSize(0)) * 0.5f;

            // 目标位置时 viewport 和 layout 左上角之间的距离
            Vector2 layoutLT2ViewLT = UILayoutUtility.GetLayoutLT2ViewportLT(layoutRectTransform, viewport);

            layoutLT2ViewLT += layoutRectTransform.anchoredPosition - anchoredPosition;

            if (direction == Direction.Horizontal)
            {
                // 左端超出(右滑)
                if (layoutLT2ViewLT.x + deltaSize.x < 0)
                {
                    return(new Vector2(layoutLT2ViewLT.x + deltaSize.x, 0));
                }
                // 右端超出(左滑)
                else if (layoutLT2ViewLT.x + viewport.rect.width - deltaSize.x > layoutRectTransform.rect.width)
                {
                    return(new Vector2(
                               layoutLT2ViewLT.x + viewport.rect.width - layoutRectTransform.rect.width - deltaSize.x, 0));
                }
            }
            else
            {
                // 上端超出(下滑)
                if (layoutLT2ViewLT.y - deltaSize.y > 0)
                {
                    return(new Vector2(0, layoutLT2ViewLT.y - deltaSize.y));
                }
                // 下端超出(上滑)
                else if (layoutLT2ViewLT.y - viewport.rect.height + deltaSize.y < -layoutRectTransform.rect.height)
                {
                    return(new Vector2(
                               0,
                               layoutLT2ViewLT.y - viewport.rect.height + layoutRectTransform.rect.height + deltaSize.y));
                }
            }

            return(Vector2.zero);
        }