首页 >Unity3D频道 >【NGUI研究院之Unity插件】 > NGUI研究院之自制 Scroll View实现触摸滚动相册效果(四)
2012
07-10

NGUI研究院之自制 Scroll View实现触摸滚动相册效果(四)

         MOMO与图灵合作的《Unity 3D游戏开发》没想到开售不到1个月就售罄了,心里实在是太高兴了。不过高兴归高兴,学习生活工作我是不能耽误的,加油哇咔咔。 NGUI系列的文章在上一章提到的活灵活现ListView相信大家已经熟练掌握,但是这些是满足不了程序猿的欲望的,哇咔咔~~NGUI中提供了两种Scroll View 一种是通过手指或鼠标滑动视图时移动平面物体,另一种则是直接移动摄像机,他们各有各的好处。但是NGUI提供的Scroll View很难实现类似Android 与 IOS 中的Scroll View 滚动相册的那种效果,至少MOMO没有很快的找到方法,不过程序猿的力量是伟大无穷的。虽然不能用它提供的API做出来,但是我们可以通过另外的手打巧妙的实现。不用担心MOMO会在这篇文章仔细向大家介绍如何实现自制Scroll View实现滚动相册。

        如下图所示,这是我们的工程页面,程序的实现原理是将相册在Unity3D世界中呈横向队列,摄像机固定的照射在第一个Item相册,当手指发生滑动事件时,计算向左滑动还是向右滑动,此时整体移动相册队列,而摄像机不动。为了让滑动效果更加好看我们需要使用插值计算滑动的时间,使滑动队列不是直接移动过去,而是以一定惯性移动过去。相册下方我们制作一个小白点用来记录当前滑动的位置,在做几个灰色的点表示队列一共的长度,滑动时下方的小白点也会跟随移动,这样就更想高级控件啦。当然小白点与小灰点是要根据item的数量居中显示的喔。

注解1:滚动相册一般可分为两种,第一种为数量已知的情况,第二种为数量未知的情况。因为第一种比较简单所以我们主要探讨第二种。

Script historyInit.cs: 该脚本用于相册队列的初始化工作。在这里初始化相册队列的数量,计算完毕让队列以横向线性的排列方式在Unity3D中。

Prefab item:每个相册的预设,我这里每个相册上还会有一些文字的描述,我需要动态的修改它们的内容。大家也可根据自己的情况制作自己的相册item。

Prefabhui:相册滚动时下方用来记录相册队列总数的灰色小点。

Prefabbai :相册滚动时下方用来记录当前滚动页面ID的白色小点。

Point :因为灰色、白色的点不能和相册队列在一个面板之上,否则会跟着相册队列移动而移动,所以这里将灰色白色的点放在两外一个面板之上。

注解2:这个面板上的4个item就是我们通过historyinit脚本初始化时动态生成赋值的、当界面发生触摸事件时,会整体移动该面板让自对象的相册队列跟随移动。

注解3:button0 – button3 是下方的Tabar。bai(Clone)表示白色的小点,hui(Clone)表示灰色的小点,它们的位置是需要根据滑动事件而改变的。

NGUI研究院之自制 Scroll View实现触摸滚动相册效果(四) - 雨松MOMO程序研究院 - 1

 

因为我们需要监听每一个Item的滑动事件,所以肯定要在每一个item预设之上绑定监听事件的脚本,如下图所示。

注解1:因为需要监听触摸滑动事件,所以肯定要绑定Box Collider组件,这个是NGUI的标准用法。

注解2:Move脚本用来监听向左滑动 向右滑动 点击事件。

注解3:这个就是每一个相册的item,在上图中挂在historyInit脚本之上。

NGUI研究院之自制 Scroll View实现触摸滚动相册效果(四) - 雨松MOMO程序研究院 - 2

 

historyInit.cs

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

public class historyInit  :MonoBehaviour
{

	//相册列表的每一个item
	public GameObject prefab;
	//灰色的小点
	public GameObject prefabhui;
	//白色的小点
	public GameObject prefabbai;
	//另外一个显示面板
	//用来放置灰色、白色小点
	public Transform  ponit;
	//白色小点的临时对象
	private GameObject bai;

