Unity基礎(23)-UGUI

UGUI

控件是UGUI內置的,控件上面因因包含不同的組件而不同。

  • Image組件
    Image等價于NGUI的Sprite組件,用于顯示圖片。

    Panal控件就是包含Image組件的, Image控件也是包含Image組件的,Sprite 有圖集的概念,可以選擇整圖導入,UNITY中使用SpriteEditor切割,也可以選擇導入后設置圖片的packageTag系統自動打包圖集,圖片小的,重復性比較高的圖片最好打成圖集,
    注意
    1,一個圖集內的圖片用UISprite,那么它就是一個DrawCall。但是如果你做了一個圖集是1024X1024的。此時你的界面 上只用了圖集中的一張很小的圖,那么很抱歉1024X1024這張大圖都需要載入你的內存里面,1024就是4M的內存,如果你做了10個1024的圖集,你的界面上剛好都只用了每個圖集里面的一張小圖,那么再次抱歉你的內存直接飆40M
    2.帶透明通道和不帶透明通道的,CreatMipMap和不Create 的,不能制作成同一圖集


    組件屬性
    Source Image(圖像源):紋理格式為Sprite(2D and UI)的圖片資源(導入圖片后選擇Texture Type為Sprite(2D and UI))。
    Color(顏色):圖片疊加的顏色。
    Material(材質):圖片疊加的材質,可以用來實現一些特殊效果,如凹凸感覺
    Raycast Target(射線投射目標):是否作為射線投射目標,關閉之后忽略UGUI的射線檢測。
    Set Native Size:點擊此按鈕則 Image 組件的長寬自動與原圖片長寬一致
    Image Type(圖片顯示類型):
    Simple(基本的):圖片整張全顯示,不裁切,不疊加,根據邊框大小會有拉伸。
    Preserve Aspect(鎖定比例):針對Simple模式,勾選之后,無論圖片的外形放大還是縮小,都會一直保持初始的長寬比例。


    Sliced(切片的):圖片切片顯示,在Project頁面選中圖片,切換為Sprite(2D and UI)模式后,點擊Sprite Editor進入圖片裁切模式,將圖片裁切為上圖的形狀,使用Sliced模式后,根據圖片邊框拉伸,圖片的四個角會保持原狀,而1和4部分會隨著圖片的橫向拉伸而拉伸,2和3部分會隨著圖片的縱向拉伸而拉伸,圖片的中間部分會拉伸5進行填充。
    Fill Center(填充中心):勾選后,5顯示,反之,5不可見。


    Tiled(平鋪的):若圖片已經過裁切,則使用Tiled模式后,根據圖片邊框拉伸,圖片的四個角會保持原狀,而1和4部分會隨著圖片的橫向拉伸而拉伸,2和3部分會隨著圖片的縱向拉伸而拉伸,圖片的中間部分會用5進行平鋪填充。若圖片未裁切,則使用Tiled模式后,根據圖片邊框拉伸,圖片保持原大小不做變化,只是用自身平鋪填充。
    Fill Center(填充中心):(已裁切的圖像源才有此選項)勾選后,5顯示,反之,5不可見。


    Filled(填充的):根據填充方式、填充起點、填充比例決定圖片顯示哪一部分。
    Fill Method(填充方式):分為水平、垂直、90度圓、180度圓、360度圓。
    Fill Origin(填充起點):根據填充方式不同有所變化。
    Fill Amount(填充比例):0是完全不顯示,1是完全顯示。

    我們將在學習button組件時進行技能冷卻設置。
    3D場景使用
    1.單個Sprite 直接拖入場景中,系統自動添加SpriteRanderder 組件,作為3D物體直接使用,2. 多個Sprite直接拖入場景,可以直接制作幀動畫,在2D中同樣也可以。

代碼使用:
using UnityEngine;
using UnityEngine.UI;
public class ImgTest : MonoBehaviour {
    public Image img;
    public Material ml;
    void Start () {
        img = GetComponent<Image>();
        // 通過資源加載進行圖片的賦值
        img.sprite = Resources.Load<Sprite>("UI/background");
       
        img.color = Color.red;

        img.material = ml;

        img.raycastTarget = true;
        
        // 根據填充方式、填充起點、填充比例決定圖片顯示哪一部分
        img.type = Image.Type.Filled;
        // 圖片具備在水平方向的拉伸,縮小,
        // 根據下面的fillAmount數值進行
        img.fillMethod = Image.FillMethod.Horizontal;
        // 設置圖片顯示為一半 
        img.fillAmount = 0.5f;    
    }
  • RayImage 組件

Textture 指定要顯示的圖片,注意:圖片類型可以是任何類型
Color 設置圖片的主色調
Material 設定Image控件的渲染材質
Raycast Target 決定是否可接收射線碰撞事件檢測
UV Rect 可以讓圖片的一部分顯示在RawImage組件中

2D使用中(平面UI)
1.Texture用在Raw Image組件上,可以用來制作動畫
2.Tuxture沒有圖集的概念,這樣內存里只會占用你這一張圖的大小,內存雖然小了但是DrawCall就上去了。因為每一張UITexture就是一次DrawCall。原畫或者背景圖建議直接使用UITexture。
3.可以通過UV 調節圖片顯示的偏移,和重復(可以用來制作多格子血條)
3D使用中(即直接拖動此類型的圖片到3D坐標系統)
1.無論單個、多個,不可以直接拖入3D場景中!2D也不行
2.用于3D模型貼圖,(Shader代碼把貼圖和紋理坐標映射),再由GPU把模型渲染出來MeshFiiter組件中模型網格,存儲的紋理坐標信息(Unity自己創建的Cube會自動添加紋理坐標所以創建后就能貼上紋理,3D建模時如果忽略 沒有給模型生成紋理坐標,會導致模型貼上貼圖沒有效果)MesherRenderder 物體渲染組件

using UnityEngine;
using UnityEngine.UI;

public class ImgTest : MonoBehaviour {

    public RawImage img;
    public Material ml;
    void Start () {
        img = GetComponent<RawImage>();

        img.texture = Resources.Load<Texture2D>("UI/background");
       
        img.color = Color.red;

        img.material = ml;

        img.raycastTarget = true;

        img.uvRect = new Rect(new Vector2(0, 0), new Vector2(1,1));
        }
}

