❤️UNITY实战进阶-ScrollView无限循环列表-3

  • 前言

先来展示下效果

自治盗版UI的网抑云


  • 基于GF框架开发

目前我们只有一个主界面,功能:
        1.用户头像和昵称
        2.搜索InputField入口,显示搜索到的歌曲列表
        3.用户拥有的歌单列表
        4.用户某一个歌单中的歌曲列表
        5.播放器-开始/暂停按钮-进度条-音量调节-歌曲ICON-歌曲名称和艺术家
        6.歌曲播放列表未开发

(1).在界面配置表中(DataTable\UIForm)加入这个界面的UI数据 


此表可以全选放入Excel中
导出选择: 文本文件(制表符分割) 即可使用

(2).需要生成对应的加密的bytes文件
呐~点一下就好了...

(3).这个表已经在Preload中加载过了,我们直接使用
        1).新建一个ProcedureMainMenu流程
        2).在ProcedurePreload->OnUpdate中跳转到ProcedureMainMenu流程
        3).在ProcedureMainMenu->OnEnter中打开MainMenuForm
              GameEntry.UI.OpenUIForm(UIFormId.MainMenuForm, this);
综上所述:  框架层面的逻辑已经做完了

这里提供个小妙招, 可根据Generate DataTables的脚本对象DataTableProcessor抓取数据生成UIFormId表, 需要你们自己拿来手改

public static class EnumIdGenerator
    {
        private const string UIFormIdName = "UIFormId";
        private const string UISoundIdName = "UISoundId";

        private const string UIFormHotfixIdName = "HotfixUIFormId";
        private const string UISoundHotfixIdName = "HotfixUISoundId";

        private const string ResourceCodePath = "Assets/Scripts/GameMain/UI";
        private const string HotfixIdCodePath = "Assets/Scripts/Hotfix/Definition/Enum";

        private const string ResourceNamespace = "MyGame";
        private const string HotfixNamespace = "MyGame.Hotfix";

        public static void GeneratorUIFormIdFile(DataTableProcessor dataTableProcessor, int isHotfixPosition = 5)
        {

            StreamWriter(dataTableProcessor, ResourceNamespace, ResourceCodePath, UIFormIdName, isHotfixPosition, false);
            StreamWriter(dataTableProcessor, HotfixNamespace, HotfixIdCodePath, UIFormHotfixIdName, isHotfixPosition, true);
        }

        public static void GeneratorUISoundIdFile(DataTableProcessor dataTableProcessor, int isHotfixPosition = 4)
        {
            StreamWriter(dataTableProcessor, ResourceNamespace, ResourceCodePath, UISoundIdName, isHotfixPosition, false);
            StreamWriter(dataTableProcessor, HotfixNamespace, HotfixIdCodePath, UISoundHotfixIdName, isHotfixPosition, true);
        }

        private static void StreamWriter(DataTableProcessor dataTableProcessor, string nmespace, string codePath, string datatableName, int isHotfixPosition, bool isHotfix)
        {
            if (!Directory.Exists($"{codePath}/"))
            {
                Log.Warning($"{codePath}不存在!");
                return;
            }

            using (StreamWriter sw = new StreamWriter($"{codePath}/{datatableName}.cs"))
            {
                sw.WriteLine("//自动生成于:" + DateTime.Now);
                //命名空间
                sw.WriteLine($"namespace {nmespace}");
                sw.WriteLine("{");
                //类名
                sw.WriteLine("\t/// <summary>");
                sw.WriteLine($"\t/// {dataTableProcessor.GetValue(0, 1)}");
                sw.WriteLine("\t/// <summary>");
                sw.WriteLine($"\tpublic enum {datatableName}");
                sw.WriteLine("\t{");
                //对象
                int start_index = 4;
                for (int i = start_index; i < dataTableProcessor.RawRowCount; i++)
                {
                    if (bool.Parse(dataTableProcessor.GetValue(i, isHotfixPosition)) == isHotfix)
                    {
                        sw.WriteLine($"\t\t//{dataTableProcessor.GetValue(i, 2)}");
                        sw.WriteLine($"\t\t{dataTableProcessor.GetValue(i, 3).ToUpper()} = {dataTableProcessor.GetValue(i, 1)},");
                    }
                }
                //end
                sw.WriteLine("\t}");
                sw.WriteLine("}");
            }
        }
    }

  •  重点知识-无限滚动循环列表-划重点

