You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Xamarin中通过Custom Renderer实现Entry获焦时占位符上移动效

Xamarin Entry 占位符上浮动画实现方案(自定义渲染器)

共享层实现自定义Entry控件

首先在共享项目中创建继承自Entry的自定义控件,暴露占位符边距、内边距以及动画时长的可绑定属性:

public class FloatingPlaceholderEntry : Entry
{
    public static readonly BindableProperty PlaceholderMarginProperty = 
        BindableProperty.Create(nameof(PlaceholderMargin), typeof(Thickness), typeof(FloatingPlaceholderEntry), new Thickness(0));
    
    public static readonly BindableProperty PlaceholderPaddingProperty = 
        BindableProperty.Create(nameof(PlaceholderPadding), typeof(Thickness), typeof(FloatingPlaceholderEntry), new Thickness(0));
    
    public static readonly BindableProperty AnimationDurationProperty = 
        BindableProperty.Create(nameof(AnimationDuration), typeof(int), typeof(FloatingPlaceholderEntry), 200);

    public Thickness PlaceholderMargin
    {
        get => (Thickness)GetValue(PlaceholderMarginProperty);
        set => SetValue(PlaceholderMarginProperty, value);
    }

    public Thickness PlaceholderPadding
    {
        get => (Thickness)GetValue(PlaceholderPaddingProperty);
        set => SetValue(PlaceholderPaddingProperty, value);
    }

    public int AnimationDuration
    {
        get => (int)GetValue(AnimationDurationProperty);
        set => SetValue(AnimationDurationProperty, value);
    }
}

Android 平台自定义渲染器

在Android项目中创建渲染器,基于原生TextInputLayout实现浮动占位符效果,适配自定义的边距属性:

[assembly: ExportRenderer(typeof(FloatingPlaceholderEntry), typeof(FloatingPlaceholderEntryRenderer))]
namespace YourNamespace.Droid.Renderers
{
    public class FloatingPlaceholderEntryRenderer : EntryRenderer
    {
        private TextInputLayout _textInputLayout;
        private FloatingPlaceholderEntry _customEntry;

        public FloatingPlaceholderEntryRenderer(Context context) : base(context) { }

        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);
            if (e.NewElement is not FloatingPlaceholderEntry customEntry || Control == null) return;
            
            _customEntry = customEntry;
            // 用TextInputLayout包裹原生EditText
            _textInputLayout = new TextInputLayout(Context);
            _textInputLayout.AddView(Control);
            SetNativeControl(_textInputLayout);

            // 开启浮动占位符效果
            _textInputLayout.HintEnabled = true;
            _textInputLayout.Hint = _customEntry.Placeholder;
            
            // 设置线性动画
            _textInputLayout.SetHintAnimationEnabled(true);
            // 适配占位符margin和padding
            _textInputLayout.SetPadding(
                (int)Context.ToPixels(_customEntry.PlaceholderPadding.Left),
                (int)Context.ToPixels(_customEntry.PlaceholderPadding.Top),
                (int)Context.ToPixels(_customEntry.PlaceholderPadding.Right),
                (int)Context.ToPixels(_customEntry.PlaceholderPadding.Bottom));
            Control.SetPadding(
                (int)Context.ToPixels(_customEntry.PlaceholderMargin.Left),
                (int)Context.ToPixels(_customEntry.PlaceholderMargin.Top),
                (int)Context.ToPixels(_customEntry.PlaceholderMargin.Right),
                (int)Context.ToPixels(_customEntry.PlaceholderMargin.Bottom));
            
            // 自定义动画时长
            var hintAnimator = _textInputLayout.HintAnimation;
            hintAnimator.Duration = _customEntry.AnimationDuration;
            hintAnimator.Interpolator = new LinearInterpolator();
        }
    }
}

iOS 平台自定义渲染器

在iOS项目中创建渲染器重写UITextField的编辑事件,通过原生线性动画实现占位符上浮效果:

[assembly: ExportRenderer(typeof(FloatingPlaceholderEntry), typeof(FloatingPlaceholderEntryRenderer))]
namespace YourNamespace.iOS.Renderers
{
    public class FloatingPlaceholderEntryRenderer : EntryRenderer
    {
        private UILabel _floatingPlaceholderLabel;
        private FloatingPlaceholderEntry _customEntry;
        private CGRect _originalPlaceholderFrame;

        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);
            if (e.NewElement is not FloatingPlaceholderEntry customEntry || Control == null) return;
            
            _customEntry = customEntry;
            // 隐藏原生占位符,自定义浮动占位符Label
            Control.Placeholder = string.Empty;
            _floatingPlaceholderLabel = new UILabel
            {
                Text = customEntry.Placeholder,
                Font = UIFont.SystemFontOfSize(14),
                TextColor = UIColor.LightGray,
                Frame = new CGRect(
                    customEntry.PlaceholderMargin.Left + customEntry.PlaceholderPadding.Left,
                    customEntry.PlaceholderMargin.Top + customEntry.PlaceholderPadding.Top,
                    Control.Bounds.Width,
                    Control.Bounds.Height)
            };
            _originalPlaceholderFrame = _floatingPlaceholderLabel.Frame;
            Control.AddSubview(_floatingPlaceholderLabel);

            // 绑定编辑事件
            Control.EditingDidBegin += OnEditingDidBegin;
            Control.EditingDidEnd += OnEditingDidEnd;
        }

        private void OnEditingDidBegin(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(Control.Text)) return;
            // 线性动画上浮占位符
            UIView.Animate(_customEntry.AnimationDuration / 1000.0, 0, UIViewAnimationOptions.CurveLinear, () =>
            {
                _floatingPlaceholderLabel.Frame = new CGRect(
                    _originalPlaceholderFrame.X,
                    _originalPlaceholderFrame.Y - 20,
                    _originalPlaceholderFrame.Width,
                    12);
                _floatingPlaceholderLabel.Font = UIFont.SystemFontOfSize(12);
            });
        }

        private void OnEditingDidEnd(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(Control.Text)) return;
            // 线性动画恢复占位符位置
            UIView.Animate(_customEntry.AnimationDuration / 1000.0, 0, UIViewAnimationOptions.CurveLinear, () =>
            {
                _floatingPlaceholderLabel.Frame = _originalPlaceholderFrame;
                _floatingPlaceholderLabel.Font = UIFont.SystemFontOfSize(14);
            });
        }
    }
}

XAML 调用示例

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:custom="clr-namespace:YourNamespace.Controls"
             x:Class="YourNamespace.MainPage">
    <StackLayout Padding="20">
        <custom:FloatingPlaceholderEntry 
            Placeholder="请输入用户名"
            PlaceholderMargin="10,0"
            PlaceholderPadding="5,0"
            AnimationDuration="250"
            HeightRequest="50"/>
    </StackLayout>
</ContentPage>

注意事项

  • 如需修改边框样式,可在各平台渲染器中对应设置TextInputLayout(Android)和UITextField.Layer.BorderWidth(iOS)的属性,避免占位符上浮后和边框重叠
  • 占位符上浮后的偏移量可根据实际UI需求自行调整,iOS端当前默认偏移20px
  • 若需要支持深色模式,可额外绑定占位符不同状态下的颜色属性

内容的提问来源于stack exchange,提问作者Shashank Mishra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 22:09:03