  • SpriteEditor (精靈)的切割和導出


1.無論是什么格式的圖片(最好直接使用PS直接導出的PSD格式),Unity都會自己搞一套格式,并且打包的時候也不會用你文件夾下圖片的格式,而是Unity自己的格式。

2.都可以在導入時設置,圖片在發生拉伸變化時使用那種濾波模式,point ,Biliner,Trilinear,得到依次濾波效果提升的圖片,point 使用最鄰近濾波,采樣像素通常只有一個,
圖像放大縮小后會有像素風格,在制作棋盤時,不希望有模糊效果選擇這這種模式更好。Biliner使用線性濾波,找相鄰四個像素差值,放大縮小后會有模糊效果,
會被模糊,Trilinear,幾乎和Biliner是一樣的,只是Triliner在多級紋理漸變中進行了混合,如果一個紋理沒有使用該技術(Creat MitMap)幾乎是一樣效果。
Splite 可以直接選CreatMipMap,Texture需要把圖片設置為Advance后選擇是否使用多級紋理漸變技術(unity會根據相機距離對象距離,生成8個Mip,
該做法在3D場景UI是很好的做法,如果UI都在平面就暴露出了它的弊端,因為都在平面,所以不會有距離相機距離的變化,勾選就行,不然會增加內存,切記。)

3.Texture 在導入設置是Warp Mode 設置可以紋理在渲染超過紋理坐標時,Climp只選擇重復紋理邊緣像素,還是repeat模式重復整個紋理的模式

4.MaxSize 該紋理的最大尺寸,如原圖尺寸為1024*568,該項設置成4096,unity也只會使用它的原尺寸大小,改值的大小大于等于圖片原尺寸,如果小于該紋理質量會有損失

5.Format 格式設置
Compressed 壓縮格式,如果紋理沒有透明通道,一般使用該項,優化內存量,如果有透明通道,顯示原圖片有可能出現問題。4位
    16bit 低質量真彩格式。16位
    TrueColor 真彩模式。質量最高,是壓縮格式的8倍,但也更消耗內存,32位
    Crunched 這種類型將會根據顯卡的GPU來選擇合適的壓縮格式進行壓縮然后會選用一種CPU上就能處理的壓縮格式再壓縮一遍。
           如果在制作供人下載的資源包的時候這種類型非常的合適。這個類型的壓縮需要很長時間,但在運行時解壓是非常快的。
    
 6.使用Advance 進一步設置Sprite 或者Texture 
你的貼圖無論如何都必須是2的冪次方。因為只有2的冪次方圖片 并且沒有透明通道才會被壓縮,
IOS會壓縮成pvr格  式,Android會壓縮成ETC格式,壓縮以后圖片會小很多的,好幾倍的小
如果原圖不是2的冪次方,可以在advance設置Non Power of 2值,

ToNearest :轉換成距離該圖片最近的2的冪次方值。
    ToLarger : 轉換成比該圖片大的2的冪次方值。
    ToSmaller : 轉換成比該圖片小的2的冪次方值。

美工做了一張100100的邊框圖給你,這張圖有時以100100顯示,有時以200100顯示,有時3000100顯示,放大會失真,如何進行?


看那個綠框,把邊界留出來



切割圖集:



  • Text

1、Font:字體
2、Font Style:
(1)Normal:正常
(2)Bold:粗體
(3)Italic:斜體
(4)Bold And Italic:粗體+斜體
3、Font Size:字體大小
4、Line Spacing:行間距(注:Text組件沒有提供修改字間距的屬性,在前面寫過修改字間距的腳本)
5、Rich Text:富文本
1、Alignment:
前面三個按鈕是水平方向(分別為左對齊、居中、右對齊),后面三個按鈕是垂直方向(分別為頂對齊,居中,底對齊)
2、Align By Geometry:
官方解釋:
Use the extents of glyph geometry to perform horizontal alignment rather than glyph metrics.
This can result in better fitting left and right alignment, but may result in incorrect positioning when attempting to overlay multiple fonts (such as a specialized outline font) on top of each other.
使用區段的字形幾何執行水平對齊,而不是字形指標。
這可以導致更好的擬合左和右對齊,但可能會導致不正確的定位當試圖覆蓋多個字體(如專業輪廓字體)上。
3、Horizontal Overflow:水平溢出
(1)Wrap:文本將自動換行,當達到水平邊界
(2)Overflow:文本可以超出水平邊界,繼續顯示
4、Vertical Overflow:垂直溢出
(1)Truncate:文本不顯示超出垂直邊界的部分
(2)Overflow:文本可以超出垂直邊界,繼續顯示
5、Best Fit:勾選之后,編輯器發生變化,顯示Min Size和Max Size
(1)Min Size:最小大小
(2)Max Size:最大大小
當邊框很大時,文字最大顯示Max Size字體大小;當邊框很小時,文字最小顯示Min Size字體大小,邊框顯示不了MinSize字體大小就不再顯示文字了。
Color:顏色
Material:材質
Raycast Target:來自類Graphic,當該項為false時,消息會透傳

富文本
你好,我是一<color = “red”>雷潮</color>asdjkl
修改顏色,發現雷潮并沒有改變顏色。這里面是HTML語法控制
  • InputField
using UnityEngine;
using UnityEngine.UI;

public class TestInputFiled : MonoBehaviour
{
    public InputField filed;
    void Start()
    {
        // InputField 有兩個空間,一個是提示文本控件Placeholder,一個是輸入文本控件Text     
        filed.placeholder.GetComponent<Text>().text = "請輸入賬號";
        // 修改輸入內容框的內容
        filed.text = "999";
        // 設置內容類型格式:密碼
        filed.contentType = InputField.ContentType.Password;
        // 設置輸入類型格式:密碼
        filed.inputType = InputField.InputType.Password;
        // 超過邊界則換行
        filed.lineType = InputField.LineType.MultiLineSubmit;
        // 設置字數限制,0表示不限制
        filed.characterLimit = 0;
        // 光標閃爍速度
        filed.caretBlinkRate = 1.0f;
        // 在手機端隱藏輸入
        filed.shouldHideMobileInput = false; 
        #region 事件監聽
        // 添加輸入框的監聽事件
        filed.onValueChanged.AddListener(OnValueChange);
        filed.onEndEdit.AddListener(OnValueEdit);
        #endregion
    }
    // 當值發生改變輸出
    void OnValueChange(string str)
    {
        Debug.Log(str);
    }
     // 當輸入完成后回車后,輸出結果
    void OnValueEdit(string str)
    {
        Debug.Log("完成編輯后:"+ str);
    }


