基于MVVMCross为Xamarin.Android和Xamarin.iOS开发3个可编辑标签页
基于MVVMCross的跨平台可编辑标签页实现示例
没问题,我帮你整理了适配Xamarin.Android和Xamarin.iOS的可编辑标签页方案,核心是用MVVMCross的共享ViewModel统一业务逻辑,再针对两个平台做视图层的实现:
1. 共享ViewModel层实现
咱们先从共享的ViewModel入手,所有平台的标签增删、选中逻辑都在这里统一处理:
using MvvmCross.ViewModels; using System.Collections.ObjectModel; using System.Windows.Input; public class TagItem { public string Name { get; set; } } public class TagsViewModel : MvxViewModel { private ObservableCollection<TagItem> _tags; public ObservableCollection<TagItem> Tags { get => _tags; set => SetProperty(ref _tags, value); } private TagItem _selectedTag; public TagItem SelectedTag { get => _selectedTag; set => SetProperty(ref _selectedTag, value); } public ICommand AddTagCommand { get; } public ICommand RemoveTagCommand { get; } public TagsViewModel() { // 初始化默认的3个标签 Tags = new ObservableCollection<TagItem> { new TagItem { Name = "标签1" }, new TagItem { Name = "标签2" }, new TagItem { Name = "标签3" } }; AddTagCommand = new MvxCommand(AddNewTag); RemoveTagCommand = new MvxCommand<TagItem>(RemoveTag); } private void AddNewTag() { var newTag = new TagItem { Name = $"新标签{Tags.Count + 1}" }; Tags.Add(newTag); SelectedTag = newTag; } private void RemoveTag(TagItem tag) { if (Tags.Contains(tag)) { var currentIndex = Tags.IndexOf(tag); Tags.Remove(tag); // 删除后自动选中下一个或上一个标签,避免空状态 if (Tags.Count > 0) { SelectedTag = Tags[currentIndex >= Tags.Count ? Tags.Count - 1 : currentIndex]; } } } }
2. Xamarin.Android 视图实现
Android这边用TabLayout + ViewPager的经典组合,配合MVVMCross绑定实现标签的增删和页面切换:
2.1 布局文件(TabsView.axml)
<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <com.google.android.material.tabs.TabLayout android:id="@+id/tabLayout" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabMode="scrollable" /> </com.google.android.material.appbar.AppBarLayout> <androidx.viewpager.widget.ViewPager android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fabAddTag" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="16dp" android:src="@drawable/ic_add" /> </androidx.coordinatorlayout.widget.CoordinatorLayout>
2.2 代码绑定实现
using Android.App; using Android.OS; using Android.Views; using MvvmCross.DroidX.Views; using MvvmCross.Binding.BindingContext; using AndroidX.ViewPager.Widget; using Google.Android.Material.Tabs; using Android.App.Alerts; [Activity(Label = "可编辑标签页")] public class TabsView : MvxAppCompatActivity<TagsViewModel> { private TabLayout _tabLayout; private ViewPager _viewPager; private MvxPagerAdapter _pagerAdapter; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.TabsView); _tabLayout = FindViewById<TabLayout>(Resource.Id.tabLayout); _viewPager = FindViewById<ViewPager>(Resource.Id.viewPager); // 初始化ViewPager适配器 _pagerAdapter = new MvxPagerAdapter(Context, SupportFragmentManager, ViewModel.Tags); _viewPager.Adapter = _pagerAdapter; _tabLayout.SetupWithViewPager(_viewPager); // 绑定添加标签命令到悬浮按钮 var bindingSet = this.CreateBindingSet<TabsView, TagsViewModel>(); bindingSet.Bind(FindViewById<View>(Resource.Id.fabAddTag)).To(vm => vm.AddTagCommand); bindingSet.Apply(); // 初始化标签长按删除事件 BindTabLongClickEvents(); // 监听标签集合变化,实时更新TabLayout ViewModel.Tags.CollectionChanged += (s, e) => { _pagerAdapter.NotifyDataSetChanged(); _tabLayout.SetupWithViewPager(_viewPager); BindTabLongClickEvents(); }; } private void BindTabLongClickEvents() { // 先清空旧的长按事件 for (int i = 0; i < _tabLayout.TabCount; i++) { var tabView = _tabLayout.GetTabAt(i).View; tabView.LongClick -= TabView_LongClick; tabView.LongClick += TabView_LongClick; } } private void TabView_LongClick(object sender, View.LongClickEventArgs e) { var tabView = sender as View; var tabIndex = _tabLayout.GetTabPosition(tabView); var targetTag = ViewModel.Tags[tabIndex]; new AlertDialog.Builder(Context) .SetTitle("删除标签") .SetMessage($"确定删除\"{targetTag.Name}\"吗?") .SetPositiveButton("确定", (dialog, args) => ViewModel.RemoveTagCommand.Execute(targetTag)) .SetNegativeButton("取消", null) .Show(); } } // 自定义ViewPager适配器 public class MvxPagerAdapter : PagerAdapter { private Context _context; private FragmentManager _fragmentManager; private ObservableCollection<TagItem> _tags; public MvxPagerAdapter(Context context, FragmentManager fragmentManager, ObservableCollection<TagItem> tags) { _context = context; _fragmentManager = fragmentManager; _tags = tags; } public override int Count => _tags.Count; public override bool IsViewFromObject(View view, Java.Lang.Object @object) { return view == ((Fragment)@object).View; } public override Java.Lang.Object InstantiateItem(ViewGroup container, int position) { // 替换成你自己的内容Fragment var contentFragment = new ContentFragment(); contentFragment.Arguments = new Bundle(); contentFragment.Arguments.PutString("TagTitle", _tags[position].Name); _fragmentManager.BeginTransaction().Add(container.Id, contentFragment).Commit(); return contentFragment; } public override void DestroyItem(ViewGroup container, int position, Java.Lang.Object @object) { _fragmentManager.BeginTransaction().Remove((Fragment)@object).Commit(); } public override CharSequence GetPageTitleFormatted(int position) { return _tags[position].Name; } } // 示例内容Fragment public class ContentFragment : Fragment { public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { var view = inflater.Inflate(Resource.Layout.ContentFragment, container, false); var titleTxt = view.FindViewById<TextView>(Resource.Id.txtTagTitle); titleTxt.Text = Arguments.GetString("TagTitle"); return view; } }
3. Xamarin.iOS 视图实现
iOS这边用UIPageViewController配合自定义标签栏(UIStackView实现),结合MVVMCross绑定实现可编辑标签:
3.1 视图控制器代码
using UIKit; using MvvmCross.iOS.Views; using MvvmCross.Binding.BindingContext; using System.Collections.Specialized; [Register("TabsView")] public class TabsView : MvxViewController<TagsViewModel> { private UIPageViewController _pageViewController; private UIStackView _tagBarStack; private UIButton _addTagBtn; public override void ViewDidLoad() { base.ViewDidLoad(); View.BackgroundColor = UIColor.White; // 初始化顶部标签栏 SetupTagBar(); // 初始化页面控制器 SetupPageViewController(); // 绑定MVVM命令 SetupBindings(); // 监听标签集合变化 ViewModel.Tags.CollectionChanged += Tags_CollectionChanged; } private void SetupTagBar() { _tagBarStack = new UIStackView { Axis = UILayoutConstraintAxis.Horizontal, Spacing = 12, TranslatesAutoresizingMaskIntoConstraints = false }; _addTagBtn = new UIButton(UIButtonType.System) { TranslatesAutoresizingMaskIntoConstraints = false, Title = "+", Font = UIFont.SystemFontOfSize(18) }; var topContainer = new UIStackView(new[] { _tagBarStack, _addTagBtn }) { Axis = UILayoutConstraintAxis.Horizontal, Spacing = 16, TranslatesAutoresizingMaskIntoConstraints = false }; View.AddSubview(topContainer); NSLayoutConstraint.ActivateConstraints(new[] { topContainer.TopAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.TopAnchor, 16), topContainer.LeadingAnchor.ConstraintEqualTo(View.LeadingAnchor, 16), topContainer.TrailingAnchor.ConstraintEqualTo(View.TrailingAnchor, -16) }); // 初始化默认标签按钮 RefreshTagButtons(); } private void SetupPageViewController() { _pageViewController = new UIPageViewController(UIPageViewControllerTransitionStyle.Scroll, UIPageViewControllerNavigationOrientation.Horizontal); _pageViewController.DataSource = new PageDataSource(ViewModel.Tags); AddChildViewController(_pageViewController); View.AddSubview(_pageViewController.View); NSLayoutConstraint.ActivateConstraints(new[] { _pageViewController.View.TopAnchor.ConstraintEqualTo(_tagBarStack.BottomAnchor, 16), _pageViewController.View.LeadingAnchor.ConstraintEqualTo(View.LeadingAnchor), _pageViewController.View.TrailingAnchor.ConstraintEqualTo(View.TrailingAnchor), _pageViewController.View.BottomAnchor.ConstraintEqualTo(View.BottomAnchor) }); _pageViewController.DidMoveToParentViewController(this); // 设置初始页面 if (ViewModel.Tags.Count > 0) { _pageViewController.SetViewControllers(new[] { GetContentVC(0) }, UIPageViewControllerNavigationDirection.Forward, false, null); SetSelectedTagButton(0); } } private void SetupBindings() { var bindingSet = this.CreateBindingSet<TabsView, TagsViewModel>(); bindingSet.Bind(_addTagBtn).To(vm => vm.AddTagCommand); bindingSet.Apply(); } private void Tags_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { RefreshTagButtons(); // 更新页面控制器数据源 (_pageViewController.DataSource as PageDataSource)?.UpdateTags(ViewModel.Tags); // 添加新标签后自动切换到新页面 if (e.Action == NotifyCollectionChangedAction.Add) { var newIndex = e.NewStartingIndex; _pageViewController.SetViewControllers(new[] { GetContentVC(newIndex) }, UIPageViewControllerNavigationDirection.Forward, true, null); SetSelectedTagButton(newIndex); } } private void RefreshTagButtons() { // 清空旧标签按钮 foreach (var view in _tagBarStack.ArrangedSubviews) { view.RemoveFromSuperview(); } // 添加新标签按钮 for (int i = 0; i < ViewModel.Tags.Count; i++) { var tag = ViewModel.Tags[i]; var tagBtn = new UIButton(UIButtonType.System) { Title = tag.Name, Tag = i }; tagBtn.TouchUpInside += (s, e) => { var index = (s as UIButton).Tag; _pageViewController.SetViewControllers(new[] { GetContentVC(index) }, UIPageViewControllerNavigationDirection.Forward, true, null); SetSelectedTagButton(index); }; // 长按删除标签 var longPress = new UILongPressGestureRecognizer((gesture) => { if (gesture.State == UIGestureRecognizerState.Began) { var alert = UIAlertController.Create("删除标签", $"确定删除\"{tag.Name}\"吗?", UIAlertControllerStyle.Alert); alert.AddAction(UIAlertAction.Create("确定", UIAlertActionStyle.Default, _ => ViewModel.RemoveTagCommand.Execute(tag))); alert.AddAction(UIAlertAction.Create("取消", UIAlertActionStyle.Cancel, null)); PresentViewController(alert, true, null); } }); tagBtn.AddGestureRecognizer(longPress); _tagBarStack.AddArrangedSubview(tagBtn); } } private void SetSelectedTagButton(int index) { for (int i = 0; i < _tagBarStack.ArrangedSubviews.Length; i++) { var btn = _tagBarStack.ArrangedSubviews[i] as UIButton; btn.TintColor = i == index ? UIColor.SystemBlueColor : UIColor.LabelColor; } } private UIViewController GetContentVC(int index) { var contentVC = new ContentViewController(); contentVC.TagTitle = ViewModel.Tags[index].Name; contentVC.TagIndex = index; return contentVC; } } // 页面数据源 public class PageDataSource : UIPageViewControllerDataSource { private ObservableCollection<TagItem> _tags; public PageDataSource(ObservableCollection<TagItem> tags) { _tags = tags; } public void UpdateTags(ObservableCollection<TagItem> tags) { _tags = tags; } public override UIViewController GetPreviousViewController(UIPageViewController pageViewController, UIViewController referenceViewController) { var index = (referenceViewController as ContentViewController).TagIndex; return index > 0 ? GetContentVC(index - 1) : null; } public override UIViewController GetNextViewController(UIPageViewController pageViewController, UIViewController referenceViewController) { var index = (referenceViewController as ContentViewController).TagIndex; return index < _tags.Count - 1 ? GetContentVC(index + 1) : null; } private UIViewController GetContentVC(int index) { var vc = new ContentViewController { TagIndex = index, TagTitle = _tags[index].Name }; return vc; } } // 示例内容视图控制器 public class ContentViewController : UIViewController { public int TagIndex { get; set; } public string TagTitle { get; set; } public override void ViewDidLoad() { base.ViewDidLoad(); View.BackgroundColor = UIColor.White; var titleLabel = new UILabel { Text = $"当前标签:{TagTitle}", TranslatesAutoresizingMaskIntoConstraints = false }; View.AddSubview(titleLabel); NSLayoutConstraint.ActivateConstraints(new[] { titleLabel.CenterXAnchor.ConstraintEqualTo(View.CenterXAnchor), titleLabel.CenterYAnchor.ConstraintEqualTo(View.CenterYAnchor) }); } }
内容的提问来源于stack exchange,提问作者Santosh
相关产品推荐
相关产品推荐

