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阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • Canvas 渲染顺序 遵循刷油漆规则(画家算法) 依次由Render CameraDepth值、Sorting ...
    沉麟阅读 1,318评论 0 0
  • 一、Unity简介 1. Unity界面 Shift + Space : 放大界面 Scene界面按钮渲染模式2D...
    MYves阅读 8,043评论 0 22
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,937评论 3 118
  • 秋天的树叶,是你寄托的思念。 疆边的戍守,是你一生的追求。 当花儿盛开,梦想在静静怒放。 愿你能幸福,家等着英雄归来。
    ting梦想抱着你阅读 218评论 0 1
  • 有效的本地 cache 机制,可以避免不必要的重复网络加载,不仅能提高相关应用场景的资源加载速度,也可以避免不必要...
    coderanger阅读 3,914评论 0 3