  • Button

Interactable:勾選,按鈕可用,取消勾選,按鈕不可用。
Transition:按鈕在狀態改變時自身的過渡方式:
Color Tint(顏色改變) Sprite Swap(圖片切換) Animation(執行動畫)
Normal Color(默認顏色):初始狀態的顏色。
Highlighted Color(高亮顏色):選中狀態或是鼠標靠近會進入高亮狀態。
Pressed Color(按下顏色):鼠標點擊或是按鈕處于選中狀態時按下enter鍵。
Disabled Color(禁用顏色):禁用時顏色。
Color Multiplier(顏色切換系數):顏色切換速度,越大則顏色在幾種狀態間變化速度越快。
Fade Duration(衰落延時):顏色變化的延時時間,越大則變化越不明顯。
當選擇Sprite Swap,出現的信息我們可這樣設置
Highlighted Sprite(高亮圖片):選中狀態或是鼠標靠近會進入高亮狀態。
Pressed Sprite(按下圖片):鼠標點擊或是按鈕處于選中狀態時按下enter鍵。
Disabled Sprite(禁用圖片):禁用時圖片。
最下面還有個Navigation是個導航鍵,實現兩個鍵之間的連接,可在上面條件腳本實現事件
選擇Animation,選中uto Generate Animatic會有提示我們保存文件我們保存好就行

關于按鈕的事件統一管理方法

 private Button[] btns;

    void Start()
    {
        btns = FindObjectsOfType<Button>();
        for (int i = 0; i < btns.Length; i++)
        {
            Button btn = btns[i];
            // 使用Lambda表達式添加偵聽方法
            btn.onClick.AddListener(()=>BtnClick(btn));
        }
    }
/* 這里通過Button的名字進行操作*/
 public void BtnClick(Button btn)
    {
        switch (btn.name)
        {
            case "Start":
                Debug.Log("Start");
                StartGame();
                break;
            case "Stop":
                Debug.Log("Stop");
                StopGame();
                break;
            case "Quit":
                Debug.Log("Quit");
                QuitGame();
                break;
        }
    }

    public void StartGame()
    {
        Debug.Log("Start");
       // 開始游戲的一些邏輯 
    }

    public void StopGame()
    {
        Debug.Log("Stop");
        // 停止游戲的一些邏輯 
    }

    public void QuitGame()
    {
        Debug.Log("Quit");
        // 退出游戲的一些邏輯 
    }
案例操作說明
  • Slider
    是一個主要用于形象的拖動以改變目標值的控件,他的最恰當應用是用來改變一個數值,最大值和最小值自定義,拖動滑塊可在此之間改變,例如改變聲音大小。

Fill Rect(填充矩形):滑塊與最小值方向所構成的填充區域所要使用的填充矩形,如果滑動條的作用只是用于改變指定值,
那么此選項建議置空,這個相比于Scrollbar所多出來的屬性主要用于標識從最小值變化到當前值所經過的變化區域,
如果用做進度條(顯示任務進行進度)的話,這個屬性是比Scrollbar多出來的一個優勢。
Handle Rect(操作條矩形):當前值處于最小值與最大值之間比例的顯示范圍,也就是整個滑條的最大可控制范圍。
Direction(方向):滑動條的方向,從左至右,從上至下還是其他的。
Min Value(最小值):滑動條的可變化最小值。
Max Value(最大值):滑動條的可變化最大值。
Whole Numbers(變化值為整型):勾選此項,拖動滑動條將按整型數(最小為1)進行改變指定值。
Value(值):當前滑動條對應的值。
On Value Changed:值改變時觸發消息。
【注】:在On Value Change 事件被調用的時候
每當滑塊的數值由于拖動被改變時調用,float類型的值會被傳遞無論WholeNumber屬性是否啟用。

    public Slider sl;
    public GameObject cube;
    public float speed;
    private void Start()
    {
        sl = GetComponent<Slider>();
        // 一開始賦值是確保物體按照設定值進行,可以將值保存起來
        speed = sl.value;
    }

    private void Update()
    {
        cube.transform.Rotate(Vector3.up * Time.deltaTime * speed);
    }

    // 偵聽我的Value值的改變
    public void MyValueChange(float value)
    {
        speed = value;
    }
}
  • Toggle
public class MyToggle : MonoBehaviour {

    public Toggle tg;
    public GameObject panel;
    void Start () {
        tg = GetComponent<Toggle>();
        // Toggle事件監聽
        //tg.onValueChanged.AddListener(MyIsOn);
        // 一開始一定要判斷。不然會導致勾選了但是一開始沒有效果。
        if (tg.isOn == true)
        {
            MyIsOn1(true);
            MyIsOn2(true);
        }
    }
    public void MyIsOn1(bool b)
    {
        Debug.Log(b + "1---");
        // 修改Panel
        panel.GetComponent<Image>().color = Color.red;
    }
    public void MyIsOn2(bool b)
    {
        Debug.Log(b + "2---");
        // 修改Panel
        panel.GetComponent<Image>().color = Color.green;
    }
}
  • Dropdown
    Lable和Arrow是用來顯示初始化的文字和勾選項的,Lable會根據首選項的內容自動更改

Caption Text和Caption Image是作為下拉列表首選項的文字和圖片顯示,也是我們每次選擇后的內容,因此可代碼調用獲取
Item Text作為下拉列表中每個item的文字顯示,
Item Image可以用來擴展模板增加內容Value值會隨著下拉列表選項的不同而變化,參考代碼部分
Options選項欄內:通過代碼可賦值給相應的Item對象 Dropdown.OptionData

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


public class DropScripts : MonoBehaviour {

    public Dropdown dw;
    private List<string> ListStr;
    private List<Sprite> sprite_list;

    public string[] showText;
    public Sprite[] sprite;
   