	//链表,用来记录每一个相册中的一些用户信息
	List<UserData> users = new List<UserData>();
	//灰色、白色小点下方的起始位置。
	int start;

	void Start ()
	{
		//将当前面板对象储存在全局静态变量中
		Globe.ListPanel = gameObject;
		loadSQL ();
		initItem();
	}

	//以前是读取数据库
	//写例子程序就没必要使用数据库了
	//这里直接写4个死值,当然数量是灵活使用的

	void loadSQL ()
	{
		//表示一共向U3D世界中添加横向4个相册队列
		for(int i =0; i< 4; i ++)
		{
			//简单的对象储存
			string  name =  "momo =" + i;
			string  age =  "26 = " + i;
			string  height = "183 ="+ i;
			users.Add(new UserData(name,age,height));
		}

	}

	void initItem()
	{
		//因为下方灰色 白色的小点需要根据相册列表的数量来计算居中显示
		int size = users.Count;
		//乘以16表示计算所有小点加起来的宽度
		int length = (size - 1) * 16;
		//得到下方灰色 白色 小点的居中起始位置
		start = (-length) >>1;

		for(int i=0; i< size; i++)
		{
			//把每一个相册加入相册列表
			GameObject o  =(GameObject) Instantiate(prefab);
			//设置这个对象的父类为 当前面板
			o.transform.parent = transform;
			//设置相对父类的坐标,这些值可根据自己的情况而设定,
			//总之就是设置相册列表中每一个item的坐标,让它们横向的排列下来就行
			o.transform.localPosition = new Vector3(25 + i* 243,-145f,-86f);
			//设置相对父类的缩放
			o.transform.localScale= new Vector3(0.7349999f,0.66f,0.7349999f);

			//得到每一个user的信息
			UserData data = users[i];
			//遍历每一个相册对象的子组件,
			UILabel []label = o.GetComponentsInChildren<UILabel>();
			//拿到UILabel并且设置它们的数据
			label[0].text = data.age;
			label[1].text = data.height;
			label[2].text = data.name;

			//把每一个灰色小点加入3D世界
			GameObject hui  =(GameObject) Instantiate(prefabhui);
			//设置灰色小点的父类为另外一个面板
			hui.transform.parent = ponit;
			//设置每一个灰色小点的位置与缩放,总之让它们居中排列显示在相册列表下方。
			hui.transform.localPosition = new Vector3(start + i* 16,-120f,0f);
			hui.transform.localScale= new Vector3(8,8,1);

			//深度 因为是先在屏幕下方绘制4个灰色的小点, 然后在灰色上面绘制白色小点
			//表示当前的窗口ID 所以深度是为了设置白色小点在灰色小点之上绘制
			hui.GetComponent<UISprite>().depth = 0;
		}

		//下面的数据是把当前初始化的数据放在一个static类中
		//在Move脚本中就可以根据这里的数据来判断了。
		//滑动列表的长度
		Globe.list_count = size -1;
		//相册每一项的宽度
		Globe.list_offset = 243;
		//当前滑动的索引
		Globe.list_currentIndex = 0;
		//点击后打开的新游戏场景
		Globe.list_go_name= "LoadScene";

		//把白色小点也加载在3D世界中
		bai  =(GameObject) Instantiate(prefabbai);
		//设置它的深度高于 灰色小点,让白色小点显示在灰色小点之上
		bai.GetComponent<UISprite>().depth = 1;
		//设置白色小点的位置
		setBaiPos();
	}

	void Update()
	{
		//当用户滑动界面
		//在Update方法中更新
		//当前白色小点的位置
		setBaiPos();
	}

	void setBaiPos()
	{
		//Globe.list_currentIndex 就是当前界面的ID
		//根据ID 重新计算白色小点的位置
		bai.transform.parent = ponit;
		bai.transform.localPosition = new Vector3(start + Globe.list_currentIndex* 16,-120f,-10f);
		bai.transform.localScale= new Vector3(8,8,1);

	}
}

 

