【问题标题】:Hiding Floating action button when parent scrolled父滚动时隐藏浮动操作按钮
【发布时间】:2018-07-31 21:49:07
【问题描述】:

我正在使用Xamarin.FormsandroidiOS 平台创建一个自定义FAB 按钮。

一切正常,但现在我希望能够以某种方式从FAB 按钮的最近父级接收scrolled 事件(如果有),因此我将能够在用户隐藏FAB 按钮时正在滚动,然后在滚动完成 2 秒后再次显示?

父项可以是普通的ScrollView 项目,也可以是ListView
这可以通过每个平台的相同自定义渲染器来实现吗?或者,这甚至可以实现吗?

这是我到目前为止所做的:

  1. FabButton类:


public partial class FabButton : Button
{
    public static BindableProperty ButtonColorProperty = BindableProperty.Create(nameof(ButtonColor), typeof(Color), typeof(FabButton), Color.Accent);
    public Color ButtonColor
    {
        get => (Color)GetValue(ButtonColorProperty);
        set => SetValue(ButtonColorProperty, value);
    }
    public FabButton()
    {
        InitializeComponent();
    }
}
  1. iOS自定义渲染器:

    public class FabButtonRenderer:ButtonRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
        {
            base.OnElementChanged(e);
            if(e.NewElement == null)
                return;
            if (Element.WidthRequest <= 0)
                Element.WidthRequest = 60;
            if (Element.HeightRequest <= 0)
                Element.HeightRequest = 60;
            if (Element.Margin == new Thickness(0, 0, 0, 0))
                Element.Margin = new Thickness(0, 0, 20, 20);
            Element.CornerRadius = (int) Element.WidthRequest / 2;
            Element.BorderWidth = 0;
            Element.Text = null;
            Control.BackgroundColor = ((FabButton) Element).ButtonColor.ToUIColor();
        }
        public override void Draw(CGRect rect)
        {
            base.Draw(rect);
            Layer.ShadowRadius = 0.2f;
            Layer.ShadowColor = UIColor.Black.CGColor;
            Layer.ShadowOffset = new CGSize(1, 1);
            Layer.ShadowOpacity = 0.80f;
            Layer.ShadowPath = UIBezierPath.FromOval(Layer.Bounds).CGPath;
            Layer.MasksToBounds = false;
        }
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName == nameof(FabButton.ButtonColor))
                Control.BackgroundColor = ((FabButton) Element).ButtonColor.ToUIColor();
        }
    }
    
  2. android自定义渲染器:

    public class FabButtonRenderer: Xamarin.Forms.Platform.Android.AppCompat.ViewRenderer<FabButton,FloatingActionButton>
    {
        public static void InitRenderer() { }
        public FabButtonRenderer(Context context):base(context)
        {
    
        }
    
        protected override void OnElementChanged(ElementChangedEventArgs<FabButton> e)
        {
            base.OnElementChanged(e);
            if (e.NewElement == null)
                return;
            if (e.NewElement.HeightRequest <= 0)
                e.NewElement.HeightRequest = 85;
            if (e.NewElement.WidthRequest <= 0)
                e.NewElement.WidthRequest = 75;
            if (e.NewElement.Margin.Equals(new Thickness(0, 0, 0, 0)))
                e.NewElement.Margin = new Thickness(0, 0, 5, 10);
            var fabButton = new FloatingActionButton(Context);
            ViewCompat.SetBackgroundTintList(fabButton, ColorStateList.ValueOf(Element.ButtonColor.ToAndroid()));
            fabButton.UseCompatPadding = true;
            if (!string.IsNullOrEmpty(Element.Image?.File))
                fabButton.SetImageDrawable(Context.GetDrawable(Element.Image.File));
            fabButton.Click += FabButton_Clicked;
            SetNativeControl(fabButton);
        }
    
        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            base.OnLayout(changed, l, t, r, b);
            Control.BringToFront();
        }
    
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(Element.ButtonColor))
                ViewCompat.SetBackgroundTintList(Control, ColorStateList.ValueOf(Element.ButtonColor.ToAndroid()));
            if (e.PropertyName == nameof(Element.Image))
            {
                if (!string.IsNullOrEmpty(Element.Image?.File))
                    Control.SetImageDrawable(Context.GetDrawable(Element.Image.File));
            }
            base.OnElementPropertyChanged(sender, e);
        }
    
        public void FabButton_Clicked(object sender, EventArgs e)
        {
            Element.SendClicked();
        }
    }
    