    void Start () {
        dw = GetComponent<Dropdown>();
        ListStr = new List<string>();
        sprite_list = new List<Sprite>();
        //ListStr.Add("1");
        //ListStr.Add("2");
        //ListStr.Add("3");
        // 清除選項內容
        // dw.ClearOptions();
        // dw.options.Clear();
        // 添加選項內容
        // dw.AddOptions(ListStr);
        
        // 添加List列表
        AddNames();
        // 賦值
        DropValue();
        // 檢測值改變
        DropValueChange(dw.value);  // 因為一開始默認調用了第一個值
        dw.onValueChanged.AddListener(DropValueChange);
    }

    public void DropValueChange(int index)
    {
        Debug.Log(index);
        if (index == 0)
        {
            Debug.Log("模式1調用了");
        }
        if (index == 1)
        {
            Debug.Log("模式2調用了");
        }
        if (index == 2)
        {
            Debug.Log("模式3調用了");
        }
    }

    // 給內部option賦值
    public void DropValue()
    {
        // 清除選項內容
        dw.options.Clear();
        // 申明一個DropData
        Dropdown.OptionData temoData;
        for (int i = 0; i < showText.Length; i++)
        {
            //給每一個option選項賦值         
            temoData = new Dropdown.OptionData();
            temoData.text = showText[i];
            temoData.image = sprite_list[i];
            dw.options.Add(temoData);
        }
        // 默認選擇第一個
        dw.captionText.text = showText[0];
    }

    // 將數組內的元素添加到List列表中
    void AddNames()
    {
        for (int i = 0; i < showText.Length; i++)
        {
            ListStr.Add(showText[i]);
        }
        for (int i = 0; i < sprite.Length; i++)
        {
            sprite_list.Add(sprite[i]);
        }
    }
}
  • Scrollbar

Handle Rect(操作條矩形):當前值處于最小值與最大值之間比例的顯示范圍,也就是整個滑條的最大可控制范圍。
Direction(方向):滾動條的方向,從左至右,從上至下還是其他的。
Value(值):當前滾動條對應的值。
Size(操作條矩形長度):操作條矩形對應的縮放長度。
//(指定可滾動的位置數量)
Numbers Of Steps:滾動條可滾動的位置數目,為0和1時不生效(事實上只有0個可滾動位置或1個可滾動位置那還叫滾動條嗎),
例如設為2,則拖動滾動條時滾動條只會處在最小值的位置和最大值的位置,因為他的可滾動位置只有2個,
例如設為3,則拖動滾動條時滾動條只會處在最小值的位置、最大值的位置以及中間位置,因為他的可滾動位置只有3個。
On Value Changed:值改變時觸發消息。

  • ScrollRect

屬性:



Content —— 滑動的內容 ( 所有需要滑動展示的內容 )
Horizontal —— 是否支持左右滑動
Vertical —— 是否支持上下滑動
MovementType —— 滑動類型 ( Unrestricted 不受滑動內容邊界限制 Elastic 帶邊界回彈的(Elasticity 彈力) clamped 邊界夾緊 )
Inertia —— 是否支持滑動慣性( Deceleration Rate 減速率 ,我感覺就是慣性的大小)
scroll sensitivity —— 滾動的靈敏度
Viewport —— 視口 ( 一般是Content 的父物體,帶Mask遮罩后的展示區域)
Horizontar Scrollbar —— 左右的滾動條( 連接的滾動條必須放在Scroll View下 )
Visibility —— 滾動條可見性 ( Permanent 不變的( 只有選擇這個關聯的Scrollbar才能隱藏 ) auto hide自動隱藏(如果內容不需要滾動就可以看到隱藏滾動條) Auto Hide and Expand Viewport 自動隱藏并擴展視圖 ( Spacing 滑動區域和滾動條的間距) )

  • 引入事件函數
using UnityEngine.UI;
using UnityEngine;
using UnityEngine.EventSystems;

public class TestOne : MonoBehaviour , IBeginDragHandler,IEndDragHandler, IDragHandler,IDropHandler
{
    public ScrollRect sr;
    public Text t;
    public Scrollbar sb;
    void Start () {
        sr = GetComponent<ScrollRect>();
        t = GetComponentInChildren<Text>();
        sr.content = t.rectTransform;
        sr.horizontal = false;
        sr.vertical = true;
        sr.verticalScrollbar = sb;
        sr.onValueChanged.AddListener(OnValueChange);     
    }

    public void OnValueChange(Vector2 value)
    {
        Debug.Log(value);
        if (value.y > 0.4)
        {
            Debug.Log("第一頁");
        }
    }

    // 剛開始拖拽的時候
    public void OnBeginDrag(PointerEventData eventData)
    {
        Debug.Log("開始拖拽");
        // 假設:一個頁面有太多的數據,這個時候最好通過提前預加載進行展示數據
        // 加載第二頁的數據
        // 加載其他內容
    }

    public void OnDrag(PointerEventData eventData)
    {
        // 當拖拽的時候
        Debug.Log("正在拖拽");
    }

    public void OnDrop(PointerEventData eventData)
    {
        // 當我們不拖拽的時候,調用在OnEndDrag之前
    }

    // 當結束拖拽的時候
    public void OnEndDrag(PointerEventData eventData)
    {
        Debug.Log("結束拖拽" + eventData.pressPosition);
        // 制作結束拖拽后的邏輯,可以體視用戶沒有內容了
    } 

小練習:寫個小框架滑動菜單

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class TestThree : MonoBehaviour,IBeginDragHandler, IEndDragHandler
{
    private float[] pageArray = new float[] {0,0.5f,1 };
    private float targetPosition = 0;
    private bool isDrag = false;

    public ScrollRect sr;
    public Toggle[] tgArray;
    public float smooth = 5f;
    void Start () {
        if (isDrag == false)
        {
            sr.horizontalNormalizedPosition = Mathf.Lerp(sr.horizontalNormalizedPosition, targetPosition, Time.deltaTime * smooth);
        }
    }
    
    // Update is called once per frame
    void Update () {
        
    }
    public void MovePange1(bool isOn)
    {
        if (isOn == true)
        {
            targetPosition = pageArray[0];
        }
    }
    public void MovePange2(bool isOn)
    {
        if (isOn == true)
        {
            targetPosition = pageArray[2];
        }
    }
    public void MovePange3(bool isOn)
    {
        if (isOn == true)
        {
            targetPosition = pageArray[3];
        }
    }


    public void OnBeginDrag(PointerEventData eventData)
    {
        isDrag = true;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        isDrag = false;
        float posX = sr.horizontalNormalizedPosition;
        int index = 0;
        float offset = Mathf.Abs(pageArray[index] - posX);
        for (int i = 0; i < pageArray.Length; i++)
        {
            float offsetTmp = Mathf.Abs(pageArray[i]-posX);
            if (offsetTmp < offset)
            {
                index = i;
                offset = offsetTmp;
            }
        }
        targetPosition = pageArray[index];
        tgArray[index].isOn = true;
        print(sr.horizontalNormalizedPosition);
    }
}

