• NGUI 滑动组件:UIScrollView


    UIScrollView:滑动组件,核心方法是Press、Drag、CalculateConstrainHelper。
    核心方法、属性:
    UIScrollView:滑动列表
            property:
            smoothDragStart:按下时立即开始滑动还是添加过渡状态
            restrictWithinPanel:滑动结束时是否调用RestrictWithinBounds,让content尽可能多地位于panel内
            canMoveHorizontally/canMoveVertically:是否支持横向/纵向滑动,根据movement判断
            shouldMoveHorizontally/shouldMoveVertically:根据包围盒大小、panel裁剪区域判断是否可以滑动
            shouldMove:所有显示内容是否都不再裁剪范围内,根据包围盒bound和裁剪区域判断
            
            function:
            OnScrollBar:滚动条拉动事件
            RestrictWithinBounds:让content尽可能多地位于panel内,返回此操作所需的Vector2偏移量(位移Vector2,让content尽可能多地位于panel内)
            UpdateScrollbars:更新滚动条
            SetDragAmount:更新uiPanel的坐标、clipOffset、scrollBar
            ResetPosition:重置回原点
            UpdatePosition、OnScrollBar:调用SetDragAmount更新panel坐标
            MoveRelative:移动UIPanel的显示内容
            Press:把滑动后缓冲的动力归零、关闭滑动后的缓冲运动(滑动后是指手指离开屏幕)、记录按下点的世界坐标、创建一个平面;其实就是为滑动做准备.如果为false,会尽量把界面拉回裁剪区域内
            drag:计算滑动偏移、计算缓冲动力、移动
            LateUpdate:计算滑动结束后的一个缓冲效果,就是手指松开以后会继续滑动一段距离,比较平滑
    movement:滑动方向
    dragEffect:滑动效果,是否有惯性
    constrainOnDrag:看代码是处理MomentumAndSpring效果下,滑动超过边界的情况
    Vector3 constraint = CalculateConstrainHelper(canMoveHorizontally, canMoveVertically);
    if (constrainOnDrag)
       RestrictWithinBoundsHelper(true, constraint);
     
    public bool RestrictWithinBoundsHelper (bool instant, Vector3 constraint)
    {
       if (constraint.sqrMagnitude > 0.1f)
       {
          if (!instant && dragEffect == DragEffect.MomentumAndSpring)
          {
             // Spring back into place
             Vector3 pos = mTrans.localPosition + constraint;
             pos.x = Mathf.Round(pos.x);
             pos.y = Mathf.Round(pos.y);
             SpringPanel.Begin(mPanel.gameObject, pos, 8f);
          }
          else
          {
             // Jump back into place
             MoveRelative(constraint);
     
             // Clear the momentum in the constrained direction
             if (Mathf.Abs(constraint.x) > 0.01f) mMomentum.x = 0;
             if (Mathf.Abs(constraint.y) > 0.01f) mMomentum.y = 0;
             if (Mathf.Abs(constraint.z) > 0.01f) mMomentum.z = 0;
             mScroll = 0f;
          }
          return true;
       }
       return false;
    }
    disableDragIfFits:当界面全部位于视图内(裁剪区域)时,true:无法拖动;false:可以拖动内容到边界外,不过会有阻力
    smoothDragStart:按下时立即开始滑动还是从0速度开始,当开始拖动滑块的时候,如果勾上了,则有一个从 0 变成拖动速度的平滑现象,如果不勾,则开始拖动时就与拖动速度一样
    restrictWithinPanel:滑动结束时是否调用RestrictWithinBounds,让content尽可能多地位于panel内
    Momenturm Amount:滑动后,惯性滑行的距离
     
    开始之前,我们先思考下,在unity里要实现拖动一张图片,最方便的实现方法是什么?
    其实就3步:鼠标点触摸touch是一样的
    1.在鼠标按下时记录按下点的坐标,
    2.鼠标抬起时,把当前的鼠标位置坐标减去按下前的坐标,这就是滑动的偏移量,当然一般会在update轮询鼠标坐标,以便在拖动时更新拖动对象的坐标。NGUI的滑动事件是在UICamera的update方法里实现的,通过go.SendMessage方法分发出去,所以只需要在Drag处理逻辑就可以了。
    3.这边还可能涉及到坐标的转换,点击坐标一般是屏幕坐标。
    Ray ray = smoothDragStart ?
       UICamera.currentCamera.ScreenPointToRay(UICamera.currentTouch.pos - mDragStartOffset) :
       UICamera.currentCamera.ScreenPointToRay(UICamera.currentTouch.pos);
     
    float dist = 0f;
     
    if (mPlane.Raycast(ray, out dist))
    {
       Vector3 currentPos = ray.GetPoint(dist);
       Vector3 offset = currentPos - mLastPos;
       mLastPos = currentPos;
    }
    NGUI的UIScrollView实现逻辑跟上面是一样:
    1.通过UIDragScrollView监听UICamera发出的OnPress/OnDrag/OnScroll事件,然后调用UIScrollView对应的接口。
    2.在Press/Drag里处理拖动事件。
    3.UIScrollView在拖动结束的时候会把滑动内容尽可能的显示在裁剪区域内,这是可选的。比如下面的图1会被修改成图2显示,尽可能多的让图片和裁剪区域重合。
    4.UIScrollView实现了惯性的效果,简单说就是手指抬起后,还会惯性滑动一段距离。
    SpringPanel:移动panel,配合ScrollView使用,移动的是UIPanel的裁剪区域clipOffset。
            function:
            Update:unity的Update函数只在OnEnable的时候调用,在这边轮询AdvanceTowardsPosition方法实现坐标移动
            AdvanceTowardsPosition:根据strength、target对坐标差值,最后赋值给mPanel.clipOffset实现panel位移效果
            Begin:开始动画,实际是设置了组件的enabled = true
            end:结束动画,实际是设置了组件的enabled = false
     
    UIWrapContent:基于UIScrollView实现循环滑动列表。
    核心方法WrapContent。
    原理实现:
    1.NGUI的UIScrollView滑动时,实际上时修改的UIPanel的LocalPosition和clipOffset,拿在初始化的时候就可以确定每个item(data)的LocalPosition。
    例如第i行j列,相对于父节点的坐标:
    private Vector3 GetItemLocalPos(int i, int j)
    {
        float posX = size.x * i;
        float posY = size.y * j;
        if (m_moveType == UIScrollView.Movement.Vertical)
        {
            posX = size.x * j;
            posY = size.y * i;
        }
     
        Vector3 localPos = new Vector3(posX, -posY, 0);
        return localPos;
    }
    初始化完成的大概是这样的,无非就是按顺序排序子对象
    2.监听滑动事件,UIPanel的clipOffset发生变化时,会触发onClipMove事件,我们监听这个事件,然后在事件里实现交换逻辑。
    3.交换逻辑。
    1.WrapContent的交换逻辑:轮询每个对象,如果对象不在裁剪区域内,移动对象(最下面的移动到最上面,最左侧的移动到最右侧)。
    Transform t = mChildren[i];
    float distance = t.localPosition.x - center.x;
    float extents = itemSize * mChildren.Count * 0.5f;
     
    if (distance < -extents)
    {
       Vector3 pos = t.localPosition;
       pos.x += ext2;
       distance = pos.x - center.x;
       int realIndex = Mathf.RoundToInt(pos.x / itemSize);
     
       if (minIndex == maxIndex || (minIndex <= realIndex && realIndex <= maxIndex))
       {
          t.localPosition = pos;
          UpdateItem(t, i);
       }
       else allWithinRange = false;
    }
    else if (distance > extents)
    {
       Vector3 pos = t.localPosition;
       pos.x -= ext2;
       distance = pos.x - center.x;
       int realIndex = Mathf.RoundToInt(pos.x / itemSize);
     
       if (minIndex == maxIndex || (minIndex <= realIndex && realIndex <= maxIndex))
       {
          t.localPosition = pos;
          UpdateItem(t, i);
       }
       else allWithinRange = false;
    }
    2.优化逻辑:监听本次滑动的距离,滑动超过一格才处理,而且只处理最边缘一列(最下面、最上面、最左侧、最右侧)。
    //滑动事件
    private void OnPanelLoopClipMove(UIPanel panel) {
        Vector3 delata = m_panelInitPos - panel.transform.localPosition;
        float distance = -1;
     
        int index; //当前显示出来的第一个格子,在grid数据中的index
        distance = delata.y != 0 ? delata.y : delata.x;
        // 满的时候向上滑不管它(滑到头了)
        if (distance > 0 && m_moveType == UIScrollView.Movement.Vertical)
            return;
        if (distance < 0 && m_moveType == UIScrollView.Movement.Horizontal)
            return;
     
        CalDistance(distance, out distance, out index);
        // 拖拽不满一个单元格
        if (index == m_lastDataIndex)
            return;
     
        // 拉到底了
        if (index + m_fillCount >= m_dataArrayNum) {
            index = m_dataArrayNum - m_fillCount;
        }
        // 重刷
        int offset = Math.Abs(index - m_lastDataIndex);
        for (int i = 1; i <= offset; i++)
        {
            //上(左)移动到下(右)
            MoveGridItem(m_lastDataIndex < index);
        }
     
        m_lastDataIndex = index;
    }
     
    private void CalDistance(float distance, out float targetDis, out int index)
    {
        FixBoxPostion();
        targetDis = Mathf.Abs(distance);
        index = Mathf.FloorToInt(targetDis / cellLength);
    }
     
    private void MoveGridItem(bool isTopToBottom) {
        // 判断是否是 上(左)移动到下(右)
        int curIndex;
        int itemIndex;
        int sign;
        if (isTopToBottom) {
            curIndex = m_maxIndex + 1;
            itemIndex = 0;
            sign = 1;
        } else {
            curIndex = m_minIndex - 1;
            itemIndex = m_items.Count - 1;
            sign = -1;
        }
     
        List<Transform> items;
        items = m_items[itemIndex];
     
        int targetIndex = itemIndex == 0 ? m_items.Count - 1 : 0;
     
        m_items.Remove(items);
        m_items.Insert(targetIndex, items);
     
        for (int i = 0; i < items.Count; i++) {
            int dataIndex = curIndex * maxPerLine + i;
            if (dataIndex < 0) {
                break;
            }
            if (dataIndex > m_loopMax - 1) {
                break;
            }
     
            if (m_listener != null)
            {
                int index = Array.IndexOf(m_itemsTran, items[i]);
                m_listener(index, dataIndex);
            }
     
            items[i].localPosition = m_itemPos[dataIndex];
        }
     
        m_minIndex += sign;
        m_maxIndex += sign;
    }
    4.分发事件通知obj。
    m_listener(index, dataIndex); index是对象数组的下标,dataIndex是数据数组的下表
    local item = self._friendItems[objIdx+1] --lua
    local data = self._friendDatas[dataIdx+1]
    5. UIWrapContent待扩展功能:
    1.只支持单行或单列。
    2.固定子节点,适配不方便。
    3.轮询全部节点没必要。
  • 相关阅读:
    Linux Core Dump
    ODP.NET Managed正式推出
    获取EditText的光标位置
    (Java实现) 洛谷 P1603 斯诺登的密码
    (Java实现) 洛谷 P1603 斯诺登的密码
    (Java实现) 洛谷 P1036 选数
    (Java实现) 洛谷 P1036 选数
    (Java实现) 洛谷 P1012 拼数
    (Java实现) 洛谷 P1012 拼数
    (Java实现) 洛谷 P1028 数的计算
  • 原文地址:https://www.cnblogs.com/wang-jin-fu/p/13509096.html
Copyright © 2020-2023  润新知