❤️UNITY Actual Combat Advanced-ScrollView Infinite Loop List-3

  • foreword

Let's show the effect first

Network suppression cloud of autonomous pirated UI


  • Developed based on GF framework

Currently we only have one main interface with functions:
        1. User avatar and nickname
        2. Search InputField entry, display the list of searched songs
        3. List of playlists owned by the user
        4. List of songs in a certain playlist of the user
        5. Player -Start/Pause button-Progress bar-Volume adjustment-Song ICON-Song name and artist
        6. Song playlist is not developed

(1). Add the UI data of this interface in the interface configuration table (DataTable\UIForm) 


This table can be selected and put into Excel
to export selection: Text file (tab-separated) can be used

(2). The corresponding encrypted bytes file needs to be generated
. Just click it...

(3). This table has been loaded in Preload, we directly use
        1). Create a new ProcedureMainMenu process
        2). Jump to ProcedureMainMenu process in ProcedurePreload->OnUpdate 3). Open MainMenuForm               GameEntry
        in ProcedureMainMenu->OnEnter
.UI.OpenUIForm(UIFormId.MainMenuForm, this);
In summary: the logic at the framework level has been completed

Here is a small trick, you can capture data to generate UIFormId table according to the script object DataTableProcessor of Generate DataTables, you need to change it by yourself

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("}");
            }
        }
    }

  •  Key knowledge - infinite scrolling circular list - focus

I believe that when many students are interviewing for game positions, the interviewer will ask a question:
        Q: When making the backpack system, if my backpack has enough data, how should we deal with it?
        A1: According to the length of the data, go one by one Instantiate, objects are cached in a container, and these objects can be manipulated
                (congratulations, you have passed the basics)
        A2: Assuming that the number of grids in the backpack is 6*6, we create 2 more lines on this basis, and when the first page is pulled to the end In the first part,
             display the objects in the last 2 lines, and put the objects in the first 2 lines at the end of the list
                (congratulations, you have almost mastered the essence)
        A3: The answer to A2 is logically close to perfect, and it is also very simple to implement, but without optimization, Constant
             GetComponent or boxing and unboxing will generate a lot of GC

First look at the effect:

 Directly upload the code and reach out to the party's welfare, but I also left a little pit, that is, I have not optimized it, hahahahaha

    /// <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);
                }
            }

        }
    }

The generated GC needs to be solved by yourself.
When creating a circular list, we need to add the scrollview component

When initializing the list, it will calculate the default creation number based on the Col and Raw you filled in. For detailed logic, please refer to the InitData() method. With the data,
we will refresh the list and assign values. For detailed logic, please refer to the InitList() method.
The callback is a single assignment only 2 parameters, one is the index in the data container and the corresponding xitem interface class

Take an edible chestnut:

    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:对象都到手了,还不赶紧想办法用一用
        }

    }

Note: Content min/max = 0, 1 pivot = 0, 1 starts from the upper left corner


  •  The data structure of XX cloud picked up by F12

In fact, it can be directly obtained  by using an API , this piece of self-learning...

(1). Get the logged-in user

    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). Obtain all playlists of the logged in user

    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). Obtain the song list of a user's playlist

    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; }
    }

Make some assignments to the specified UI objects based on these data objects


  • Playing Songs - Use of AV Pro Video

        Students who have read the preface should know that I originally planned to use the uAudio Mp3 PlayerStreamer plug-in to make online song playback. Later, I gave up this plug-in. The main reason is that I cannot play lossless music format files (.flac), and I switched to AV Pro Video plug-in

        (1). Add VideoPlayerComponent in the customization of the framework, and it must be initialized in GameEntry.InitCustomComponents

         (2). Add button and slider control in MusicPlayer.cs->Awake

//声音
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) Display playback progress in 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) Drag the progress bar and play the song from the dragged position

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();
    }
}

  •  song download

        Now that the URL of the music has been obtained, how can we not download it?
        The GF framework comes with a downloader, so you can say goodbye to the trouble of writing a downloader, and it also supports breakpoint resume  

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

 You can download any connected resources you want to
 download with just this sentence

//注册事件
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 dynamically loads network pictures

        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();
            }
        }

This piece can store the used Texture2D in the container cache, so that the next read will be pulled from the cache first, and there is no need to download it again, so that although the memory will increase, it can be displayed more quickly

Note: It is recommended to use the ObjectPool that comes with the GF framework


  • last of the last

Take a look at the new photos.....

Nothing is displayed on startup
Playlist and users
song list
search

Boring students are welcome to add my Wangyiyun account friend (please look for the lazy cat brand) and
enjoy yourself in the ocean of music!


 Next, the production chapter will start!
Since the blog is written while producing, the progress will be slow!
There is usually no time to write on weekends!
Interested friends can pay attention to a wave

 o(* ̄▽ ̄*)bu

Guess you like

Origin blog.csdn.net/flj135792468/article/details/120265718