  • Scroll View
    就是由ScrollRect和ScrollBar等控件組成的,滿足開發者需求的集合體控件
ScollView 控件下由三個組成部分,
Viewport 視圖,
Scrollbar Horizontal ,水平滾動條, 
Scrollbar Vertical ,垂直滾動條。

ScrollView的Content不能根據實際Content下的游戲物體的多少自動改變Content的寬高問題

在實際使用UGUI開發的過程中發現一個UGUI的BUG:當Content下的子物體增加時,ScrollBar下的Handle滑條大小沒有實時根據發生Content下的子物體數量發生變化。(在Hierarchy面板中右鍵創建UI->ScrollView,在子物體中找到Content,需要按行列布置的游戲物體都作為Content的子物體掛在Content下)(以開發垂直的ScrollView為例)在查找問題的過程中發現:我的這個項目里Content的高小于遮罩層Viewport的的高,致使ScrollBar滑條的size一直為1的狀態。調整Content的高使高大于遮罩層Viewport的的高后又發現如下問題:在編輯模式下ScrollBar滑條的size只根據Content與遮罩層Viewport的大小比例進行了調整,而不是根據Content的子物體數量進行變換,致使了在Content下添加的子物體的總高大于Content設置的高時下拉滑條并不能全部顯示的問題,并且在游戲運行時ScrollBar的Size又重新變回1了,無論怎么調整參數都無濟于事。于是自己寫了一個腳本,根據Content下的子物體的個數來控制Content的寬高(原理是修改RectTransform的sizedelta)

/* 
 * 說明:掛在UGUI中ScrollView中的Content游戲物體下(在Hierarchy面板中右鍵創建UI->ScrollView,在子物體中找到Content) * 
 * 功能:解決ScrollView中Content不能根據實際Content下的游戲物體的多少自動改變Content的寬高問題
 *     以至于在Content動態添加需要排序的游戲物體時ScrollBar滑條變更不正確的問題 
 *   (Content Size Fitter組件是用于文本組件時自動根據文本變更大小的組件,這里不適用) 
*/ 
using System.Collections;
using System.Collections.Generic;
using UnityEngine; 
public class ScrollViewContentTool : MonoBehaviour {  
   /// <summary>    
   /// 根據ScrollBar的類型自動調整Content的寬或高   
  /// </summary>    
public enum ScrollBarType   {    
    Vertical,      //為垂直狀態時需設置RectTransform的Anchors為Min(0,1),Max(1,1)        
    Horizontal,    //為水平向右延伸狀態時需設置RectTransform的Anchors為Min(0,0),Max(0,1)        HorizontalAndVertical    
}     
public ScrollBarType m_barType;     
/// <summary>    
/// 該Content表示實際寬高(這里寬高大小應該為Viewport遮罩層的大小)    
/// </summary>    
public float m_ContentWidth = 0;    
public float m_ContentHeight = 0;     
/// <summary>   
 /// Content下排列的游戲物體X軸和Y軸的間距    
/// </summary>    
public Vector2 m_Spacing = Vector2.zero;    
private RectTransform m_rectTransform = null;    
private int m_tempChildCount = 0;    
private Vector2 m_ChildSize = Vector2.zero;  
//存儲Content子物體的寬高   
// Use this for initialization  
private void Awake () {       
   m_rectTransform = this.GetComponent<RectTransform>();        
   m_tempChildCount = this.transform.childCount;        
       if (m_tempChildCount > 0) 
          m_ChildSize = this.transform.GetChild(0).GetComponent<RectTransform>().sizeDelta;        
       if (m_barType == ScrollBarType.Horizontal)       
       {            
          if (m_ContentWidth == 0) Debug.LogError("請設置Content的Width!!");            
          m_rectTransform.anchorMin = new Vector2(0, 0);            
          m_rectTransform.anchorMax = new Vector2(0, 1);            
          m_rectTransform.sizeDelta = new Vector2(m_ContentWidth, 0);       
       }  else if (m_barType == ScrollBarType.Vertical)        {            
              if (m_ContentHeight == 0) Debug.LogError("請設置Content的Height!!");           
             m_rectTransform.anchorMin = new Vector2(0, 1);            
            m_rectTransform.anchorMax = new Vector2(1, 1);            
            m_rectTransform.sizeDelta = new Vector2(0, m_ContentHeight);       
       }  else if (m_barType == ScrollBarType.HorizontalAndVertical)       
       {            
          if (m_ContentHeight == 0 || m_ContentWidth == 0) Debug.LogError("請設置Content的Width和Height!!");                  
          m_rectTransform.anchorMin = new Vector2(0, 0);           
          m_rectTransform.anchorMax = new Vector2(1, 1);            
          m_rectTransform.sizeDelta = new Vector2(0, 0);       
       }         
     //Debug.Log(this.transform.GetChild(0).GetComponent<RectTransform>().sizeDelta);   
}       

private void Update () { 
       if (m_tempChildCount != this.transform.childCount)        
       {            
               m_tempChildCount = this.transform.childCount;            
               UpdateContentSize(m_tempChildCount);        
      }   
 }     
/// <summary>    
/// 根據Content下子物體數量的變化更新Content的寬高    
/// </summary>    
private void UpdateContentSize(int _count)    {        
      if (m_barType == ScrollBarType.Horizontal)        
      {            
            if (_count * m_ChildSize.x > m_ContentWidth)           
            {                
                m_rectTransform.sizeDelta = new Vector2(_count * (m_ChildSize.x + m_Spacing.x), 0);            
            }       
       }  
      else if (m_barType == ScrollBarType.Vertical)       
     {            
            if (_count * m_ChildSize.y > m_ContentHeight)           
           {                
              m_rectTransform.sizeDelta = new Vector2(0, _count * (m_ChildSize.y + m_Spacing.y));
            }       
     }        
        //此時的m_rectTransform.sizeDelta代表往右和往下的增量,為0時代表Content的初始大小       
      else if (m_barType == ScrollBarType.HorizontalAndVertical)       
     {    
              if (_count * m_ChildSize.x > m_ContentWidth)         
              {               
                 float width = Mathf.Abs(m_ContentWidth - _count * (m_ChildSize.x + m_Spacing.x));         
                 m_rectTransform.sizeDelta = new Vector2(width, 0); 
              }             
              if (_count * m_ChildSize.y > m_ContentHeight)          
              {               
                  float height = Mathf.Abs(m_ContentHeight - _count * (m_ChildSize.y + m_Spacing.y));                        
                  m_rectTransform.sizeDelta = new Vector2(0, -height);        
              }   
     }  
  }
}
  • ScrollView無限滾動
    scrollview理論上是支持無限多個item單元(即滾動的單元條目),但實際應用中,我們在一開始實例化幾個或者十幾個item對象時一般是沒問題,但是當item非常多時,幾百或者上千時,完全實例化比較耗時消耗性能大,也有可能帶來占用手機內存比較高,甚至會讓內存溢出。
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Collections.Generic;
 
[RequireComponent(typeof(GridLayoutGroup))]
[RequireComponent(typeof(ContentSizeFitter))]
public class InfinityGridLayoutGroup : MonoBehaviour 
{
 
