UGUI--进阶_常规案例_中

目录

[TOC]

六、优化版滚动视图

1.常规滚动视图Scroll View

需要用到的UI组件:

UI组件的详细介绍可见博客:UGUI–基础_基础组件_中

  • Scroll View滚动视图;

    picture0

  • Vertical Layout Group垂直布局组件;(添加在Content上)

    picture1

  • Content Size Fitter内容大小适配器;(添加在Content上)

Vertical Layout Group垂直布局组件用于将待显示的子项合理得平铺在Content下;Content Size Fitter内容大小适配器用于使Content根据子项来调节自身大小。

最终效果:

GIF1

2.常规滚动视图的缺陷

常规滚动视图会在一开始就将所有的待显示的子项(Content下的各个Image)生成出来,即使Viewport可视区域并不能同一时间同时显现所有子项。

此时就会存在暂时不显示的子项无端占用内存的现象,如果子项不多还没什么关系,如果子项数量成千上万,就会导致巨大的内存消耗。

所以需要自制一个滚动视图来优化此项。

3.优化的滚动视图

效果展示:

GIF2

优化的滚动视图核心原理:

  • 1.生成的子项数量恒定,使用滚动视图的时候,只是动态为子项赋值数据。(不会频繁删除、生成对象)
  • 2.子项动态改变数据核心在于动态改变数据索引,数据索引在初始化的时候确定下来。
  • 3.数据索引的改变策略:

假设Viewport同一时间可以显示3个子项,则此滚动视图共生成4个子项。

首先定义3个变量:

  • self:自身的数据索引
  • start:开始的数据索引
  • end:结束的数据索引

picture2

picture3

代码:

挂载在ScrollView上的CustomScrollView

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

namespace StudyUGUIExample
{
    public class CustomScrollView : MonoBehaviour
    {
        [Tooltip("子项间距")]
        public float Spacing;

        private float mItemHeight;
        private int mSpawnNum;
        private RectTransform mContent;
        private List<CustomScrollViewItem> mItems;//子项List
        private List<CustomScrollViewItemModel> mModels;//子项数据List

        private void Start()
        {
            //初始化的时候注意逻辑时序
            mItems = new List<CustomScrollViewItem>();
            mModels = new List<CustomScrollViewItemModel>();
            GetModels();
            mContent = transform.Find("Viewport/Content").GetComponent<RectTransform>();
            GameObject itemPrefab = Resources.Load<GameObject>("CustomScrollViewItem");
            mItemHeight = itemPrefab.GetComponent<RectTransform>().rect.height;
            SetContentSize();
            mSpawnNum = GetSpawnNum(mContent, mItemHeight);
            SpawnItem(mSpawnNum, itemPrefab, Spacing);

            transform.GetComponent<ScrollRect>().onValueChanged.AddListener(ValueChanged);
        }
        //当滚动视图Viewport中显示项改变时调用一次
        private void ValueChanged(Vector2 data)
        {
            for (int i = 0; i < mItems.Count; i++)
            {
                mItems[i].OnValueChanged();
            }
        }
        private int GetSpawnNum(RectTransform content, float itemHeight)
        {
            float scrollViewHeight = GetComponent<RectTransform>().rect.height;
            return Mathf.CeilToInt(scrollViewHeight / (itemHeight + Spacing)) + 1;
        }
        private void SpawnItem(int count, GameObject prefab, float spacing)
        {
            CustomScrollViewItem item;
            for (int i = 0; i < count; i++)
            {
                item = Instantiate(prefab, mContent).AddComponent<CustomScrollViewItem>();
                item.AddGetModelListener(GetModel);//添加获取数据方法,要在Init之前
                item.Init(i, count, spacing);
                mItems.Add(item);
            }
        }
        private CustomScrollViewItemModel GetModel(int index)
        {
            //对数据索引进行合理性校验,不合理的索引值返回空的数据
            if (index < 0 || index >= mModels.Count)
                return new CustomScrollViewItemModel();
            return mModels[index];
        }
        private void GetModels()
        {
            foreach (Sprite sprite in Resources.LoadAll<Sprite>("Icon"))
            {
                mModels.Add(new CustomScrollViewItemModel(sprite, "描述:" + sprite.name));
            }
        }
        private void SetContentSize()
        {
            //设置Content大小(与数据项个数相关)
            float y = mModels.Count * (mItemHeight + Spacing) - Spacing;
            mContent.sizeDelta = new Vector2(mContent.sizeDelta.x, y);
        }
    }
}

处理自定义滚动视图子项相关逻辑的脚本CustomScrollViewItem

using System;
using UnityEngine;
using UnityEngine.UI;

namespace StudyUGUIExample
{
    public class CustomScrollViewItem : MonoBehaviour
    {
        private RectTransform mRect;
        private Image mImage;
        private Text mText;
        private RectTransform mContent;
        private int mIndex;//序号
        private int mSpawnNum;//生成子项数量
        private CustomScrollViewItemModel mModel;//数据
        private Func<int, CustomScrollViewItemModel> mGetModel;
        private float mSpacing;

