一、 实现原理
NGUI实现拖拽滑动功能组件是ScrollView脚本。实现拖拽刷新与获取更多的原理修改ScrollView的源代码,NGUI提供了检测拖拽内容的边与Panel边缘距离的方法,通过这个方法判断何时调用刷新、获取更多等方法。ScrollView为UI控制脚本,只控制何时刷新,具体的刷新逻辑并不能在里面实现,需要使用Action实现外部的调用,具体看代码。
二、 实现方法
修改ScrollView源代码不能影响其他不需要拖拽刷新的地方的功能,所以,先添加两个标志位,用于控制该ScrollView是否具有拖拽刷新和获取更多的功能。
/// <summary>
/// 控制该ScrollView是否有拖拽刷新功能
/// </summary>
public bool CanDragToRefresh = false;
/// <summary>
///是否有拖拽获取更多功能s
/// </summary>
public bool CanDragToGetMore = false;
这两个量声明为public,可在监测界面勾选,注意是在Debug面板而不是Normal面板。
public UIViewDragToReferesh DragToRefresh; //控制刷新具体操作的类
public System.Action RefreshAction; //拖拽刷新的方法
public System.Action GetMoreAction; //拖拽获得更多的方法
private float refreshStartTime = 0f; //刷新开始的时间
private float refreshEndTime=0f; //刷新结束的时回调的时间
private float refreshDeltaTime=1f; //发送请求的最小间隔时间
private bool isCanRefresh = true; //是否可以再次刷新
private bool isCanGetMore = true; //是否可以再次获取更多
这里是需要声明的其他的一些变量。
ScrollView中的RestrictWithinBounds方法是控制滑动内容超出Panel边界后返回到边界的方法。“Restrict the scroll view's contents to be within the scroll view's bounds.”这是官方给出的注释。该方法在鼠标抬起之后到滑动未停止之前每一帧调用,也就是说是在滑动一次之后依靠惯性继续滑动一段距离的每一帧调用。调用的时候会去判断是否超出了Panel的边缘,未超出的话会直接Return false,超出的话会执行弹回等操作。
在ScrollView中添加的部分:
void Update()
{
if (CanDragToRefresh || CanDragToGetMore)
{
Bounds b = bounds;
Vector3 constraint = mPanel.CalculateConstrainOffset(b.min, b.max);
if(constraint.sqrMagnitude>30f)
{
DragToRefresh.gameObject.SetActive(true);
}
else
{
DragToRefresh.gameObject.SetActive(false);
}
if(CanDragToRefresh && mPressed && constraint.y>100f)
{
DragToRefresh.ChangeWhenDrag();
//ChangeWhenDragAction();
return;
}
else if(CanDragToRefresh && mPressed && constraint.y<100 && constraint.y>1)
{
DragToRefresh.ChangeWhenDragReturn();
//ChangeWhenDragReturnAction();
return;
}
if(CanDragToRefresh && constraint.y<=1f)
{
DragToRefresh.FinishRefresh();
// if(FinishRefreshAction!=null)
// FinishRefreshAction();
}
if(CanDragToGetMore && mPressed && constraint.y<-100)
{
DragToRefresh.ChangeWhenGetMore();
//ChangeWhenDragAction();
return;
}
else if(CanDragToGetMore && mPressed && constraint.y<100 && constraint.y<-1)
{
DragToRefresh.ChangeWhenGetMoreReturn();
//ChangeWhenDragReturnAction();
return;
}
}
}
/// <summary>
/// 拖拽刷新获得数据后的回调方法
/// </summary>
public void CallBackAfterRecvRefreshData()
{
if (!CanDragToRefresh && !CanDragToGetMore) return;
DragToRefresh.StopRefreshLoad ();
refreshEndTime = Time.realtimeSinceStartup;
float deltaTime = refreshEndTime - refreshStartTime;
if (deltaTime > refreshDeltaTime)
{
SpringPanelBack();
return;
}
StartCoroutine(WaitToBack(refreshDeltaTime-deltaTime));
}
public void CallBackAfterRecvGetMoreData()
{
if (!CanDragToRefresh && !CanDragToGetMore) return;
if(DragToRefresh!=null)
DragToRefresh.StopGetMoreLoad ();
refreshEndTime = Time.realtimeSinceStartup;
float deltaTime = refreshEndTime - refreshStartTime;
if (deltaTime > refreshDeltaTime)
{
SpringPanelBack();
return;
}
StartCoroutine(WaitToGetMoreAgain(refreshDeltaTime-deltaTime));
}
public void CallBackFromTopAfterRecvGetMoreData()
{
if (!CanDragToRefresh && !CanDragToGetMore) return;
if(DragToRefresh!=null)
DragToRefresh.StopGetMoreLoadFromTop ();
refreshEndTime = Time.realtimeSinceStartup;
float deltaTime = refreshEndTime - refreshStartTime;
if (deltaTime > refreshDeltaTime)
{
isCanGetMore = true;
return;
}
StartCoroutine(WaitToGetMoreAgain(refreshDeltaTime-deltaTime));
}
IEnumerator WaitToBack( float t )
{
yield return new WaitForSeconds (Mathf.Abs(t));
SpringPanelBack();
}
IEnumerator WaitToGetMoreAgain(float t)
{
yield return new WaitForSeconds (Mathf.Abs(t));
SpringPanelBack();
}
void SpringPanelBack()
{
isCanGetMore = true;
isCanRefresh = true;
Bounds b = bounds;
Vector3 constraint = mPanel.CalculateConstrainOffset(b.min, b.max);
if (CanDragToRefresh && constraint.sqrMagnitude > 0.1f)
{
if (dragEffect == DragEffect.MomentumAndSpring)
{
Vector3 pos = mTrans.localPosition + constraint;
pos.x = Mathf.Round (pos.x);
pos.y = Mathf.Round (pos.y);
SpringPanel.Begin (mPanel.gameObject, pos, 13f).strength = 8f;
}
}
}
/// <summary>
/// Restrict the scroll view's contents to be within the scroll view's bounds.
/// </summary>
public bool RestrictWithinBounds (bool instant, bool horizontal, bool vertical)
{
if (mPanel == null) return false;
Bounds b = bounds;
//这个向量看做是滑动内容与Panel界面的差值,未超出边界的时候为0
Vector3 constraint = mPanel.CalculateConstrainOffset(b.min, b.max);
if (!horizontal) constraint.x = 0f;
if (!vertical) constraint.y = 0f;
//当constraint向量的模大于0.1,也就是内容边缘离开Panel边缘的时候
if (constraint.sqrMagnitude > 0.1f)
{
//如果有弹回效果
if (!instant && dragEffect == DragEffect.MomentumAndSpring)
{
// Spring back into place弹回距离
Vector3 pos = mTrans.localPosition + constraint;
//如果具有拖拽刷新或者获取更多功能
if (CanDragToRefresh || CanDragToGetMore)
{
pos.x = Mathf.Round (pos.x);
pos.y = Mathf.Round (pos.y);
//如果顶部超出边缘100之后
if (CanDragToRefresh && constraint.y > 100f)
{
if (isCanRefresh)
{
refreshStartTime = Time.realtimeSinceStartup;
RefreshAction ();
isCanRefresh = false;
}
SpringPanel.Begin (mPanel.gameObject, new Vector3 (pos.x, pos.y - 100, pos.z), 13f).strength = 8f;
return true;
}
//如果底部超出边缘100之后
else if (CanDragToGetMore && constraint.y < -100f)
{
if (isCanGetMore)
{
refreshStartTime = Time.realtimeSinceStartup;
GetMoreAction ();
isCanGetMore = false;
}
SpringPanel.Begin (mPanel.gameObject, new Vector3 (pos.x, pos.y + 105, pos.z), 13f).strength = 8f;
return true;
}
}
SpringPanel.Begin (mPanel.gameObject, pos, 13f).strength = 8f;
return true;
}
else
{
// Jump back into place
MoveRelative (constraint);
// Clear the momentum in the constrained direction
if (Mathf.Abs (constraint.x) > 0.01f)
mMomentum.x = 0;
if (Mathf.Abs (constraint.y) > 0.01f)
mMomentum.y = 0;
if (Mathf.Abs (constraint.z) > 0.01f)
mMomentum.z = 0;
mScroll = 0f;
}
return true;
}
return false;
}
控制刷新效果的脚本:
using UnityEngine;
using System.Collections;
using E;
public class UIViewDragToReferesh : MonoBehaviour
{
public Transform upAndDownSprite;
public UISprite rotateSprite;
public UILabel refreshLabel;
public UILabel lastRefreshLabel;
public Transform getMoreUpAndDown;
public UISprite getMoreRotate;
public UILabel getMoreLabel;
[HideInInspector]
public bool IsLoading = false;
private bool IsGetMoreLoading=false;
//当拖拽过程中超过临界点之后调用
public void ChangeWhenDrag()
{
refreshLabel.text = "松开刷新";
upAndDownSprite.rotation = Quaternion.Lerp(upAndDownSprite.rotation, Quaternion.Euler(new Vector3(0f,0f,180f)) , 10f*Time.deltaTime);
}
public void ChangeWhenGetMore()
{
getMoreLabel.text="松开刷新";
getMoreUpAndDown.rotation = Quaternion.Lerp (getMoreUpAndDown.rotation, Quaternion.Euler (new Vector3 (0f, 0f, 0f)), 10f * Time.deltaTime);
}
//服务器返回数据后
public void StopRefreshLoad()
{
refreshLabel.text = "刷新成功";
IsLoading = false;
rotateSprite.gameObject.SetActive (false);
}
public void StopGetMoreLoad()
{
if (UICtrlGetMyMessagepanel.Instance.refreshResvers)
{
getMoreUpAndDown.rotation = Quaternion.Euler(new Vector3(0f,0f,9f));
getMoreLabel.text = "下拉刷新";
IsGetMoreLoading = false;
getMoreRotate.gameObject.SetActive (false);
getMoreUpAndDown.gameObject.SetActive (true);
}
else
{
getMoreLabel.text = "上拉刷新";
IsGetMoreLoading = false;
getMoreRotate.gameObject.SetActive (false);
getMoreUpAndDown.gameObject.SetActive (true);
getMoreUpAndDown.rotation = Quaternion.Euler(new Vector3(0f,0f,180f));
}
}
public void StopGetMoreLoadFromTop()
{
getMoreUpAndDown.rotation = Quaternion.Euler(new Vector3(0f,0f,0));
getMoreLabel.text = "下拉刷新";
IsGetMoreLoading = false;
getMoreRotate.gameObject.SetActive (false);
getMoreUpAndDown.gameObject.SetActive (true);
}
//普通刷新,在外部点击按钮等非拖拽刷新的时候调用
public void NormalRefresh(string s)
{
refreshLabel.text="下拉刷新";
lastRefreshLabel.text = CalculateLastRefreshTime (s);
}
//拖拽刷新的时候调用
public void DragToRefresh(string s)
{
if (refreshLabel == null) return;
if (rotateSprite == null) return;
if (upAndDownSprite == null) return;
if (lastRefreshLabel == null) return;
refreshLabel.text="正在刷新";
rotateSprite.gameObject.SetActive (true);
IsLoading = true;
upAndDownSprite.gameObject.SetActive (false);
lastRefreshLabel.text = CalculateLastRefreshTime (s);
}
public void DragToGetMore()
{
getMoreLabel.text="正在刷新";
getMoreRotate.gameObject.SetActive (true);
IsGetMoreLoading = true;
getMoreUpAndDown.gameObject.SetActive (false);
}
//一次刷新完成之后,重置操作
public void FinishRefresh()
{
refreshLabel.text = "下拉刷新";
upAndDownSprite.gameObject.SetActive (true);
upAndDownSprite.rotation = Quaternion.identity;
rotateSprite.gameObject.SetActive (false);
}
//拖拽内容回到临界点以上的时候调用
public void ChangeWhenDragReturn()
{/*
if (AppManager.Instance.InformationData.IsNoMore ()) {
NoMoreToGet ();
} else {
*/
refreshLabel.text="下拉刷新";
upAndDownSprite.rotation = Quaternion.Lerp(upAndDownSprite.rotation, Quaternion.Euler(new Vector3(0f,0f,0f)) , 10f*Time.deltaTime);
//}
}
public void ChangeWhenGetMoreReturn()
{
if (UICtrlGetMyMessagepanel.Instance != null)
{
if (UICtrlGetMyMessagepanel.Instance.refreshResvers) {
getMoreLabel.text="下拉刷新";
getMoreUpAndDown.rotation = Quaternion.Lerp(getMoreUpAndDown.rotation, Quaternion.Euler(new Vector3(0f,0f,0f)) , 10f*Time.deltaTime);
}
return;
}
getMoreLabel.text="上拉刷新";
getMoreUpAndDown.rotation = Quaternion.Lerp(getMoreUpAndDown.rotation, Quaternion.Euler(new Vector3(0f,0f,180f)) , 10f*Time.deltaTime);
}
//没有更多内容
public void NoMoreToGet()
{
getMoreUpAndDown.gameObject.SetActive (false);
getMoreRotate.gameObject.SetActive (false);
getMoreLabel.text = "没有更多内容";
}
//计算上次刷新的时间
private string CalculateLastRefreshTime(string r)
{
System.DateTime refreshDate = System.DateTime.Now;
string lastTimeString = PlayerPrefs.GetString ("lastRefreshTime"+r);
if (lastTimeString == "")
{
PlayerPrefs.SetString ("lastRefreshTime"+r, refreshDate.ToString ());
return null;
}
else
{
PlayerPrefs.SetString ("lastRefreshTime"+r, refreshDate.ToString ());
System.DateTime lastRefreshTime= System.DateTime.Parse(lastTimeString);
System.TimeSpan refreshDeltaTime = refreshDate - lastRefreshTime;
if(refreshDeltaTime.Minutes<=0)
{
return "上次刷新时间:"+"刚刚";
}
else if(refreshDeltaTime.Hours < 1)
{
return "上次刷新时间:" + refreshDeltaTime.Minutes.ToString() + "分钟以前";
}
else if(refreshDeltaTime.Hours >= 1 && refreshDeltaTime.Hours<24)
{
return "上次刷新时间:" + refreshDate.ToString("HH:mm");
}
else
{
return "上次刷新时间:" + refreshDate.ToString("yy-MM-dd HH:mm");
}
}
}
void LateUpdate()
{
if(IsLoading)
rotateSprite.transform.Rotate (Vector3.forward * 50 * Time.deltaTime, Space.World);
if (IsGetMoreLoading)
getMoreRotate.transform.Rotate (Vector3.forward * 50 * Time.deltaTime, Space.World);
}
}
现在功能有了,就要使用它了
在控制脚本里,需要绑定刷新方法,因为在ScrollView里用了委托,
ScrollView.RefreshAction = DragRefresh;
ScrollView.GetMoreAction = OnMoreHandler;
private void UpdateHasMore()
{
if (AppManager.Instance.TopicData.HasMore)
{
_cachedView.ScrollView.CanDragToGetMore=true;
}
else
{
_cachedView.ScrollView.CanDragToGetMore=false;
_cachedView.ScrollView.DragToRefresh.NoMoreToGet();
}
}
public void OnMoreHandler()
{
AppManager.Instance.TopicData.SendGetMoreTopicsRequest (StopGetMoreLoad);
_cachedView.ScrollView.DragToRefresh.DragToGetMore ();
}
/// <summary>
/// update dongtai list.
/// </summary>
public void OnRefreshHandler()
{
AppManager.Instance.TopicData.SendGetNewestTopicsRequest (StopRefreshLoad);
_cachedView.ScrollView.DragToRefresh.NormalRefresh ("_topic");
}
public void DragRefresh()
{
AppManager.Instance.TopicData.SendGetNewestTopicsRequest (StopRefreshLoad);
_cachedView.ScrollView.DragToRefresh.DragToRefresh ("_topic");
}
/// <summary>
/// callback of refresh
/// </summary>
private void StopRefreshLoad()
{
_cachedView.ScrollView.CallBackAfterRecvRefreshData ();
}
/// <summary>
/// callback of getmore
/// </summary>
private void StopGetMoreLoad()
{
_cachedView.ScrollView.CallBackAfterRecvGetMoreData ();
UpdateHasMore ();
}
在OnRefreshHandler 方法中发送刷新请求,是下拉刷新,请求的是最新的数据,服务器会返回来新的数据,在OnMoreHandler中,是发送获取更多的请求,跟服务器协议好,发的什么参数服务器才知道是要返回更多的数据,如果返回的数据list很多,可以协议好每次请求返回一定数量的数据,请求更多的时候在已经返回的数据里添加新的数据,一起呈现。
每次数据更新的时候用到ScrollView.UpdatePosition()方法,可以更新到当前位置。