    [SerializeField]
    int minAmount = 0;//實現無限滾動,需要的最少的child數量。屏幕上能看到的+一行看不到的,比如我在屏幕上能看到 2 行,每一行 2 個。則這個值為 2行*2個 + 1 行* 2個 = 6個。
 
    RectTransform rectTransform;
 
    GridLayoutGroup gridLayoutGroup;
    ContentSizeFitter contentSizeFitter;
 
    ScrollRect scrollRect;
 
    List<RectTransform> children=new List<RectTransform>();
 
    Vector2 startPosition;
 
    int amount = 0;
 
    public delegate void UpdateChildrenCallbackDelegate(int index, Transform trans);
    public UpdateChildrenCallbackDelegate updateChildrenCallback = null;
 
    int realIndex = -1;
    int realIndexUp = -1; //從下往上;
 
    bool hasInit = false;
    Vector2 gridLayoutSize;
    Vector2 gridLayoutPos;
    Dictionary<Transform, Vector2> childsAnchoredPosition = new Dictionary<Transform, Vector2>();
    Dictionary<Transform, int> childsSiblingIndex = new Dictionary<Transform, int>();
 
 
    // Use this for initialization
    void Start ()
    {
        //StartCoroutine(InitChildren());
    }
 
    IEnumerator InitChildren()
    {
        yield return 0;
 
        if (!hasInit)
        {
            //獲取Grid的寬度;
            rectTransform = GetComponent<RectTransform>();
 
            gridLayoutGroup = GetComponent<GridLayoutGroup>();
            gridLayoutGroup.enabled = false;
            contentSizeFitter = GetComponent<ContentSizeFitter>();
            contentSizeFitter.enabled = false;
 
            gridLayoutPos = rectTransform.anchoredPosition;
            gridLayoutSize = rectTransform.sizeDelta;
 
            
            //注冊ScrollRect滾動回調;
            scrollRect = transform.parent.GetComponent<ScrollRect>();
            scrollRect.onValueChanged.AddListener((data) => { ScrollCallback(data); });
 
            //獲取所有child anchoredPosition 以及 SiblingIndex;
            for (int index = 0; index < transform.childCount; index++)
            {
                Transform child=transform.GetChild(index);
                RectTransform childRectTrans= child.GetComponent<RectTransform>();
                childsAnchoredPosition.Add(child, childRectTrans.anchoredPosition);
 
                childsSiblingIndex.Add(child, child.GetSiblingIndex());
            }
        }
        else
        {
            rectTransform.anchoredPosition = gridLayoutPos;
            rectTransform.sizeDelta = gridLayoutSize;
 
            children.Clear();
 
            realIndex = -1;
            realIndexUp = -1;
 
            //children重新設置上下順序;
            foreach (var info in childsSiblingIndex)
            {
                info.Key.SetSiblingIndex(info.Value);
            }
 
            //children重新設置anchoredPosition;
            for (int index = 0; index < transform.childCount; index++)
            {
                Transform child = transform.GetChild(index);
                
                RectTransform childRectTrans = child.GetComponent<RectTransform>();
                if (childsAnchoredPosition.ContainsKey(child))
                {
                    childRectTrans.anchoredPosition = childsAnchoredPosition[child];
                }
                else
                {
                    Debug.LogError("childsAnchoredPosition no contain "+child.name);
                }
            }
        }
 
        //獲取所有child;
        for (int index = 0; index < transform.childCount; index++)
        {
            Transform trans = transform.GetChild(index);
            trans.gameObject.SetActive(true);
 
            children.Add(transform.GetChild(index).GetComponent<RectTransform>());
 
            //初始化前面幾個;
            UpdateChildrenCallback(children.Count - 1, transform.GetChild(index));
        }
 
        startPosition = rectTransform.anchoredPosition;
 
        realIndex = children.Count - 1;
 
        //Debug.Log( scrollRect.transform.TransformPoint(Vector3.zero));
 
       // Debug.Log(transform.TransformPoint(children[0].localPosition));
 
        hasInit = true;
 
        //如果需要顯示的個數小于設定的個數;
        for (int index = 0; index < minAmount; index++)
        {
            children[index].gameObject.SetActive(index < amount);
        }
 
        if (gridLayoutGroup.constraint == GridLayoutGroup.Constraint.FixedColumnCount)
        {
            //如果小了一行,則需要把GridLayout的高度減去一行的高度;
            int row = (minAmount - amount) / gridLayoutGroup.constraintCount;
            if (row > 0)
            {
                rectTransform.sizeDelta -= new Vector2(0, (gridLayoutGroup.cellSize.y + gridLayoutGroup.spacing.y) * row);
            }
        }
        else
        {
            //如果小了一列,則需要把GridLayout的寬度減去一列的寬度;
            int column = (minAmount - amount) / gridLayoutGroup.constraintCount;
            if (column > 0)
            {
                rectTransform.sizeDelta -= new Vector2((gridLayoutGroup.cellSize.x + gridLayoutGroup.spacing.x) * column, 0);
            }
        }
    }
    
