Xamarin.Android自定义ScrollPicker执行Drop后Label不可见问题
问题描述
我开发了名为ScrollPicker的自定义控件,内部封装ScrollView实现滚动能力,控件子元素仅包含Label,支持滚动、点击以及拖拽放置(Drop)操作。
在Android平台运行应用测试时,滚动、点击功能均正常,Drop功能也可正常触发,但执行Drop操作后,对应的Label会变为不可见状态:该Label仍占据原有布局位置,且在该不可见Label的位置再次执行Drop操作时,Drop处理逻辑仍会正常执行。
相关代码
ScrollPicker XAML 定义
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="DebugTest.MyView.ApplyView.ScrollPicker"> <ContentView.Content> <ScrollView x:Name="ScrollView" x:FieldModifier="Public"> <StackLayout x:Name="MainView" x:FieldModifier="Public"> </StackLayout> </ScrollView> </ContentView.Content> </ContentView>
ScrollPicker C# 后台代码
public partial class ScrollPicker : ContentView { public static readonly BindableProperty OrientationProperty = BindableProperty.Create("Orientation", typeof(StackOrientation), typeof(ScrollPicker), StackOrientation.Vertical, propertyChanged: Orientation_Changed); public static readonly BindableProperty ItemSourceProperty = BindableProperty.Create("ItemSource", typeof(List<string>), typeof(ScrollPicker), propertyChanged: ItemSource_Changed); private static void Orientation_Changed(BindableObject bindable, object oldValue, object newValue) { if(((StackOrientation)newValue) == StackOrientation.Horizontal) { ((ScrollPicker)bindable).ScrollView.Orientation = ScrollOrientation.Horizontal; ((ScrollPicker)bindable).MainView.Orientation = StackOrientation.Horizontal; } else { ((ScrollPicker)bindable).ScrollView.Orientation = ScrollOrientation.Vertical; ((ScrollPicker)bindable).MainView.Orientation = StackOrientation.Vertical; } } private static void ItemSource_Changed(BindableObject bindable, object oldValue, object newValue) { var view = ((ScrollPicker)bindable); view.ItemSourceList = (List<string>)newValue; view.ResetLabelList(); } public StackOrientation Orientation { get { return (StackOrientation)GetValue(ItemSourceProperty); } set { SetValue(ItemSourceProperty, value); } } public List<string> ItemSource { get { return (List<string>)GetValue(ItemSourceProperty); } set { SetValue(ItemSourceProperty, value); } } public DropGestureRecognizer DropGR; public event EventHandler<IndexArgs> ItemClickEvent; public event EventHandler<IndexArgs> DropEvent; public List<string> ItemSourceList; public List<Label> LabelList = new List<Label>(); public ScrollPicker() { InitializeComponent(); ItemClickEvent += ScrollPicker_ItemClickEvent; DropEvent += ScrollPicker_DropEvent; } private void ScrollPicker_DropEvent(object sender, EventArgs e) { } private void CE_Click(Element obj) { if(LabelList != null && LabelList.Count != 0) { if(LabelList.Exists(label => label == obj)) { int index = LabelList.IndexOf((Label)obj); ItemClickEvent.Invoke(obj, new IndexArgs(index)); } } } private void ScrollPicker_ItemClickEvent(object sender, EventArgs e) { } private void ResetLabelList() { MainView.Children.Clear(); LabelList.Clear(); if (ItemSourceList != null && ItemSourceList.Count != 0) { for(int i=0;i< ItemSourceList.Count;i++) { //创建Label Label temp = CreateSubView(ItemSourceList[i]); //绑定CE ClickEffect CE = new ClickEffect(); CE.Click += CE_Click; Global.ClickEffect_BindClickEffect(temp, CE); //添加Drop DropGR = new DropGestureRecognizer(); DropGR.DragOver += DropGR_DragOver; DropGR.DragLeave += DropGR_DragLeave; DropGR.Drop += DropGR_Drop; temp.GestureRecognizers.Add(DropGR); //添加视图 LabelList.Add(temp); MainView.Children.Add(temp); if (i == 0) temp.Opacity = 0.1; } } } private void DropGR_DragOver(object sender, DragEventArgs e) { } private void DropGR_DragLeave(object sender, DragEventArgs e) { } private void DropGR_Drop(object sender, DropEventArgs e) { var view = (sender as GestureRecognizer).Parent as View; int index = MainView.Children.IndexOf(view); DropEvent.Invoke(view, new IndexArgs(index)); } private Label CreateSubView(string text) { Label label = new Label() { Text = text, FontSize = 20, WidthRequest = 75, VerticalTextAlignment = TextAlignment.Start, HorizontalTextAlignment = TextAlignment.Start, }; return label; } } public abstract partial class ScrollPickerItemView : ContentView { } public class IndexArgs: EventArgs { public int Index; public IndexArgs(int index =0) { Index = index; } } public enum ScrollPickerOrientation { Vertical, Horizontal }
页面调用代码
...... <AbsoluteLayout AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,0.1" BackgroundColor="#66CCFF"> <Label AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,0.2,1" Text="Project:" FontSize="Large" VerticalTextAlignment="Center" HorizontalTextAlignment="Center"/> <applyview:ScrollPicker x:Name="ProjectPicker" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="1,0,0.8,1" Orientation="Horizontal" ItemClickEvent="ProjectPicker_ItemClickEvent" DropEvent="ProjectPicker_DropEvent"/> </AbsoluteLayout>
问题成因与解决方案
问题成因
- 核心问题是Xamarin.Forms Android平台拖拽手势的原生行为特性:当
Drop事件触发时,Android原生拖拽系统会默认将接收Drop事件的目标视图透明度设置为0,且这个原生修改不会同步到Xamarin.Forms层面的Opacity属性绑定,因此会出现视图看不见但仍占布局位置、手势仍可响应的现象。 - 代码存在隐藏bug:
Orientation属性的get/set逻辑写错,错误绑定到了ItemSourceProperty上,会导致方向设置功能异常。
解决方案
- 修复属性绑定错误,将
Orientation属性的get/set逻辑修正为绑定OrientationProperty:
// 修正后代码 public StackOrientation Orientation { get { return (StackOrientation)GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } }
- 处理Drop后的透明度恢复,在
DropGR_Drop方法中手动将触发Drop的目标视图Opacity重置为1,解决不可见问题:
private void DropGR_Drop(object sender, DropEventArgs e) { var view = (sender as GestureRecognizer).Parent as View; // 修复Android平台Drop后视图被原生设置为透明的问题 view.Opacity = 1; int index = MainView.Children.IndexOf(view); DropEvent.Invoke(view, new IndexArgs(index)); }
- 可选优化:如果需要自定义拖拽过程中的视觉反馈,可以在
DragOver和DragLeave事件中手动控制视图透明度,覆盖原生默认样式。
内容的提问来源于stack exchange,提问作者xxpp123zz
相关产品推荐
相关产品推荐