如下图所示,我们可以看出Tabbar 、 下方记录界面的灰色、白色小点、摄像机  它们是不会发生改变的。唯一改变的就是面板之上的相册队列。为了让滑动界面的效果更加连贯,我们需要以插值的形式来计算真个相册面板的坐标。

 

NGUI研究院之自制 Scroll View实现触摸滚动相册效果(四) - 雨松MOMO程序研究院 - 3

 

触摸的事件全都写在Move.cs脚本中。

using UnityEngine;
using System.Collections;

public class Move : MonoBehaviour {

	//是否触摸
	bool isTouch = false;
	//是否向左滑动
	bool isRight = false;
	//是否向右滑动
	bool isLeft = false;
	//是否正在滑动中
	bool isOnDrag = false;

	//滑动中事件
	void OnDrag (Vector2 delta)
	{
		//为了避免事件冲突
		//这里只判断一个滑动的事件
		if(!isTouch)
		{
			if(delta.x > 0.5)
			{
				//向左滑动
				isRight = true;
				isOnDrag = true;
			}else if(delta.x < -0.5)
			{
				//向右滑动
				isLeft = true;
				isOnDrag = true;
			}
			isTouch = true;
		}
	}

	//滑动后松手调用OnPress事件
	void OnPress()
	{
		//重新计算当前界面的ID
		if(Globe.list_currentIndex < Globe.list_count && isLeft)
		{
			Globe.list_currentIndex++;
		}

		if(Globe.list_currentIndex >0 && isRight)
		{
			Globe.list_currentIndex--;
		}

		//表示一次滑动事件结束
		isTouch = false;
		isLeft = false;
		isRight = false;
	}

	void Update()
	{
		//这个方法就是本节内容的核心
		//Globe.ListPanel 这个对象是我们在historyInit脚本中得到的,它用来当面相册面板对象使用插值,让面板有惯性的滑动。

		//Vector3.Lerp() 这个是一个插值的方法, 参数1 表示开始的位置 参数2 表示结束的位置  参数3 表示一共所用的时间, 在Time.deltaTime * 5 时间以内 Update每一帧中得到插值当前的数值,只到插值结束

		//-(Globe.list_currentIndex * Globe.list_offset) 得到当前需要滑动的目标点。
		//请大家仔细看这个方法。

		Globe.ListPanel.transform.localPosition =Vector3.Lerp(Globe.ListPanel.transform.localPosition, new Vector3(-(Globe.list_currentIndex * Globe.list_offset),0,0), Time.deltaTime * 5);
	}

	void OnClick ()
	{
		//当点击某个item时进入这里

		if(!isOnDrag)
		{
			//如果不是在拖动中 进入另一个场景
			Application.LoadLevel(Globe.list_go_name);
		}
		else
		{
			//否则等待用户重新发生触摸事件
			isOnDrag = false;
		}
	}
}

 

还有两个辅助的类,我也贴出来。

UserData.cs

using UnityEngine;
using System.Collections;

public class UserData{

	public string name;
	public string age;
	public string height;
	public string hand;

	public UserData(string _name, string _age,string _height)
	{
		age = _age;
		height = _height;
		name = _name;
	}

}

 

Globe.cs 这个静态类用来共享记录多个脚本甚至多个场景所需的数据。

using System.Collections.Generic;
public class Globe
{

	 public static GameObject ListPanel;
	 public static int list_count;
	 public static int list_currentIndex;
	 public static int list_offset;
	 public static string list_go_name;

}

 

写到这里,本篇博文就写的差不多了。这篇文章我是用NGUI来实现的触摸滚动效果,仔细想想,其实不用NGUI完全也能实现这样的效果。在脚本中完全可以通过射线 以及 触摸的时间去计算当前用户操作的事件,这篇文章里的工程最后我是打包在Android上面的,效果挺不错,滑动的效果图不好截取我也不截取了,主要还是文章的书写内容,希望大家学习愉快,雨松MOMO祝大家晚安,哇咔咔,啦啦啦。