    // Update is called once per frame
    void Update () 
    {
    
    }
 
 
    void ScrollCallback(Vector2 data)
    {
        UpdateChildren();
    }
 
    void UpdateChildren()
    {
        if (transform.childCount < minAmount)
        {
            return;
        }
 
        Vector2 currentPos = rectTransform.anchoredPosition;
 
        if (gridLayoutGroup.constraint == GridLayoutGroup.Constraint.FixedColumnCount)
        {
            float offsetY = currentPos.y - startPosition.y;
 
            if (offsetY > 0)
            {
                //向上拉,向下擴展;
                {
                    if (realIndex >= amount - 1)
                    {
                        startPosition = currentPos;
                        return;
                    }
 
                    float scrollRectUp = scrollRect.transform.TransformPoint(Vector3.zero).y;
 
                    Vector3 childBottomLeft = new Vector3(children[0].anchoredPosition.x, children[0].anchoredPosition.y - gridLayoutGroup.cellSize.y, 0f);
                    float childBottom = transform.TransformPoint(childBottomLeft).y;
 
                    if (childBottom >= scrollRectUp)
                    {
                        //Debug.Log("childBottom >= scrollRectUp");
 
                        //移動到底部;
                        for (int index = 0; index < gridLayoutGroup.constraintCount; index++)
                        {
                            children[index].SetAsLastSibling();
 
                            children[index].anchoredPosition = new Vector2(children[index].anchoredPosition.x, children[children.Count - 1].anchoredPosition.y - gridLayoutGroup.cellSize.y - gridLayoutGroup.spacing.y);
 
                            realIndex++;
 
                            if (realIndex > amount - 1)
                            {
                                children[index].gameObject.SetActive(false);
                            }
                            else
                            {
                                UpdateChildrenCallback(realIndex, children[index]);
                            }
                        }
 
                        //GridLayoutGroup 底部加長;
                        rectTransform.sizeDelta += new Vector2(0, gridLayoutGroup.cellSize.y + gridLayoutGroup.spacing.y);
 
                        //更新child;
                        for (int index = 0; index < children.Count; index++)
                        {
                            children[index] = transform.GetChild(index).GetComponent<RectTransform>();
                        }
                    }
                }
            }
            else
            {
                //Debug.Log("Drag Down");
                //向下拉,下面收縮;
                if (realIndex + 1 <= children.Count)
                {
                    startPosition = currentPos;
                    return;
                }
                RectTransform scrollRectTransform = scrollRect.GetComponent<RectTransform>();
                Vector3 scrollRectAnchorBottom = new Vector3(0, -scrollRectTransform.rect.height - gridLayoutGroup.spacing.y, 0f);
                float scrollRectBottom = scrollRect.transform.TransformPoint(scrollRectAnchorBottom).y;
 
                Vector3 childUpLeft = new Vector3(children[children.Count - 1].anchoredPosition.x, children[children.Count - 1].anchoredPosition.y, 0f);
 
                float childUp = transform.TransformPoint(childUpLeft).y;
 
                if (childUp < scrollRectBottom)
                {
                    //Debug.Log("childUp < scrollRectBottom");
 
                    //把底部的一行 移動到頂部
                    for (int index = 0; index < gridLayoutGroup.constraintCount; index++)
                    {
                        children[children.Count - 1 - index].SetAsFirstSibling();
 
                        children[children.Count - 1 - index].anchoredPosition = new Vector2(children[children.Count - 1 - index].anchoredPosition.x, children[0].anchoredPosition.y + gridLayoutGroup.cellSize.y + gridLayoutGroup.spacing.y);
 
                        children[children.Count - 1 - index].gameObject.SetActive(true);
 
                        UpdateChildrenCallback(realIndex - children.Count - index, children[children.Count - 1 - index]);
                    }
 
                    realIndex -= gridLayoutGroup.constraintCount;
 
                    //GridLayoutGroup 底部縮短;
                    rectTransform.sizeDelta -= new Vector2(0, gridLayoutGroup.cellSize.y + gridLayoutGroup.spacing.y);
 
                    //更新child;
                    for (int index = 0; index < children.Count; index++)
                    {
                        children[index] = transform.GetChild(index).GetComponent<RectTransform>();
                    }
                }
            }
        }
        else
        {
            float offsetX = currentPos.x - startPosition.x;
 
            if (offsetX < 0)
            {
                //向左拉,向右擴展;
                {
                    if (realIndex >= amount - 1)
                    {
                        startPosition = currentPos;
                        return;
                    }
 
                    float scrollRectLeft = scrollRect.transform.TransformPoint(Vector3.zero).x;
 
                    Vector3 childBottomRight = new Vector3(children[0].anchoredPosition.x+ gridLayoutGroup.cellSize.x, children[0].anchoredPosition.y, 0f);
                    float childRight = transform.TransformPoint(childBottomRight).x;
 
                   // Debug.LogError("childRight=" + childRight);
 
                    if (childRight <= scrollRectLeft)
                    {
                        //Debug.Log("childRight <= scrollRectLeft");
 
                        //移動到右邊;
                        for (int index = 0; index < gridLayoutGroup.constraintCount; index++)
                        {
                            children[index].SetAsLastSibling();
 
                            children[index].anchoredPosition = new Vector2(children[children.Count - 1].anchoredPosition.x + gridLayoutGroup.cellSize.x + gridLayoutGroup.spacing.x, children[index].anchoredPosition.y);
 
                            realIndex++;
 
                            if (realIndex > amount - 1)
                            {
                                children[index].gameObject.SetActive(false);
                            }
                            else
                            {
                                UpdateChildrenCallback(realIndex, children[index]);
                            }
                        }
 
                        //GridLayoutGroup 右側加長;
                        rectTransform.sizeDelta += new Vector2(gridLayoutGroup.cellSize.x + gridLayoutGroup.spacing.x,0);
 
                        //更新child;
                        for (int index = 0; index < children.Count; index++)
                        {
                            children[index] = transform.GetChild(index).GetComponent<RectTransform>();
                        }
                    }
                }
            }
            else
            {
                //Debug.Log("Drag Down");
                //向右拉,右邊收縮;
                if (realIndex + 1 <= children.Count)
                {
                    startPosition = currentPos;
                    return;
                }
                RectTransform scrollRectTransform = scrollRect.GetComponent<RectTransform>();
                Vector3 scrollRectAnchorRight = new Vector3(scrollRectTransform.rect.width + gridLayoutGroup.spacing.x, 0, 0f);
                float scrollRectRight = scrollRect.transform.TransformPoint(scrollRectAnchorRight).x;
 
                Vector3 childUpLeft = new Vector3(children[children.Count - 1].anchoredPosition.x, children[children.Count - 1].anchoredPosition.y, 0f);
 
                float childLeft = transform.TransformPoint(childUpLeft).x;
 
                if (childLeft >= scrollRectRight)
                {
                    //Debug.LogError("childLeft > scrollRectRight");
 
                    //把右邊的一行 移動到左邊;
                    for (int index = 0; index < gridLayoutGroup.constraintCount; index++)
                    {
                        children[children.Count - 1 - index].SetAsFirstSibling();
 
                        children[children.Count - 1 - index].anchoredPosition = new Vector2(children[0].anchoredPosition.x - gridLayoutGroup.cellSize.x - gridLayoutGroup.spacing.x,children[children.Count - 1 - index].anchoredPosition.y);
 
                        children[children.Count - 1 - index].gameObject.SetActive(true);
 
                        UpdateChildrenCallback(realIndex - children.Count - index, children[children.Count - 1 - index]);
                    }
 
                    
 
                    //GridLayoutGroup 右側縮短;
                    rectTransform.sizeDelta -= new Vector2(gridLayoutGroup.cellSize.x + gridLayoutGroup.spacing.x, 0);
 
                    //更新child;
                    for (int index = 0; index < children.Count; index++)
                    {
                        children[index] = transform.GetChild(index).GetComponent<RectTransform>();
                    }
 
                    realIndex -= gridLayoutGroup.constraintCount;
                }
            }
        }
 
        startPosition = currentPos;
    }
 
