最近在帮同事做一点项目。分配给我了人员位置热力图的功能块。其中一块需要时间段的的选取。效果如下:
看到这个瞬间让我想到很久之前画折线图那块,我们通过给Image新的顶点来绘制我们需要的图形。所以我们需要在Canvas下创建一个空物体并挂上我们自己创建的脚本MyDoubleSlider,由于我们看效果图可以看到一个横条是被分成了12份,所以根据UI我们自己设定空物体的width=960,heigt=30代码如下:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System;
namespace Unite
{
public class MyDoubleSlider : MaskableGraphic
{
//开始时间
public DateTime StartTime { get { DateTime dateTime = DateTime.Now.Date; return dateTime.Add(GetTime(leftTran.localPosition.x));}}
//结束时间
public DateTime EndTime { get { DateTime dateTime = DateTime.Now.Date; return dateTime.Add(GetTime(rightTran.localPosition.x)); } }
public string StartTimeStr
{
get {return StartTime.ToString("yyyy-MM-dd HH:mm:ss"); }
}
public string EndTimeStr
{
get { return EndTime.ToString("yyyy-MM-dd HH:mm:ss"); }
}
[SerializeField] [Tooltip("是否是被分为整数段 如果是 勾选")]
private bool IsInteger = true;
private Transform leftTran, rightTran;
float width, height,InitPosX;
float uniteLength;
protected override void Awake()
{
base.Awake();
leftTran = transform.Find("Left");
rightTran = transform.Find("Right");
}
void Start()
{
width = rectTransform.rect.width;
height = rectTransform.rect.size.y;
InitPosX = transform.localPosition.x;
uniteLength = width / 12;
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
Debug.Log(StartTime.ToString("yyyy-MM-dd HH:mm:ss")+":"+EndTime.ToString("yyyy-MM-dd HH:mm:ss"));
}
TimeSpan GetTime(float xpos)
{
float offset = xpos- InitPosX;
if (IsInteger)
{
int count = Mathf.RoundToInt(offset / uniteLength);
return new TimeSpan(0,12+(int)count*2,0,0);
}
else
{
float count = offset / uniteLength;
count = (float)Math.Round(12 + count * 2, 2);
int inter = (int)count;
float floater = count - inter;
int totalseconds = (int)(floater * 3600);
int minutes = (int)totalseconds / 60;
int second = totalseconds % 60;
return new TimeSpan(0, inter, minutes, second);
}
}
///
/// 限制一下移动范围 以及移动固定单位时的判定
///
///
///
///
public Vector2 Clamp(Vector2 v, Transform tran)
{
v.y = 0;
if (tran.Equals(leftTran))
{
v.x = Mathf.Clamp(v.x, -width / 2+ InitPosX, rightTran.localPosition.x);
if (IsInteger)
{
float offset = v.x - leftTran.localPosition.x;
offset = Mathf.RoundToInt(offset / uniteLength) * uniteLength;
v.x = leftTran.localPosition.x + offset;
}
}
else
{
v.x = Mathf.Clamp(v.x, leftTran.localPosition.x, width / 2+ InitPosX);
if (IsInteger)
{
float offset = v.x - rightTran.localPosition.x;
offset = Mathf.RoundToInt(offset / uniteLength) * uniteLength;
v.x = rightTran.localPosition.x + offset;
}
}
return v;
}
///
/// 拖动两端的时候调用这个脚本
///
public void OnDrag()
{
SetVerticesDirty();//更改顶点 会调用OnPopulateMesh函数
}
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
UIVertex[] verts = new UIVertex[4];
verts[0].position = new Vector3(leftTran.localPosition.x, -height / 2);
verts[0].color = color;
verts[0].uv0 = Vector2.zero;
verts[1].position = new Vector3(leftTran.localPosition.x, height / 2);
verts[1].color = color;
verts[1].uv0 = Vector2.zero;
verts[2].position = new Vector3(rightTran.localPosition.x, height / 2);
verts[2].color = color;
verts[2].uv0 = Vector2.zero;
verts[3].position = new Vector3(rightTran.localPosition.x, -height / 2);
verts[3].color = color;
verts[3].uv0 = Vector2.zero;
vh.AddUIVertexQuad(verts);
}
}
}
同时需要在此物提下创建两个新的Image,并分别命名为Left,Right。分别设置Left物体的pivot为(1,0.5f),Right的pivot为(0,0.5f)。这样是为了能让两个物体在边界位置正好拼接在一起而不是覆盖。保证点击不被遮挡。Left参数如下:
在创建一个专门控制左右滑动的脚本MySliderBtn,分别挂载在Left,Right物体上,代码如下:
public class MySliderBtn : MonoBehaviour , IDragHandler
{
RectTransform rectTran;
MyDoubleSlider myslider;
void Awake()
{
rectTran = transform.parent.GetComponent();
myslider = rectTran.GetComponent();
}
///
/// Object被拖动的时候
///
///
public void OnDrag(PointerEventData eventData)
{
Vector2 pos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTran.GetComponent(),
Input.mousePosition, null, out pos);
pos = myslider.Clamp(pos,transform);
transform.localPosition = pos;
myslider.OnDrag();
}
}
整个功能小块的布局如下图:
这里注意下:OnPopulateMesh函数(专门用于绘制UI组件里的函数,我们只需要传入顶点数据即可)的调用需要更改ui元素的顶点坐标,官方文档并没有讲怎样会调用(Callback function when a UI element needs to generate vertices.)。一顿搜索后找到当调用SetVerticesDirty()函数会去回调OnPopulateMesh。之前写折线图的时候一直以为改变RectTransform的大小就可以刷新OnPopulateMesh函数调用,其实是不正确的。
人员位置热力图已经做完了,但是因为现在都是随机的假数据(噪点太多)以及颜色图谱没有确定,所以做出来的效果不美观。等真实数据来了以后如果效果还不错,就会把位置热力图的实现过程更新上来。
项目工程地址:源码地址。