最后编辑:
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!

NGUI研究院之自制 Scroll View实现触摸滚动相册效果(四)》有 52 条评论

  1. 哟哟哟说:

    雨松大神,插值方法的第二个参数的y坐标为啥是0呢

  2. 可以看看你的源码吗306891402@qq.com,谢谢

  3. 杨静轩说:

    在Move.cs文件中的OnPress函数结尾应该加句isOnDrag = false;要不在一次滑动事件之后得点击第二下才能响应按键事件了

  4. 橘南小巷゛说:

    雨松大大 这个项目有源文件么?希望带上源文件

  5. MTT说:

    雨松老师 有没有 2dtookit 的呢?

  6. 求学说:

    请问item(clone)和hui(clone)是什么啊?他们是什么类型的GameObject?而且希望能求到工程源文件,麻烦发到214137022@qq.com,谢谢了。。。

  7. 雨松老师讲的真好,有些地方有点不懂。求工程源文件呀。我的邮箱是365206763@qq.com

  8. zm说:

    为什么 我的OnDrag 和 OnPress 没有反应

  9. MENG说:

    我也购买了博主大人的《unity3d 游戏开发》,真是入门的好教材,谢谢博主大人!另外博主大人这篇ScrollView的完整工程能发给我看一下吗?我的邮箱是m_iSnow@hotmail.com再次感谢!

  10. peng说:

    请问如果scrollview的范围设置得比较大,但是里面的内容比较少,就会造成内容显示在区域中央,有没有方法当内容不够的时候,让内容显示在顶部?

    • twntwn说:

      修改UIPanelpublic bool isTop=false;public Vector3 CalculateConstrainOffset (Vector2 min, Vector2 max) { float offsetX = clipRange.z * 0.5f; float offsetY = clipRange.w * 0.5f; Vector2 minRect = new Vector2(min.x, min.y); Vector2 maxRect = new Vector2(max.x, max.y); Vector2 minArea = new Vector2(clipRange.x – offsetX, clipRange.y – offsetY); Vector2 maxArea = new Vector2(clipRange.x + offsetX, clipRange.y + offsetY); if (clipping == UIDrawCall.Clipping.SoftClip) { minArea.x += clipSoftness.x; minArea.y += clipSoftness.y; maxArea.x -= clipSoftness.x; maxArea.y -= clipSoftness.y; } Vector2 v2 =NGUIMath.ConstrainRect(minRect, maxRect, minArea, maxArea); if(isTop&&(max.y-min.y)

    • twntwn说:

      修改UIPanelpublic bool isTop=false;public Vector3 CalculateConstrainOffset (Vector2 min, Vector2 max){float offsetX = clipRange.z * 0.5f;float offsetY = clipRange.w * 0.5f;Vector2 minRect = new Vector2(min.x, min.y);Vector2 maxRect = new Vector2(max.x, max.y);Vector2 minArea = new Vector2(clipRange.x – offsetX, clipRange.y – offsetY);Vector2 maxArea = new Vector2(clipRange.x offsetX, clipRange.y offsetY);if (clipping == UIDrawCall.Clipping.SoftClip){minArea.x = clipSoftness.x;minArea.y = clipSoftness.y;maxArea.x -= clipSoftness.x;maxArea.y -= clipSoftness.y;}Vector2 v2 =NGUIMath.ConstrainRect(minRect, maxRect, minArea, maxArea);if(isTop&&(max.y-min.y)

  11. 程宝说:

    老师,能给个源代码么,谢谢啦~我的邮箱是244157539@qq.com

  12. VIP说:

    看了你的书,但觉得自己还处于一个小小有基础的人,这个能给个源代码吗,谢谢谢。我的邮箱1975209805@qq.com

  13. 史金亮说:

    雨松可以给下这个的源码吗?看你做的挺好看的。资源也是难求的。呵呵。谢谢。sjl_leaf@163.com

留下一个回复

你的email不会被公开。 必填项已用 * 标注。