我相信很多同学面试游戏岗位的时候, 面试官都会问道一个问题:
        Q: 制作背包系统的时候,如果我的背包有足够大的数据时, 我们如何去处理?
        A1:根据数据长度一个个去Instantiate, 对象缓存到一个容器中,就能操作这些对象了
                (恭喜你基础合格了)
        A2:假设背包的格数为6*6, 我们在此基础上多创建2行,当第一页拉到底部时,
             展示后2行的对象,在把前2行对象放到列表最后
                (恭喜你快掌握精髓了)
        A3:A2的回答逻辑上接近完美了, 实现起来也很简单, 但是不去优化, 一直不停的
             GetComponent或者装箱拆箱,会产生大量的GC产生

先看效果:

 直接上代码,伸手党的福利, 但是我也留了一点坑, 那就是我没有发优化过的, 哈哈哈哈哈

    /// <summary>
    /// Content min/max = 0, 1  pivot  = 0, 1
    /// </summary>
    public class XCycleList : MonoBehaviour
    {
        private enum RollingShaftType
        {
            Horizontal,
            Vertical
        }
        public delegate void UpdateListItemEvent(XItem item, int index);
        //滚动轴
        [SerializeField]
        private RollingShaftType m_RollingType = RollingShaftType.Horizontal;
        //单元格宽
        [SerializeField]
        private int m_ItemWidth;
        //单元格高
        [SerializeField]
        private int m_ItemHeight;
        /// <summary>
        /// 显示列数
        /// </summary>
        [SerializeField]
        private int m_ColumnCount;
        /// <summary>
        /// 显示行数
        /// </summary>
        [SerializeField]
        private int m_RowCount;
        // 列间隔
        [SerializeField]
        private int m_OffsetX = 0;
        // 行间隔
        [SerializeField]
        private int m_OffsetY = 0;
        // 起始X间隔
        [SerializeField]
        private int m_StartSpaceX = 0;
        // 起始Y间隔
        [SerializeField]
        private int m_StartSpaceY = 0;
        // 实例化的节点
        [SerializeField]
        private RectTransform m_ContentItemParent;
        // 实例化对象
        [SerializeField]
        private XItem m_ItemTemplate;
        // 滑动
        private ScrollRect m_ScrollRect;
        // 遮罩大小
        private Vector2 m_MaskSize;
        // 创建的数量
        private int m_CreateCount;
        // 列表宽度
        private int m_RectWidth;
        // 列表高度
        private int m_RectHeigh;
        // 列表总的需要显示的数量,外部给
        private int m_ListCount;
        // 总共多少列
        private int m_ColumnSum;
        // 总共多少行
        private int m_RowSum;
        // 当前实际显示的数量(小于或等于createCount)
        private int m_ShowCount;
        // 记录上次的初始序号
        private int m_LastStartIndex = 0;
        // 显示开始序号
        private int m_StartIndex = 0;
        // 显示结束序号
        private int m_EndIndex = 0;
        // item对应的序号
        private Dictionary<int, XItem> m_DicItemIndex = new Dictionary<int, XItem>();
        // 当前的位置
        private Vector3 m_CurItemParentPos = Vector3.zero;
        // 缓存
        private XItem m_TempItem = null;
        private UpdateListItemEvent m_UpdateItemEvent = null;
        private List<int> m_NewIndexList = new List<int>();
        private List<int> m_ChangeIndexList = new List<int>();

        public Dictionary<int, XItem> ItemDatas
        {
            get
            {
                return m_DicItemIndex;
            }
        }

        public bool IsHorizontal
        {
            get { return m_RollingType == RollingShaftType.Horizontal; }
        }

        public T[] GetAllItem<T>()
        {
            return m_ContentItemParent.GetComponentsInChildren<T>();
        }

        private void Awake()
        {
            m_ScrollRect = transform.GetComponent<ScrollRect>();
            if (m_ScrollRect != null)
            {
                m_ScrollRect.horizontal = m_RollingType == RollingShaftType.Horizontal;
                m_ScrollRect.vertical = m_RollingType == RollingShaftType.Vertical;
                m_ScrollRect.onValueChanged.AddListener(OnValueChange);
            }

            if (m_ContentItemParent == null)
            {
                Debug.Log("Item Parent Is NullPtr!");
                return;
            }
            m_ContentItemParent.anchorMin = new Vector2(0, 1);
            m_ContentItemParent.anchorMax = new Vector2(0, 1);
            m_ContentItemParent.pivot = new Vector2(0, 1);

            if (m_ItemTemplate == null)
            {
                Debug.Log("Item Template Is NullPtr!");
                return;
            }
            m_ItemTemplate.SetActive(false);
            RectTransform itemRec = m_ItemTemplate.GetComponent<RectTransform>();
            itemRec.anchorMin = new Vector2(0, 1);
            itemRec.anchorMax = new Vector2(0, 1);
            itemRec.pivot = new Vector2(0, 1);
            m_MaskSize = GetComponent<RectTransform>().sizeDelta;
        }

        /// <summary>
        /// 初始化数据 item长宽,列数和行 ,代码设置,如不调用界面控制
        /// </summary>
        /// <param item宽="width"></param>
        /// <param item长="heigh"></param>
        /// <param 1="column"></param>
        /// <param 一个列表最多能显示多少行(元素)="row"></param>
        public void Init(int width, int heigh, int column, int row)
        {
            m_ItemWidth = width;
            m_ItemHeight = heigh;
            m_ColumnCount = column;
            m_RowCount = row;
            InitData();
        }

        /// <summary>
        /// 初始化列表 item长宽,列数和行 
        /// </summary>
        public void InitData()
        {
            if (m_ColumnCount >= m_RowCount && IsHorizontal)
            {
                m_ColumnCount = m_ColumnCount + 2;
            }
            else
            {
                m_RowCount = m_RowCount + 2;
            }
            m_CreateCount = m_ColumnCount * m_RowCount;
            if (m_CreateCount <= 0)
            {
                Debug.LogError("横纵不能为0!");
                return;
            }
            if (IsHorizontal)
                m_RectHeigh = m_RowCount * (m_ItemHeight + m_OffsetY);
            else
                m_RectWidth = m_ColumnCount * (m_ItemWidth + m_OffsetX);
        }

        设置元素之间的间距 spacing :SetOffset()
        //public void SetOffset(int x, int y)
        //{
        //    m_OffsetX = x;
        //    m_OffsetY = y;
        //    m_RectHeigh = (m_RowCount - 1) * (m_ItemHeight + m_OffsetY);
        //}

        /// <summary>
        /// 刷新赋值列表 回滚到顶部
        /// </summary>
        /// <param 列表的元素的最大个数="count"></param>
        /// <param 委托:进行 单个元素的赋值="updateItem"></param>
        public void InitList(int count, UpdateListItemEvent updateItem)
        {
            //记录有多少个item
            m_ListCount = count;
            m_UpdateItemEvent = updateItem;
            m_ContentItemParent.transform.localPosition = Vector2.zero;
            if (IsHorizontal)
            {
                //计算有多少行,用于计算出总高度
                m_ColumnSum = count / m_RowCount + (count % m_RowCount > 0 ? 1 : 0);
                m_RectWidth = Mathf.Max(0, m_ColumnSum * m_ItemWidth + (m_ColumnSum - 1) * m_OffsetX);
            }
            else
            {
                //计算有多少列,用于计算出总高度
                m_RowSum = count / m_ColumnCount + (count % m_ColumnCount > 0 ? 1 : 0);
                m_RectHeigh = Mathf.Max(0, m_RowSum * m_ItemHeight + (m_RowSum - 1) * m_OffsetY);
            }
            m_ContentItemParent.sizeDelta = new Vector2(m_StartSpaceX + m_RectWidth, m_StartSpaceY + m_RectHeigh);
            //显示item的数量
            m_ShowCount = Mathf.Min(count, m_CreateCount);
            m_StartIndex = 0;
            m_DicItemIndex.Clear();
            //显示多少个
            //设置数据
            for (int i = 0; i < m_ShowCount; i++)
            {
                XItem item = GetItem(i);
                SetItem(item, i);
            }
            ShowListCount(m_ContentItemParent, m_ShowCount);
        }

        /// <summary>
        /// 生成列表 不回滚,继续往下浏览
        /// </summary>
        /// <param 列表的元素的最大个数="count"></param>
        /// <param 委托:进行 单个元素的赋值 = "updateItem"></param>
        public void Refresh(int count, UpdateListItemEvent updateItem)
        {
            m_ListCount = count;
            m_UpdateItemEvent = updateItem;
            if (IsHorizontal)
            {
                //计算有多少行,用于计算出总高度
                m_ColumnSum = count / m_RowCount + (count % m_RowCount > 0 ? 1 : 0);
                m_RectWidth = Mathf.Max(0, m_ColumnSum * m_ItemWidth + (m_ColumnSum - 1) * m_OffsetX);
            }
            else
            {
                //计算有多少列,用于计算出总高度
                m_RowSum = count / m_ColumnCount + (count % m_ColumnCount > 0 ? 1 : 0);
                m_RectHeigh = Mathf.Max(0, m_RowSum * m_ItemHeight + (m_RowSum - 1) * m_OffsetY);
            }
            m_ContentItemParent.sizeDelta = new Vector2(m_StartSpaceX + m_RectWidth, m_StartSpaceY + m_RectHeigh);
            //显示item的数量
            m_ShowCount = Mathf.Min(count, m_CreateCount);
            m_DicItemIndex.Clear();
            if (count == 0)
            {
                ShowListCount(m_ContentItemParent, m_ShowCount);
                return;
            }
            //计算起始的终止序号
            //--如果数量小于遮罩正常状态下能显示的总量
            if (count <= m_CreateCount)
            {
                m_StartIndex = 0;
                m_EndIndex = count - 1;
            }
            else
            {
                m_StartIndex = GetStartIndex(IsHorizontal ? m_ContentItemParent.localPosition.x : m_ContentItemParent.localPosition.y);
                if (m_StartIndex + m_CreateCount >= count)
                {

                    m_StartIndex = count - m_CreateCount;
                    m_EndIndex = count - 1;
                }
                else
                {
                    m_EndIndex = m_StartIndex + m_CreateCount - 1;
                }
            }
            m_LastStartIndex = m_StartIndex;
            if (m_EndIndex < m_StartIndex)
            {
                Debug.LogError("列表有问题!");
                return;
            }
            for (int i = m_StartIndex; i <= m_EndIndex; i++)
            {
                XItem item = GetItem(i - m_StartIndex);
                SetItem(item, i);
            }
            ShowListCount(m_ContentItemParent, m_ShowCount);
        }

        /// <summary>
        /// 创建item 有就拿来用,没有就创建
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        private XItem GetItem(int index)
        {
            XItem item = null;
            if (index < m_ContentItemParent.childCount)
                item = m_ContentItemParent.GetChild(index).GetComponent<XItem>();
            else
                item = Instantiate(m_ItemTemplate);
            item.name = index.ToString();
            item.RectTransform.sizeDelta = new Vector2(m_ItemWidth, m_ItemHeight);
            item.transform.SetParent(m_ContentItemParent);
            item.transform.localScale = Vector3.one;
            return item;
        }

        /// <summary>
        /// 刷新item对应数据信息
        /// </summary>
        /// <param name="item"></param>
        /// <param name="index"></param>
        private void SetItem(XItem item, int index)
        {
            m_DicItemIndex[index] = item;
            item.transform.localPosition = GetPos(index);
            item.transform.name = index.ToString();
            if (m_UpdateItemEvent != null)
                m_UpdateItemEvent(item, index);
        }

        /// <summary>
        /// item对应位置
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        private Vector2 GetPos(int index)
        {
            if (IsHorizontal)
                return new Vector2(m_StartSpaceX + index / m_RowCount * (m_ItemWidth + m_OffsetX), -index % m_RowCount * (m_ItemHeight + m_OffsetY) - m_StartSpaceY);
            else
                return new Vector2(m_StartSpaceX + index % m_ColumnCount * (m_ItemWidth + m_OffsetX), -index / m_ColumnCount * (m_ItemHeight + m_OffsetY) - m_StartSpaceY);
        }

        // 获取起始序列号
        private int GetStartIndex(float x)
        {
            int _spreadWidth = 0;
            int _spreadHeight = 0;
            //float scrollWidth = gameObject.GetComponent<RectTransform>().sizeDelta.x;
            if (IsHorizontal)
            {
                //将负坐标转正
                x = -x;
                if (x <= (m_ItemWidth + _spreadWidth))
                    return 0;
                float scrollWidth = m_MaskSize.x;
                if (x >= (m_ContentItemParent.sizeDelta.x - scrollWidth - _spreadWidth))        //拉到底部了
                {
                    if (m_ListCount <= m_CreateCount)
                        return 0;
                    else
                        return m_ListCount - m_CreateCount;
                }
                return ((int)((x - _spreadWidth) / (m_ItemWidth + m_OffsetX)) + ((x - _spreadWidth) % (m_ItemWidth + m_OffsetX) > 0 ? 1 : 0) - 1) * m_RowCount;
            }
            else
            {
                if (x <= (m_ItemHeight + _spreadHeight))
                    return 0;
                float scrollHeight = m_MaskSize.y;
                if (x >= (m_ContentItemParent.sizeDelta.y - scrollHeight - _spreadHeight))        //拉到底部了
                {
                    if (m_ListCount <= m_CreateCount)
                        return 0;
                    else
                        return m_ListCount - m_CreateCount;
                }
                return ((int)((x - _spreadHeight) / (m_ItemHeight + m_OffsetY)) + ((x - _spreadHeight) % (m_ItemHeight + m_OffsetY) > 0 ? 1 : 0) - 1) * m_ColumnCount;
            }
        }

        //显示子物体的数量
        private void ShowListCount(Transform trans, int num)
        {
            if (trans.childCount < num)
                return;
            for (int i = 0; i < num; i++)
            {
                trans.GetChild(i).gameObject.SetActive(true);
            }
            for (int i = num; i < trans.childCount; i++)
            {
                trans.GetChild(i).gameObject.SetActive(false);
            }
        }

        //列表位置刷新
        private void OnValueChange(Vector2 pos)
        {
            m_CurItemParentPos = m_ContentItemParent.localPosition;
            if (m_ListCount <= m_CreateCount)
                return;
            m_StartIndex = GetStartIndex(IsHorizontal ? m_ContentItemParent.localPosition.x : m_ContentItemParent.localPosition.y);
            //Debug.Log(m_StartIndex);
            if (m_StartIndex + m_CreateCount >= m_ListCount)
            {
                m_StartIndex = m_ListCount - m_CreateCount;
                m_EndIndex = m_ListCount - 1;
            }
            else
            {
                m_EndIndex = m_StartIndex + m_CreateCount - 1;
            }
            if (m_StartIndex == m_LastStartIndex)
                return;
            m_LastStartIndex = m_StartIndex;
            m_NewIndexList.Clear();
            m_ChangeIndexList.Clear();
            for (int i = m_StartIndex; i <= m_EndIndex; i++)
            {
                m_NewIndexList.Add(i);
            }

            var e = m_DicItemIndex.GetEnumerator();
            while (e.MoveNext())
            {
                int index = e.Current.Key;
                if (index >= m_StartIndex && index <= m_EndIndex)
                {
                    if (m_NewIndexList.Contains(index))
                        m_NewIndexList.Remove(index);
                    continue;
                }
                else
                {
                    m_ChangeIndexList.Add(e.Current.Key);
                }
            }

            for (int i = 0; i < m_NewIndexList.Count && i < m_ChangeIndexList.Count; i++)
            {
                int oldIndex = m_ChangeIndexList[i];
                int newIndex = m_NewIndexList[i];
                if (newIndex >= 0 && newIndex < m_ListCount)
                {
                    m_TempItem = m_DicItemIndex[oldIndex];
                    m_DicItemIndex.Remove(oldIndex);
                    SetItem(m_TempItem, newIndex);
                }
            }

        }
    }

