zoukankan      html  css  js  c++  java
  • 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.轮询全部节点没必要。
  • 相关阅读:
    SSH免密登录
    要不要学AI
    俞军产品方法论-笔记
    数据产品经理:实战进阶-笔记
    java代码中引用了scala类,Maven打包编译时爆出找不到scala类的异常
    Flink unable to generate a JAAS configuration file
    开始学习首席AI架构师
    flink checkpoinnt失败
    程序员的三种发展方向
    每日站会
  • 原文地址:https://www.cnblogs.com/wang-jin-fu/p/13509096.html
Copyright © 2011-2022 走看看