using System; using Android.Views; using Android.Content; using Android.Widget; using Android.Graphics.Drawables; namespace Shared { //已经全面检查了代码 /// /// 位置布局 /// public class HorizontalPages : ViewGroup { /// /// 视图高度 /// /// The height. 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; } } /// /// 视图宽度 /// /// The width. 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; } } /// /// 页面变化事件 /// public Action PageChange; /// /// 构造函数 /// public HorizontalPages() { viewGroup = new AndroidFrameLayout(Application.Activity, null); realViewGroup = new InnerHorizontalPages(Application.Activity, this) { }; viewGroup.AddView(realViewGroup); } int pageIndex; /// /// 设置或者获取当前的界面索引 /// /// The index of the page. 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); } } /// /// 是否允许滑动 /// /// true if scroll enabled; otherwise, false. public bool ScrollEnabled { get { return (realViewGroup as InnerHorizontalPages).ScrollEnabled; } set { (realViewGroup as InnerHorizontalPages).ScrollEnabled = value; } } /// /// 增加子控件 /// /// View. 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(); } } } /// /// 移除当前控件 /// /// View. 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; } /// /// 移除所有的控件 /// public override void RemoveAll() { while (0 < viewList.Count) { GetChildren(0)?.RemoveFromParent(); } (realViewGroup as InnerHorizontalPages).SetSelection(0); } /// /// 根据索引移除控件 /// /// Index. public override void RemoveAt(int index) { if (viewList.Count - 1 < index || index < 0) { return; } GetChildren(index)?.RemoveFromParent(); } /// /// page的外边距 /// public int RowPadding { get { return (realViewGroup as InnerHorizontalPages).Padding; } set { (realViewGroup as InnerHorizontalPages).Padding = value; } } /// /// 两个Page之间的距离 /// 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; /// /// 界面之间的边距 /// 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(); } /// /// 重写这个是为了方便部局,在每个界面之间都多加了一个控件包住 /// /// 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; } /// /// 滑动到指定的界面 /// void snapToDestination(bool isDelay = true) { var whichScreen = (ScrollX + (MeasuredWidth / 2)) / MeasuredWidth; snapToScreen(whichScreen, isDelay); } /// /// 滑动到指定界面 /// /// Which screen. 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); } } }