using System;
|
using Android.Views;
|
using Android.Content;
|
using Android.Widget;
|
using Android.Graphics.Drawables;
|
|
|
namespace Shared
|
{
|
//已经全面检查了代码
|
/// <summary>
|
/// 位置布局
|
/// </summary>
|
public class HorizontalPages : ViewGroup
|
{
|
/// <summary>
|
/// 视图高度
|
/// </summary>
|
/// <value>The height.</value>
|
public override int Height
|
{
|
get
|
{
|
return base.Height;
|
}
|
set
|
{
|
base.Height = value;
|
if (!IsCanRefresh)
|
{
|
return;
|
}
|
var layoutParameters = realViewGroup.LayoutParameters;
|
layoutParameters.Height = Height;
|
realViewGroup.LayoutParameters = layoutParameters;
|
}
|
}
|
|
/// <summary>
|
/// 视图宽度
|
/// </summary>
|
/// <value>The width.</value>
|
public override int Width
|
{
|
get
|
{
|
return base.Width;
|
}
|
set
|
{
|
base.Width = value;
|
if (!IsCanRefresh)
|
{
|
return;
|
}
|
var layoutParameters = realViewGroup.LayoutParameters;
|
layoutParameters.Width = Width;
|
realViewGroup.LayoutParameters = layoutParameters;
|
}
|
}
|
/// <summary>
|
/// 页面变化事件
|
/// </summary>
|
public Action<HorizontalPages, int> PageChange;
|
/// <summary>
|
/// 构造函数
|
/// </summary>
|
public HorizontalPages()
|
{
|
viewGroup = new AndroidFrameLayout(Application.Activity, null);
|
realViewGroup = new InnerHorizontalPages(Application.Activity, this) { };
|
viewGroup.AddView(realViewGroup);
|
}
|
|
int pageIndex;
|
/// <summary>
|
/// 设置或者获取当前的界面索引
|
/// </summary>
|
/// <value>The index of the page.</value>
|
public int PageIndex
|
{
|
get
|
{
|
return (realViewGroup as InnerHorizontalPages).CurrentScreen;
|
}
|
set
|
{
|
if (value < 0 || ChildrenCount < value)
|
{
|
return;
|
}
|
pageIndex = value;
|
if (!IsCanRefresh)
|
{
|
return;
|
}
|
|
(realViewGroup as InnerHorizontalPages).SetSelection(value);
|
}
|
}
|
|
/// <summary>
|
/// 是否允许滑动
|
/// </summary>
|
/// <value><c>true</c> if scroll enabled; otherwise, <c>false</c>.</value>
|
public bool ScrollEnabled
|
{
|
get
|
{
|
return (realViewGroup as InnerHorizontalPages).ScrollEnabled;
|
}
|
set
|
{
|
(realViewGroup as InnerHorizontalPages).ScrollEnabled = value;
|
}
|
}
|
|
|
|
/// <summary>
|
/// 增加子控件
|
/// </summary>
|
/// <param name="view">View.</param>
|
public override void AddChidren(View view)
|
{
|
view.Parent = this;
|
viewList.Add(view);
|
realViewGroup.AddView(view.AndroidView);
|
|
if (!IsCanRefresh)
|
{
|
return;
|
}
|
|
view.Refresh();
|
|
if (view is ViewGroup)
|
{
|
var tempViewGroup = (ViewGroup)view;
|
for (int i = 0; i < tempViewGroup.ChildrenCount; i++)
|
{
|
tempViewGroup.GetChildren(i).Refresh();
|
}
|
}
|
}
|
|
/// <summary>
|
/// 移除当前控件
|
/// </summary>
|
/// <param name="view">View.</param>
|
internal override void Remove(View view)
|
{
|
if (view == null)
|
{
|
return;
|
}
|
var index = viewList.FindIndex(obj => obj == view);
|
if (index < 0)
|
{
|
return;
|
}
|
|
viewList.Remove(view);
|
(realViewGroup as InnerHorizontalPages).RemoveViewAt(index);
|
view.Parent = null;
|
}
|
|
/// <summary>
|
/// 移除所有的控件
|
/// </summary>
|
public override void RemoveAll()
|
{
|
while (0 < viewList.Count)
|
{
|
GetChildren(0)?.RemoveFromParent();
|
}
|
(realViewGroup as InnerHorizontalPages).SetSelection(0);
|
}
|
|
/// <summary>
|
/// 根据索引移除控件
|
/// </summary>
|
/// <param name="index">Index.</param>
|
public override void RemoveAt(int index)
|
{
|
if (viewList.Count - 1 < index || index < 0)
|
{
|
return;
|
}
|
GetChildren(index)?.RemoveFromParent();
|
}
|
|
|
/// <summary>
|
/// page的外边距
|
/// </summary>
|
public int RowPadding
|
{
|
get
|
{
|
return (realViewGroup as InnerHorizontalPages).Padding;
|
}
|
set
|
{
|
(realViewGroup as InnerHorizontalPages).Padding = value;
|
|
}
|
}
|
|
/// <summary>
|
/// 两个Page之间的距离
|
/// </summary>
|
public int PagePadding
|
{
|
get
|
{
|
return (realViewGroup as InnerHorizontalPages).Padding - (realViewGroup as InnerHorizontalPages).PagePadding;
|
}
|
set
|
{
|
(realViewGroup as InnerHorizontalPages).PagePadding = (realViewGroup as InnerHorizontalPages).Padding - value;
|
|
}
|
}
|
}
|
|
public class InnerHorizontalPages : Android.Widget.FrameLayout
|
{
|
public bool ScrollEnabled = true;
|
static int snapVelocity = 1000;
|
static int invalidScreen = -1;
|
static int touchStateRest = 0;
|
static int touchStateScrolling = 1;
|
|
/// <summary>
|
/// 界面之间的边距
|
/// </summary>
|
public int Padding = 100;
|
public int PagePadding = 50;
|
Scroller scroller;
|
VelocityTracker mVelocityTracker = VelocityTracker.Obtain();
|
int touchState = touchStateRest;
|
float lastMotionX;
|
float lastMotionY;
|
int mTouchSlop;
|
int mMaximumVelocity;
|
internal int CurrentScreen;
|
|
public override void RemoveViewAt(int index)
|
{
|
if (index == ChildCount - 1) {
|
CurrentScreen--;
|
if (CurrentScreen < 0) {
|
CurrentScreen = 0;
|
}
|
}
|
base.RemoveViewAt(index);
|
|
//刷新
|
snapToDestination();
|
}
|
|
Shared.HorizontalPages horizontalPages;
|
public InnerHorizontalPages(Context context, Shared.HorizontalPages view) : base(context)
|
{
|
horizontalPages = view;
|
init();
|
}
|
|
/// <summary>
|
/// 重写这个是为了方便部局,在每个界面之间都多加了一个控件包住
|
/// </summary>
|
/// <param name="child"></param>
|
public override void AddView(Android.Views.View child)
|
{
|
var innerView = new Android.Widget.FrameLayout(Shared.Application.Activity);
|
innerView.SetPadding(Padding, 0, Padding, 0);
|
if (1 <= ChildCount)
|
{
|
innerView.SetX(LayoutParameters.Width * ChildCount - Padding - PagePadding);
|
}
|
base.AddView(innerView, new Android.Widget.FrameLayout.LayoutParams(LayoutParameters.Width, LayoutParameters.Height) { });
|
innerView.AddView(child);
|
}
|
|
void init()
|
{
|
scroller = new Scroller(Context);
|
var configuration = ViewConfiguration.Get(Context);
|
//最小的滑动距离
|
mTouchSlop = configuration.ScaledTouchSlop;
|
mMaximumVelocity = configuration.ScaledMaximumFlingVelocity;
|
}
|
|
public override void RequestDisallowInterceptTouchEvent(bool disallowIntercept)
|
{
|
base.RequestDisallowInterceptTouchEvent(disallowIntercept);
|
disallowInterceptTouchEvent = disallowIntercept;
|
}
|
bool disallowInterceptTouchEvent;
|
|
bool isFirst;
|
public override bool OnInterceptTouchEvent(MotionEvent ev)
|
{
|
if (disallowInterceptTouchEvent)
|
{
|
return false;
|
}
|
System.Console.WriteLine($"PageLayout->OnInterceptTouchEvent:{Height} {ev.Action}");
|
//如果没有子控件或者不允许滑动就返回,不处理当前事件
|
if (ChildCount == 0 || !ScrollEnabled)
|
return false;
|
|
mVelocityTracker.AddMovement(ev);
|
var x = ev.GetX();
|
var y = ev.GetY();
|
switch (ev.Action)
|
{
|
//每次点击都执行这里
|
case MotionEventActions.Down:
|
if (!scroller.IsFinished)
|
{
|
//如果还在滑动,但是用户重新点击了,就让控件马上停止
|
scroller.AbortAnimation();
|
snapToDestination(false);
|
}
|
//记录点击的最新X坐标
|
lastMotionX = x;
|
lastMotionY = y;
|
touchState = scroller.IsFinished ? touchStateRest : touchStateScrolling;
|
break;
|
//有子控件时执行这里
|
case MotionEventActions.Move:
|
var deltaX = (int)(lastMotionX - x);
|
var deltaY = (int)(lastMotionY - y);
|
//当前滑动是否已经超出了设定值
|
var xMoved = Math.Abs(deltaX) > mTouchSlop;
|
System.Console.WriteLine($"PageLayout->xMoved22:{xMoved } {deltaX}");
|
|
if (xMoved && Math.Abs(deltaY) < Math.Abs(deltaX))
|
{
|
if (!isFirst)
|
{
|
touchState = touchStateScrolling;
|
}
|
isFirst = false;
|
System.Console.WriteLine($"PageLayout->xMoved33:{xMoved } {deltaX}");
|
|
}
|
if (touchState == touchStateScrolling)
|
{
|
//记录最新的坐标,因为接下来控件已经滑动了
|
lastMotionX = x;
|
System.Console.WriteLine($"PageLayout->lastMotionX:{lastMotionX } {touchState}");
|
//当前事件自己处理了,子控制不需要处理当前这事件
|
return true;
|
}
|
break;
|
case MotionEventActions.Cancel:
|
touchState = touchStateRest;
|
mVelocityTracker.Clear();
|
break;
|
}
|
return false;
|
}
|
|
public override bool OnTouchEvent(MotionEvent e)
|
{
|
if (disallowInterceptTouchEvent)
|
{
|
return false;
|
}
|
System.Console.WriteLine($"PageLayout->OnTouchEvent:{Height} {e.Action}");
|
|
if (ChildCount == 0 || !ScrollEnabled)
|
return false;
|
|
mVelocityTracker.AddMovement(e);
|
|
var x = e.GetX();
|
var y = e.GetY();
|
switch (e.Action)
|
{
|
case MotionEventActions.Move:
|
var deltaX = (int)(lastMotionX - x);
|
var deltaY = (int)(lastMotionY - y);
|
//当前滑动是否已经超出了设定值
|
var xMoved = Math.Abs(deltaX) > mTouchSlop;
|
|
System.Console.WriteLine($"PageLayout->xMoved:{xMoved } {deltaX}");
|
|
if (xMoved && Math.Abs(deltaY) < Math.Abs(deltaX))
|
{
|
if (!isFirst)
|
{
|
touchState = touchStateScrolling;
|
}
|
isFirst = false;
|
}
|
|
if (touchState == touchStateScrolling)
|
{
|
lastMotionX = x;
|
return true;
|
}
|
break;
|
|
case MotionEventActions.Up:
|
if (touchState == touchStateScrolling)
|
{
|
mVelocityTracker.ComputeCurrentVelocity(1000, mMaximumVelocity);
|
var velocityX = mVelocityTracker.XVelocity;
|
|
if (velocityX > snapVelocity && CurrentScreen > 0)
|
{
|
// 显示左边界面
|
snapToScreen(CurrentScreen - 1);
|
}
|
else if (velocityX < -snapVelocity && CurrentScreen < ChildCount - 1)
|
{
|
// 显示右边界面
|
snapToScreen(CurrentScreen + 1);
|
}
|
}
|
|
touchState = touchStateRest;
|
mVelocityTracker.Clear();
|
break;
|
case MotionEventActions.Cancel:
|
touchState = touchStateRest;
|
mVelocityTracker.Clear();
|
break;
|
}
|
return true;
|
}
|
|
/// <summary>
|
/// 滑动到指定的界面
|
/// </summary>
|
void snapToDestination(bool isDelay = true)
|
{
|
var whichScreen = (ScrollX + (MeasuredWidth / 2)) / MeasuredWidth;
|
|
snapToScreen(whichScreen, isDelay);
|
}
|
/// <summary>
|
/// 滑动到指定界面
|
/// </summary>
|
/// <param name="whichScreen">Which screen.</param>
|
void snapToScreen(int whichScreen, bool isDelay = true)
|
{
|
if (!scroller.IsFinished)
|
{
|
scroller.AbortAnimation();
|
}
|
var beforePosition = CurrentScreen;
|
whichScreen = Math.Max(0, Math.Min(whichScreen, ChildCount - 1));
|
CurrentScreen = whichScreen;
|
//还原这个控件之前的位置
|
var newX = CurrentScreen * MeasuredWidth;
|
var delta = newX - ScrollX;
|
scroller.StartScroll(ScrollX, 0, delta, 0, isDelay ? 800 : 0);
|
Invalidate();
|
refreshPage();
|
|
if (beforePosition != CurrentScreen)
|
{
|
horizontalPages.PageChange?.Invoke(horizontalPages, CurrentScreen);
|
}
|
}
|
|
void refreshPage()
|
{
|
GetChildAt(CurrentScreen).SetX(LayoutParameters.Width * CurrentScreen);
|
|
if (0 <= CurrentScreen - 1)
|
{
|
//让上一个界面向右出来一点
|
GetChildAt(CurrentScreen - 1).SetX(LayoutParameters.Width * (CurrentScreen - 1) + Padding + PagePadding);
|
}
|
if (CurrentScreen + 1 <= ChildCount - 1)
|
{
|
//让下一个界面向左出来一点
|
GetChildAt(CurrentScreen + 1).SetX(LayoutParameters.Width * (CurrentScreen + 1) - Padding - PagePadding);
|
}
|
}
|
public override void ComputeScroll()
|
{
|
if (scroller.ComputeScrollOffset())
|
{
|
ScrollTo(scroller.CurrX, scroller.CurrY);
|
PostInvalidate();
|
}
|
else if (CurrentScreen != invalidScreen)
|
{
|
//mCurrentScreen = Math.Max(0, Math.Min(mNextScreen, ChildCount - 1));
|
//mNextScreen = invalidScreen;
|
//postViewSwitched(mLastScrollDirection);
|
}
|
}
|
|
public void SetSelection(int position)
|
{
|
if (position < 0)
|
{
|
return;
|
}
|
scroller.ForceFinished(true);
|
|
position = Math.Min(position, ChildCount - 1);
|
|
snapToScreen(position, false);
|
}
|
|
public override bool DispatchTouchEvent(MotionEvent e)
|
{
|
//System.Console.WriteLine($"PageLayout->DispatchTouchEvent:{Height} {e.Action}");
|
if (e.Action == MotionEventActions.Down)
|
{
|
//还原中断
|
RequestDisallowInterceptTouchEvent(false);
|
isFirst = true;
|
}
|
return base.DispatchTouchEvent(e);
|
}
|
}
|
}
|