【问题讨论】:

    标签: c# mvvm xamarin.forms xamarin.ios xamarin.android


    【解决方案1】:

    我认为 Scrollview 的“滚动”事件可以帮助您,您将获得 X 和 Y 轴值,基于它您必须在滚动时隐藏您的 fab 按钮。 2秒后,您可以使用“Device.StartTimer”。

    【讨论】:

    • 这样不行。显然,我不知道页面的元素树中是否有单独的ScrollView控件,所以我需要遍历元素树并找出当前的parent是否有ScrollView和然后也许订阅那个Scrolled 事件。
    • 如果父母是 listview github.com/velocitysystems/xf-controls 这可以帮助你
    • 对不起,伙计,但您的回答在我的具体情况下并没有帮助我,而是为 ListView 自定义渲染器参考 +1。
    【解决方案2】:

    不幸的是,没有built-in 方法可以做到这一点。
    在我的情况下,Floating Action Button 可能会受到ScrollView 控件滚动或列表视图控件滚动或其他的影响。
    我能想到的唯一方法是明确指定Floating Action Button应该连接到哪个ScrollView控件,因为当Floating Action Button应该连接到时这将是一个麻烦的实现列表视图ScrollView,由于最好使用MVVM 模式,我找到了一种更简单的方法。
    首先我声明了一个接口IFloatingActionButtonHost:

    public interface IFloatingActionButtonHost
    {
        bool ShouldBeHiddenWhenScrolling { get; }
        event Action LinkedScrollViewScrolled;
    }
    

    然后,我在 FabButton 控件中声明了一个 BindableProperty,如下所示:

    public static BindableProperty ButtonHostProperty = BindableProperty.Create(nameof(ButtonHost), typeof(IFloatingActionButtonHost), typeof(FabButton));
    public IFloatingActionButtonHost ButtonHost
    {
        get => (IFloatingActionButtonHost)GetValue(ButtonHostProperty);
        set => SetValue(ButtonHostProperty, value);
    }
    

    例如在Renderer 中,在iOS 渲染器上,我检查了这个属性是否不为空,如果Floating Action Button 在目标ScrollView 时隐藏,应该是否滚动,然后订阅界面中的事件。

        var fabButton = (FabButton)Element;
        if (fabButton.ButtonHost != null && fabButton.ButtonHost.ShouldBeHiddenWhenScrolling)
        {
            fabButton.ButtonHost.LinkedScrollViewScrolled += OnLinkedScrollView_Scrolled;
        }
    

    然后我在渲染器中处理了事件:

    private void OnLinkedScrollView_Scrolled()
    {
        Element.IsVisible = false;
        Device.StartTimer(TimeSpan.FromSeconds(3), () =>
        {
            if(Element != null){
                Element.IsVisible = true;
            }
            return false;
        });
    }
    

    OnElementPropertyChanged 的实现也应该更改。

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        if (e.PropertyName == nameof(FabButton.ButtonColor))
            Control.BackgroundColor = ((FabButton) Element).ButtonColor.ToUIColor();
        else if(e.PropertyName == nameof(FabButton.ButtonHost)){
            var fabButton = (FabButton)Element;
            if (fabButton.ButtonHost != null && fabButton.ButtonHost.ShouldBeHiddenWhenScrolling)
            {
                fabButton.ButtonHost.LinkedScrollViewScrolled += OnLinkedScrollView_Scrolled;
            }
        }
    }
    

    在每个页面code-behind,我让页面继承自这个接口,并将页面实例传递给目标ViewModel
    P.S.对于ListView,我不得不编写自定义渲染器以公开Scrolled 事件。抱歉,我没有找到更好的方法来做到这一点。

    这是我解决这个问题的尝试,如果您有更好的解决方案,请为这个问题写另一个答案,我会将其标记为最佳答案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-07-27
      • 1970-01-01
      • 2015-12-13
      • 2017-08-12
      • 2020-05-28
      • 1970-01-01
      • 2020-07-07
      • 2015-02-15
      相关资源
      最近更新 更多