碧血红天的HomePage

U3d杂谈 — 循环滚动时间选择器组件

我们经常在手机上看那种滚动循环选择年份或者日期的空间。今天,同事询问我目前在UGUI中这个怎么制作。我就抽了2个小时帮助他做了一个组件。实现这个循环滚动的组件,支持缩放,速度滚动,停止校准。

实现的效果如下图,缩放的高度可以调整也可以选择2种不同的缩放方式和缩放偏移。

这里面比较困难一点的是怎么让一个顺序排列的组件。这也是困扰我同事的一点。其实这个换个思考方式也就简单了。如果我们在3D空间把对象放成一个圆是不是很简单,我们只需要使用三角函数就能轻松搞定。我们把2D上循环的也能想象成3D一个圆环在一个方向的投影,我们把X坐标保持不变,然后Y使用三角函数计算出的正负确认确定当前Y是在上面还是下面。

位置计算

所以我们首先为每一个片段指定一个角度,比如当前有24个item,每个item的高度是20像素,那现在整个平铺下来的整个高度是20 * 24像素。那每个像素所占用的角度是360/(20*24)。

我们需记录每隔放个的相对位置,然后我们滑动,就是改变放个的相对位置。我们有个移动偏移量moveDistance。我们知道每个像素代表的角度,我们就可以根据每个方格的相对位置与偏移量计算出当前与游标处的夹角,通过夹角我们就能知道是否超过180度。我们规定负数角度是在游标的下面也就是Y是负,否则就在上面Y为整数。

现在就需要把做成一个环状,我们首先来理解圆的最小夹角怎么算的。比如现在我们当前角度是45度,另一个角度位置是315度。那么这2个角度构成的最小夹角怎么算,是不是把最小的加个360 然后减去大的。因为加一个360,代表的位置是没有变化。

同理,我们在一个直线方向也是如此。现在我们先确定此方格是在上还是下。如果在上,那就是当前偏移位置的角度减去当前方格的实时角度。如果偏移位置角度小,那就加个360再减。如果在下方,那就是当前方格角度减去偏移位置的角度,如果当前方格的角度小于偏移位置,那就当前方格加上360再减。

curMoveDistance = (curMoveDistance + totalLength) % totalLength; //全部转换成正数偏移,过0就是最大偏移开始
        float adgress = curMoveDistance * pixAgress;
        for(int i = 0; i < list.Count; ++i)
        {
            float ad = list[i].factY *  pixAgress;
            float adOffset = adgress - ad;
            float sinY = Mathf.Sin(adOffset / 180 * Mathf.PI);
            list[i].sinY = sinY;
            if(sinY <= 0)
            {
                if(curMoveDistance > list[i].factY)
                {
                    list[i].curY =  curMoveDistance - (list[i].factY + totalLength);
                }else{
                    list[i].curY =  curMoveDistance - list[i].factY;
                }

            }else{
                if(list[i].factY > curMoveDistance)
                {
                    list[i].curY = curMoveDistance + totalLength - list[i].factY;
                }else{
                    list[i].curY = curMoveDistance - list[i].factY;                    
                }

            }
            RefreshGoPosition(list[i]);
        }

curMoveDistance 我们需要足以一下,我们只算正数偏移,比如负方向滚动就是从末尾开始减。

速度衰减

为了表现效果,我们在瞬间拉动的时候,希望有惯性的滚动的效果。有几种方式实现,可以是固定时间停止还是速度衰减, 我这里使用速度衰减,也就是每一帧速度衰减一部分。

public void OnDrag(PointerEventData eventData)
    {
        curMoveDistance += eventData.delta.y;      
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        isDraging = true;
        curSpeed = 0;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        if (Mathf.Abs(eventData.delta.y) > attenSpend)
        {
            curSpeed = eventData.delta.y;
        }
        isDraging = false;
    }