        public RectTransform Rect
        {
            get
            {
                if (mRect == null)
                    mRect = GetComponent<RectTransform>();
                return mRect;
            }
        }
        public Image Image
        {
            get
            {
                if (mImage == null)
                    mImage = transform.Find("Image").GetComponent<Image>();
                return mImage;
            }
        }
        public Text Text
        {
            get
            {
                if (mText == null)
                    mText = transform.Find("Text").GetComponent<Text>();
                return mText;
            }
        }

        public void Init(int index, int spawnNum, float spacing)
        {
            mContent = transform.parent.GetComponent<RectTransform>();
            mIndex = -1;
            mSpawnNum = spawnNum;
            mSpacing = spacing;
            ChangeIndex(index);
        }
        //当滚动视图Viewport中显示项改变时调用一次
        public void OnValueChanged()
        {
            int start = 0;
            int end = 0;
            UpdateIndexRange(out start, out end);
            CheckSelfIndex(mIndex, start, end);
        }
        private void UpdateIndexRange(out int start, out int end)
        {
            //开始点的索引值=Content的Y轴坐标/(子项高度+间距)
            start = Mathf.FloorToInt(mContent.anchoredPosition.y / (Rect.rect.height + mSpacing));
            end = start + mSpawnNum - 1;
            //Debug.LogFormat("start+end+self=={0}+{1}+{2}", start, end, mIndex);
        }
        private void CheckSelfIndex(int self, int start, int end)
        {
            int offset = 0;
            if (self < start)
            {
                offset = start - 1 - self;//self向上多移动的格子数
                ChangeIndex(end - offset);
                Debug.LogFormat("start+end+self=={0}+{1}+{2}", start, end, end - offset);
            }
            else if (self > end)
            {
                offset = self - (end + 1);//self向下多移动的格子数
                ChangeIndex(start + offset);
                //Debug.LogFormat("start+end+self=={0}+{1}+{2}", start, end, start + offset);
            }
        }
        private void ChangeIndex(int newIndex)
        {
            //index发生改变且改变的值合法才进行index修改
            if (mIndex != newIndex && ValidIndex(newIndex))
            {
                mIndex = newIndex;
                mModel = mGetModel(mIndex);
                SetModel(mModel);
                //设置新位置
                SetPos(mIndex);
            }
        }
        private bool ValidIndex(int index)
        {
            return !mGetModel(index).Equals(new CustomScrollViewItemModel());
        }
        private void SetPos(int index)
        {
            //索引值与Y轴坐标相关
            Rect.anchoredPosition = new Vector2(0, -index * (Rect.rect.height + mSpacing));
        }
        private void SetModel(CustomScrollViewItemModel model)
        {
            Image.sprite = model.Sprite;
            Text.text = model.Describe;
        }
        public void AddGetModelListener(Func<int, CustomScrollViewItemModel> onGetModel)
        {
            mGetModel = onGetModel;
        }
    }
}

自定义滚动视图子项的数据CustomScrollViewItemModel

using UnityEngine;

namespace StudyUGUIExample
{
    public struct CustomScrollViewItemModel
    {
        public Sprite Sprite;
        public string Describe;

        public CustomScrollViewItemModel(Sprite sprite, string describe)
        {
            Sprite = sprite;
            Describe = describe;
        }
    }
}

七、血条

(受需求影响,此处暂略)

八、UGUI特效层级问题

在粒子特效的Particle System组件中的Renderer下有Order in Layer可以用来控制特效层级:

picture4

创建两个Canvas,设置Render ModeScreen Space - Camera,并设置好渲染摄像机Render Camera;分别为两个Canvas设置不同的渲染层级,一个的Sorting Layer设置为0,一个设置为10。

picture5

因为创建多个Canvas会产生渲染上的性能消耗,所以尽可能创建较少的Canvas,每一个负责一组渲染层级,将相同渲染层级的UI元素放在同一个Canvas下,如此一来可节省性能。

效果如下:(Order大的会覆盖小的进行渲染)

GIF3

九、裁切粒子特效

有时候会有在指定范围内显示粒子特效的需求,此时需要一个遮罩,创建一个空物体,为其添加Sprite Renderer组件、Sprite Mask组件,为组件指定一个遮罩图片。

其中Sprite Mask组件遮罩依据的是图片的透明度Alpha值,Alpha Cutoff即为透明度裁剪阈值,高于此透明度的像素点会视作遮罩内部,反之为遮罩外部。

picture6

然后为粒子特效组件Particle System设置遮罩:(内部会自动搜寻场景中存在Sprite Mask组件的物体)

picture7

Hierarchy面板如下:

picture8

效果如下:

GIF4

十、互动照片墙

(暂略)