产生的GC需要你们自己去解决了.
在创建循环列表的时候我们需要添加scrollview组件

初始化列表的时候会根据你填的Col和Raw计算默认创建个数, 详细逻辑请看InitData()方法
有了数据,我们就刷新列表并赋值,详细逻辑请看InitList()方法
回调是单个赋值只有2个参数, 一个是数据容器中的index和相对应的xitem接口类

举个食用栗子:

    public class SearchMusicForm : MonoBehaviour
    {
        [SerializeField]
        private XCycleList m_CycleSearchList;

        public void OnInit()
        {
            m_CycleSearchList.InitData();
        }

        public void SearchMusic(int songCount, SearchSongResult[] songs)
        {
            m_CycleSearchList.InitList(songs.Length, UpdateListSearchMusic);
        }

        private void UpdateListSearchMusic(XItem item, int index)
        {
            //TODO:对象都到手了,还不赶紧想办法用一用
        }

    }

注意: Content min/max = 0, 1  pivot  = 0, 1  以左上角为起点


  •  F12扒来的XX云的数据结构

 其实可以使用某API直接获得,这块自行学习...

(1).获得登录用户

    public class Profile
    {
        /// <summary>
        /// 
        /// </summary>
        public int userId { get; set; }
        /// <summary>
        /// 此帐号已锁定
        /// </summary>
        public string nickname { get; set; }
        /// <summary>
        /// 头像
        /// </summary>
        public string avatarUrl { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string birthday { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int userType { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int djStatus { get; set; }
    }

    public class BindingsItem
    {
        /// <summary>
        /// 
        /// </summary>
        public int expiresIn { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public bool expired { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string tokenJsonStr { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int refreshTime { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public object id { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int type { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int userId { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public long bindingTime { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string url { get; set; }
    }

    public class UserData
    {
        /// <summary>
        /// 
        /// </summary>
        public int code { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public Profile profile { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public List<BindingsItem> bindings { get; set; }
    }

(2).获得登录用户的所有歌单

    public class PlaylistObject
    {
        /// <summary>
        /// 
        /// </summary>
        public List<object> subscribers { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string artists { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public List<TracksObject> tracks { get; set; } 
        /// <summary>
        /// 
        /// </summary>
        public int subscribedCount { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int cloudTrackCount { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int trackCount { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int playCount { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string description { get; set; }
        /// <summary>
        /// 此帐号已锁定喜欢的音乐
        /// </summary>
        public string name { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public object id { get; set; }   
    }

    public class PlayListsData
    {
        /// <summary>
        /// 
        /// </summary>
        public string version { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public bool more { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public List<PlaylistObject> playlist { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int code { get; set; }
    }

(3).获取用户某歌单的歌曲列表

    public class ArObject
    {
        /// <summary>
        /// 
        /// </summary>
        public int id { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string name { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public List<object> tns { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public List<object> alias { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string img1v1Url { get; set; }
    }

    public class AlObject
    {
        /// <summary>
        /// 
        /// </summary>
        public object id { get; set; }
        /// <summary>
        /// 所念皆星河
        /// </summary>
        public string name { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string picUrl { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public List<object> tns { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public string pic_str { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public long pic { get; set; }
  
    }

    public class VoQuObject
    {
        /// <summary>
        /// 
        /// </summary>
        public int br { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int fid { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int size { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public double vd { get; set; }
    }

    public class TracksObject
    {
        /// <summary>
        /// 所念皆星河
        /// </summary>
        public string name { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public object id { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int pst { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int t { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public List<ArObject> ar { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public double pop { get; set; } 
        /// <summary>
        /// 
        /// </summary>
        public AlObject al { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public VoQuObject h { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public VoQuObject m { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public VoQuObject l { get; set; }
      
    }

    public class PlaylistSongsData
    {
        /// <summary>
        /// 
        /// </summary>
        public int code { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public PlaylistObject playlist { get; set; }
    }

根据这些数据对象对指定的UI对象做一些赋值


  • 播放歌曲-AV Pro Video的使用

        看过前言的同学应该知道,我本来是打算使用uAudio Mp3 PlayerStreamer插件来制作网络歌曲播放的, 后来我放弃了这个插件, 主要原因是无法播放无损音乐格式的文件(.flac), 转而使用AV Pro Video插件

        (1).在框架的自定义中新增VideoPlayerComponent, 一定要在GameEntry.InitCustomComponents初始化

         (2).在MusicPlayer.cs->Awake中增加button和slider的控制

//声音
GameEntry.VideoPlayer.Volume = m_SongVolumeBar.value;
m_SongVolumeBar.onValueChanged.AddListener(OnSongVolumeValueChanged);

//播放进度
m_ProcessBar.onValueChanged.AddListener(OnSongProcessValueChanged);
m_ProcessBarEventTrigger = m_ProcessBar.gameObject.GetOrAddComponent<EventTrigger>();
//开始拖动
AddTriggersListener(m_ProcessBarEventTrigger, EventTriggerType.BeginDrag, OnSongProcessBeginDrag);
//拖动中
AddTriggersListener(m_ProcessBarEventTrigger, EventTriggerType.Drag, OnSongProcessDrag);
//拖动结束
AddTriggersListener(m_ProcessBarEventTrigger, EventTriggerType.EndDrag, OnSongProcessEndDrag);

//播放
m_PlayBtn.onClick.AddListener(OnClickPlay);

//监听acvpro的结束事件播放结束自动暂停
GameEntry.VideoPlayer.VideoPlayEndHandler += VideoEndHandler;

public static void AddTriggersListener(EventTrigger trigger, EventTriggerType eventID, UnityAction<BaseEventData> action)
{
    UnityAction<BaseEventData> callback = new UnityAction<BaseEventData>(action);
    EventTrigger.Entry entry = new EventTrigger.Entry
    {
        eventID = eventID
    };
    entry.callback.AddListener(callback);
    trigger.triggers.Add(entry);
}

         (3) 在Update中展示播放进度

if (m_IsPlay && GameEntry.VideoPlayer.MediaPlayer.Control.IsPlaying())
{
    m_CurPlayTimeMs = GameEntry.VideoPlayer.MediaPlayer.Control.GetCurrentTimeMs();
    m_CurPlayProcess = m_CurPlayTimeMs / m_EndPlayTimeMs;
    m_CurTimeText.text = TimeUtility.GetTimeFormat((int)(m_CurPlayTimeMs / 1000));
    m_ProcessBar.value = m_CurPlayProcess;
}

         (4)拖动进度条并从拖动位置播放歌曲

private void OnSongProcessValueChanged(float processIN)
{
    if (processIN != m_CurPlayProcess)
    {
        //TODO:主要是这句话!!!!
        GameEntry.VideoPlayer.MediaPlayer.Control.Seek(processIN * m_EndPlayTimeMs);
    }
}

private void OnSongProcessBeginDrag(BaseEventData arg0)
{
    if (GameEntry.VideoPlayer.MediaPlayer.Control.IsPlaying())
    {
        Pause();
    }
}

private void OnSongProcessDrag(BaseEventData arg0)
{
    m_CurPlayProcess = m_ProcessBar.value;
    m_CurTimeText.text = TimeUtility.GetTimeFormat((int)(m_CurPlayProcess * m_EndPlayTimeMs / 1000));
}

private void OnSongProcessEndDrag(BaseEventData arg0)
{
    if (!GameEntry.VideoPlayer.MediaPlayer.Control.IsPlaying())
    {
        Play();
    }
}

  •  歌曲下载

        既然音乐的URL已经拿到了, 那我们怎么能不去下载它呢?
        GF框架自带下载器, 让你告别写下载器的烦恼,还支持断点续传  

string path = Utility.Path.GetCombinePath(GlobalData.s_DownloadFolderName, combineName);
GameEntry.Download.AddDownload(path, songObj.data[0].url, this);

 就这一句话就可以下载你想下载的任意连接的资源了
 想要得知下载进度, 下载成功, 下载失败, 那你需要监听这3个事件了, 由GF框架底层的Event做的事件分发

//注册事件
GameEntry.Event.Subscribe(DownloadUpdateEventArgs.EventId, OnDownloadUpdateChanged);
GameEntry.Event.Subscribe(DownloadSuccessEventArgs.EventId, OnDownloadUpdateSuccess);
GameEntry.Event.Subscribe(DownloadFailureEventArgs.EventId, OnDownloadUpdateFailure);


//取消注册事件
GameEntry.Event.Unsubscribe(DownloadUpdateEventArgs.EventId, OnDownloadUpdateChanged);
GameEntry.Event.Unsubscribe(DownloadSuccessEventArgs.EventId, OnDownloadUpdateSuccess);
GameEntry.Event.Unsubscribe(DownloadFailureEventArgs.EventId, OnDownloadUpdateFailure);

//下载
AddDownload(string downloadPath, string downloadUri, object userData)

//上面的object userData 和 下面的ne.UserData是对等的

//需要使用UserData == this来判断是否是当前脚本的监听
private void OnDownloadUpdateSuccess(object sender, GameEventArgs e)
{
    DownloadUpdateEventArgsne ne = (DownloadUpdateEventArgs)e;
    if (ne.UserData == this)
    {

    }
}

  • UnitywebRequest动态加载网络图片

        public void LoadSprite(string networkPath, Image img, string appointName = "")
        {
            StartCoroutine(LoadSprites(networkPath, img, appointName));
        }

        public IEnumerator LoadSprites(string networkPath, Image img, string appointName)
        {
            string fileName;
            if (string.IsNullOrEmpty(appointName))
            {
                fileName = networkPath.Substring(networkPath.LastIndexOf('/') + 1);
            }
            else
            {
                fileName = ReplaceName(appointName, "/", "_"); ;
            }
            //本地存在这个名字 
            if (FindName(fileName))
            {
                string path = string.Format("{0}/{1}", FolderPath, fileName);
                Texture2D texture;
                yield return texture = TextureUtility.LoadTexture(path);
                if (img && texture)
                    img.sprite = (Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)));
            }
            //下载保存本地
            else
            {
                if (string.IsNullOrEmpty(networkPath))
                {
                    if (img != null)
                        img = null;
                    yield break;
                }
                UnityWebRequest www = new UnityWebRequest(networkPath);
                DownloadHandlerTexture download = new DownloadHandlerTexture(true);
                www.downloadHandler = download;
                yield return www.SendWebRequest();
                while (!www.isDone || www.isNetworkError)
                    yield return new WaitForEndOfFrame();
                if (img != null)
                {
                    if (download.texture != null)
                    {
                        img.sprite = (Sprite.Create(download.texture, new Rect(0, 0, download.texture.width, download.texture.height), new Vector2(0.5f, 0.5f)));
                        CreatePNG(string.Format("{0}/{1}", FolderPath, fileName), download.data, fileName);
                    }
                    else
                    {
                        img = null;
                    }
                }
                www.Dispose();
            }
        }

这块可以把使用过的Texture2D存到容器缓存里面, 这样下次读取先从缓存中拉去,没有再去下载,这样虽然内存会增加, 这样能更快速显示

注意: 推荐使用GF框架自带的ObjectPool来使用


  • 最后的最后

上新几张照片瞅瞅看.....

开机啥也不显示
歌单列表和用户
歌单
搜索

无聊的同学欢迎加我的网抑云账号好友(请认准懒猫牌子)
在音乐的海洋里自嗨!


 接下来就开启制作篇章!
由于是边制作边写博客, 进度会偏慢!
周末一般没时间写!
有兴趣的小伙伴可以关注一波

 o(* ̄▽ ̄*)ブ

猜你喜欢

转载自blog.csdn.net/flj135792468/article/details/120265718
今日推荐