if(curSpeed > 0)
        {
            if (curSpeed < attenSpend)
            {
                curSpeed = 0;
            }
            else
            {
                curSpeed -= attenSpend;
            }
               
            curMoveDistance += curSpeed;
            
        }else if(curSpeed < 0)
        {
            if (Mathf.Abs(curSpeed) < attenSpend)
            {
                curSpeed = 0;
            }
            else
            {
                curSpeed += attenSpend;
            }
            curMoveDistance += curSpeed;
        }
        else
        {
            if(!isDraging && selectItem != null && selectItem.curY != 0)
            {
                curMoveDistance -= selectItem.curY;
            }
        }

这里还能加上曲线速度衰减,我这里就没有试了。

滚动缩放

就是在选中的item是最大,上下2边的是有缩放值的。我这里加了2种,一种就是利用我们计算sinY值来缩放,另一种是通过距离0的偏移量缩放。但是缩放我们不想缩放到0,希望[0.3,1]这样的区间变化,特意加了一个缩放偏移,这样避免缩放成0 的情况。

float sacle = 1 - Mathf.Abs(data.sinY);
        if(scaleType == ScaleType.DISTANCE_TYPE)
        {
            //距离偏移缩放
            sacle = 1 - Mathf.Min(Mathf.Abs(data.curY) / (itemHeight * scaleCount), 1);
        }

        sacle = (scaleOffset + sacle) / (1 + scaleOffset); //缩放偏移

下面附上完整的组件代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class ItemData
{
    public float factY;
    public float curY;
    public int index;
    public float sinY;
    public GameObject go;
    public RectTransform rectTrf;
}

public enum ScaleType
{
    DISTANCE_TYPE = 1, //距离方式
    SIN_TYPE = 2,// 三角函数Sin 方式
}

public class CircleControl : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler
{
    public GameObject baseGo;
    public int count;
    public float itemHeight;
    public bool isRectTransform;
    public float attenSpend = 0.3f;
    public float scaleOffset;
    public bool isUseScale;
    public ScaleType scaleType = ScaleType.DISTANCE_TYPE;
    public int scaleCount = 1; //只在DISTANCE_TYPE 有效

    private float totalLength;
    private float pixAgress;
    private float curSpeed;

    private System.Action<int, GameObject> renderCallBack;
    

    private float curMoveDistance;
    private ItemData selectItem;
    private bool isDraging;

    private List<ItemData> list = new List<ItemData>();

    void Start()
    {
        totalLength = count * itemHeight;
        pixAgress =  360 / totalLength;
        for(int i = 0; i < count; ++i)
        {
            GameObject t = GameObject.Instantiate(baseGo);
            t.transform.SetParent(gameObject.transform);
            t.transform.localPosition = Vector3.zero;
            ItemData d = new ItemData();
            d.index = i;
            d.factY = i * itemHeight;
            d.curY = d.factY;
            d.go = t;
            if(isRectTransform)
            {
                d.rectTrf = t.GetComponent<RectTransform>();
            }
            list.Add(d);
            //t.GetComponent<Text>().text = i.ToString();
            if(renderCallBack != null)
            {
                renderCallBack(d.index, t);
            }
        }
    }

    public void SetRenderCallBack(System.Action<int, GameObject> callback)
    {
        renderCallBack = callback;
    }

    public int GetCurSelectIndex()
    {
        if(selectItem != null)
        {
            return selectItem.index;
        }
        return -1;
    }

    private void RefreshGoPosition(ItemData data)
    {
        float sacle = 1 - Mathf.Abs(data.sinY);
        if(scaleType == ScaleType.DISTANCE_TYPE)
        {
            //距离偏移缩放
            sacle = 1 - Mathf.Min(Mathf.Abs(data.curY) / (itemHeight * scaleCount), 1);
        }

        sacle = (scaleOffset + sacle) / (1 + scaleOffset); //缩放偏移
        if (isRectTransform)
        {
            data.rectTrf.anchoredPosition = new Vector2(0, data.curY);
            if (isUseScale)
            {
                data.rectTrf.localScale = new Vector3(sacle, sacle, 1);
            }
        }
        else
        {
            data.go.transform.localPosition = new Vector3(0, data.curY, 0);
            if (isUseScale)
            {
                if (isUseScale)
                {
                    data.go.transform.localScale = new Vector3(sacle, sacle, 1);
                }
            }
        }
        
        if(Mathf.Abs(data.curY) <= itemHeight/2)
        {
            selectItem = data;
        }
    }