    void UpdateChildrenCallback(int index,Transform trans)
    {
        if (updateChildrenCallback != null)
        {
            updateChildrenCallback(index, trans);
        }
    }
 
 
    /// <summary>
    /// 設置總的個數;
    /// </summary>
    /// <param name="count"></param>
    public void SetAmount(int count)
    {
        amount = count;
 
        StartCoroutine(InitChildren());
    }
}

MCV方式進行ScrollView使用,并對內部數據進行設置

Model

public class ScrollVItemDataModel{
    public int index;
    public string name;
    public string imageURL;

    // 構造方法
    public ScrollVItemDataModel(int Index, string Name,string ImageURL)
    {
        index = Index;
        name = Name;
        imageURL = ImageURL;
    }   
}

Control

public class ScrollViewItem : MonoBehaviour {

    // 獲取Item里面的控件
    private Button ItemBtn;
    private Text ItemBtnText;
    // 數據類
    public ScrollViewItemData data;

    private void Awake()
    {
        ItemBtn =this.gameObject.GetComponent<Button>();
        ItemBtnText =  ItemBtn.GetComponentInChildren<Text>();      
    }

    public void SetData(ScrollViewItemData Data)
    {
        data = Data;
        ItemBtnText.text = Data.name;
    }
}

View

 // 添加數據與Item
    List<ScrollViewItem> itemList = new List<ScrollViewItem>();
    List<ScrollViewItemData> itemDataList = new List<ScrollViewItemData>();

    // 獲取網格布局組,進行控件大小排布
    GridLayoutGroup grid;
    // 獲取rect控件
    ScrollRect scrollRect;
    // 設置布局大小
    ContentSizeFitter fitter;

    // 個數
    private int count = 20;

    void Start()
    {
        // 20條內容
        for (int i = 0; i < count; i++)
        {
            itemDataList.Add(new ScrollViewItemData(i, "第" + i + "個元素", "https://123.com"));
        }
        // 獲取網格grid,內容尺寸fitter,scrollRect
        grid = GetComponent<GridLayoutGroup>();
        fitter = GetComponent<ContentSizeFitter>();
        scrollRect = GetComponentInParent<ScrollRect>();
        //監聽ScrollRect的回調
        // scrollRect.onValueChanged.AddListener((data) => { ScrollCallback(data); });
        // 獲取ScrollView寬高(像素)
        Vector2 viewSize = scrollRect.GetComponent<RectTransform>().sizeDelta;
       // Debug.Log(viewSize.y);
       // Debug.Log(grid.cellSize.y);
        
        // 生成20的預制體
        for (int i = 0; i < count; i++)
        {
            if (itemDataList.Count <= i)
                break;
            GameObject itemR = Resources.Load("Prefabs/Item") as GameObject;
            GameObject itemObj = Instantiate(itemR, this.transform) as GameObject;
            ScrollViewItem item = itemObj.AddComponent<ScrollViewItem>();
            itemList.Add(item);
            item.SetData(itemDataList[i]);
        }
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容

  • Canvas 渲染順序 遵循刷油漆規則(畫家算法) 依次由Render CameraDepth值、Sorting ...
    沉麟閱讀 1,505評論 0 0
  • 一、Unity簡介 1. Unity界面 Shift + Space : 放大界面 Scene界面按鈕渲染模式2D...
    MYves閱讀 8,337評論 0 22
  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網絡請求組件 FMDB本地數據庫組件 SD...
    陽明先生_X自主閱讀 16,000評論 3 119
  • 秋天的樹葉,是你寄托的思念。 疆邊的戍守,是你一生的追求。 當花兒盛開,夢想在靜靜怒放。 愿你能幸福,家等著英雄歸來。
    ting夢想抱著你閱讀 234評論 0 1
  • 有效的本地 cache 機制,可以避免不必要的重復網絡加載,不僅能提高相關應用場景的資源加載速度,也可以避免不必要...
    coderanger閱讀 4,022評論 0 3