    void Update()
    {
        if(curSpeed > 0)
        {
            if (curSpeed < attenSpend)
            {
                curSpeed = 0;
            }
            else
            {
                curSpeed -= attenSpend;
            }
               
            curMoveDistance += curSpeed;
            
        }else if(curSpeed < 0)
        {
            if (Mathf.Abs(curSpeed) < attenSpend)
            {
                curSpeed = 0;
            }
            else
            {
                curSpeed += attenSpend;
            }
            curMoveDistance += curSpeed;
        }
        else
        {
            if(!isDraging && selectItem != null && selectItem.curY != 0)
            {
                curMoveDistance -= selectItem.curY;
            }
        }
        curMoveDistance = (curMoveDistance + totalLength) % totalLength; //全部转换成正数偏移,过0就是最大偏移开始
        float adgress = curMoveDistance * pixAgress;
        for(int i = 0; i < list.Count; ++i)
        {
            float ad = list[i].factY *  pixAgress;
            float adOffset = adgress - ad;
            float sinY = Mathf.Sin(adOffset / 180 * Mathf.PI);
            list[i].sinY = sinY;
            if(sinY <= 0)
            {
                if(curMoveDistance > list[i].factY)
                {
                    list[i].curY =  curMoveDistance - (list[i].factY + totalLength);
                }else{
                    list[i].curY =  curMoveDistance - list[i].factY;
                }

            }else{
                if(list[i].factY > curMoveDistance)
                {
                    list[i].curY = curMoveDistance + totalLength - list[i].factY;
                }else{
                    list[i].curY = curMoveDistance - list[i].factY;                    
                }

            }
            RefreshGoPosition(list[i]);
        }
    }

    public void OnDrag(PointerEventData eventData)
    {
        curMoveDistance += eventData.delta.y;      
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        isDraging = true;
        curSpeed = 0;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        if (Mathf.Abs(eventData.delta.y) > attenSpend)
        {
            curSpeed = eventData.delta.y;
        }
        isDraging = false;
    }
}

31 评论

  1. hi opp ggeis 2022 ert go fi

  2. canadian cialis online cvs cialis tadalafil online without prescription

  3. viagra chewable how can you get viagra viagra online purchase in india

  4. average price of viagra sildenafil generic nz rx pharmacy generic viagra

  5. buying cialis without prescription tadalafil no prescription forum cialis or levitra

  6. side effects of cialis tadalafil us cialis purchase overnight cialisis on line

  7. cheap tadalafil 20mg what is the generic for cialis cialis online shopping

  8. how to find a reputable canadian pharmacy genoa pharmacy ambien pharmacy price

  9. misoprostol in cvs pharmacy fry’s food store pharmacy hours pharmacy canada cialis

  10. meijer pharmacy lipitor mexican pharmacy valtrex permethrin canada pharmacy

  11. canadian pharmacy cialis tadalafil Celebrex dutasteride from dr reddy’s or inhouse pharmacy

  12. prescription drugs that get you high dilaudid online pharmacy accutane buy canada pharmacy

  13. tadalafil online -canada purchasing cialis tadalafil citrate dosage bodybuilding

  14. cheap viagra generic online generic viagra soft tablets generic viagra price uk

  15. viagra for women over the counter viagra 50 mg tablet buy online viagra 12

  16. sildenafil 100mg discount buy generic viagra india viagra prescription online

  17. research cialis how to save money on cialis cialis next day delivery

  18. purchase viagra online without prescription where to buy viagra pills online sildenafil over the counter australia

  19. over the counter cialis walgreens buy cialis online in austalia cialis and viagra

  20. cialis coupon free trial tadalafil 20 walgreens price online tadalafil

  21. how much is cialis per pill cialis sample request form cialis over the counter usa

  22. tadalafil (megalis-macleods) reviews cialis online no prior prescription cialis coupon voucher

  23. viagra price comparison usa 415 viagra 4 viagra cream price

  24. cialis and viagra cialis drug mylan tadalafil canada

  25. generic sildenafil 92630 viagra best price usa cost of viagra per pill

发表评论