Xamarin.Forms实现带前置图标及密码显隐图标的Entry
Absolutely! You can definitely have both a leading icon and the trailing password toggle on your custom Entry—this is totally achievable. The issue you're running into is most likely because the separate effects/renderers you're using are overriding each other's changes instead of working in tandem. Let's walk through how to fix this by creating a unified custom Entry control that supports both features.
The best approach is to build a single custom Entry control in your shared code, then implement platform-specific renderers that handle both icons at once. This avoids conflicts between separate effects.
1. Define the Custom Entry in Shared Code
First, create a reusable Entry subclass that exposes properties for both the leading icon and password toggle functionality:
using Xamarin.Forms; namespace YourAppNamespace.Controls { public class CustomPasswordEntry : Entry { // Property for leading icon public static readonly BindableProperty LeadingIconProperty = BindableProperty.Create(nameof(LeadingIcon), typeof(ImageSource), typeof(CustomPasswordEntry)); // Property to enable password toggle public static readonly BindableProperty IsPasswordToggleEnabledProperty = BindableProperty.Create(nameof(IsPasswordToggleEnabled), typeof(bool), typeof(CustomPasswordEntry), false); public ImageSource LeadingIcon { get => (ImageSource)GetValue(LeadingIconProperty); set => SetValue(LeadingIconProperty, value); } public bool IsPasswordToggleEnabled { get => (bool)GetValue(IsPasswordToggleEnabledProperty); set => SetValue(IsPasswordToggleEnabledProperty, value); } } }
2. Android Renderer (Handle Both Icons)
On Android, we'll use CompoundDrawables to add both the leading (left) and trailing (right) icons, plus add a touch listener to the trailing icon for the password toggle:
using Android.Content; using Android.Graphics.Drawables; using Android.Views; using YourAppNamespace.Controls; using YourAppNamespace.Droid.Renderers; using Xamarin.Forms; using Xamarin.Forms.Platform.Android; [assembly: ExportRenderer(typeof(CustomPasswordEntry), typeof(CustomPasswordEntryRenderer))] namespace YourAppNamespace.Droid.Renderers { public class CustomPasswordEntryRenderer : EntryRenderer { public CustomPasswordEntryRenderer(Context context) : base(context) { } protected override void OnElementChanged(ElementChangedEventArgs<Entry> e) { base.OnElementChanged(e); if (Control == null || Element is not CustomPasswordEntry customEntry) return; // Setup leading icon Drawable leadingDrawable = null; if (customEntry.LeadingIcon != null) { var handler = new ImageSourceHandler(); leadingDrawable = handler.LoadImageAsync(customEntry.LeadingIcon, Context).Result; leadingDrawable.SetBounds(0, 0, leadingDrawable.IntrinsicWidth, leadingDrawable.IntrinsicHeight); } // Setup password toggle icon Drawable trailingDrawable = null; if (customEntry.IsPasswordToggleEnabled) { // Load initial toggle icon based on IsPassword state trailingDrawable = Resources.GetDrawable(customEntry.IsPassword ? Resource.Drawable.ic_eye_off : Resource.Drawable.ic_eye); trailingDrawable.SetBounds(0, 0, trailingDrawable.IntrinsicWidth, trailingDrawable.IntrinsicHeight); // Add touch listener to detect taps on the trailing icon Control.Touch += (s, args) => { if (args.Action != MotionEventActions.Up) return; var entryRect = new Rect(); Control.GetGlobalVisibleRect(entryRect); // Check if tap is within the trailing icon area if (args.RawX >= entryRect.Right - trailingDrawable.IntrinsicWidth - Control.PaddingRight) { customEntry.IsPassword = !customEntry.IsPassword; // Update the toggle icon trailingDrawable = Resources.GetDrawable(customEntry.IsPassword ? Resource.Drawable.ic_eye_off : Resource.Drawable.ic_eye); trailingDrawable.SetBounds(0, 0, trailingDrawable.IntrinsicWidth, trailingDrawable.IntrinsicHeight); Control.SetCompoundDrawables(leadingDrawable, null, trailingDrawable, null); args.Handled = true; } }; } // Set both drawables and adjust padding Control.SetCompoundDrawables(leadingDrawable, null, trailingDrawable, null); Control.CompoundDrawablePadding = (int)TypedValue.ApplyDimension(ComplexUnitType.Dip, 12, Resources.DisplayMetrics); } } }
3. iOS Renderer (Handle Both Icons)
On iOS, we'll use LeftView for the leading icon and RightView for the password toggle, then enable both views and add a tap gesture to the toggle:
using System; using UIKit; using YourAppNamespace.Controls; using YourAppNamespace.iOS.Renderers; using Xamarin.Forms; using Xamarin.Forms.Platform.iOS; [assembly: ExportRenderer(typeof(CustomPasswordEntry), typeof(CustomPasswordEntryRenderer))] namespace YourAppNamespace.iOS.Renderers { public class CustomPasswordEntryRenderer : EntryRenderer { private UIImageView _passwordToggleView; protected override void OnElementChanged(ElementChangedEventArgs<Entry> e) { base.OnElementChanged(e); if (Control == null || Element is not CustomPasswordEntry customEntry) return; // Setup leading icon if (customEntry.LeadingIcon != null) { var leadingImageView = new UIImageView { ContentMode = UIViewContentMode.ScaleAspectFit }; // Load the leading icon image ImageSource.FromUri(new Uri(customEntry.LeadingIcon.ToString())).LoadImageAsync((image, error) => { if (image != null) { leadingImageView.Image = image; leadingImageView.Frame = new CoreGraphics.CGRect(0, 0, 20, 20); Control.LeftView = leadingImageView; Control.LeftViewMode = UITextFieldViewMode.Always; Control.LeftView.Frame = new CoreGraphics.CGRect(0, 0, 30, Control.Frame.Height); } }); } // Setup password toggle icon if (customEntry.IsPasswordToggleEnabled) { _passwordToggleView = new UIImageView { Image = UIImage.FromBundle(customEntry.IsPassword ? "ic_eye_off" : "ic_eye"), ContentMode = UIViewContentMode.ScaleAspectFit, UserInteractionEnabled = true }; // Add tap gesture for toggle var tapGesture = new UITapGestureRecognizer(() => { customEntry.IsPassword = !customEntry.IsPassword; _passwordToggleView.Image = UIImage.FromBundle(customEntry.IsPassword ? "ic_eye_off" : "ic_eye"); }); _passwordToggleView.AddGestureRecognizer(tapGesture); Control.RightView = _passwordToggleView; Control.RightViewMode = UITextFieldViewMode.Always; Control.RightView.Frame = new CoreGraphics.CGRect(0, 0, 30, Control.Frame.Height); } // Adjust padding to prevent text from overlapping icons Control.LeftViewPadding = new UIEdgeInsets(0, 8, 0, 8); Control.RightViewPadding = new UIEdgeInsets(0, 8, 0, 8); } } }
4. Use the Custom Entry in XAML
Now you can use your CustomPasswordEntry in any XAML page, setting both the leading icon and enabling the password toggle:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:controls="clr-namespace:YourAppNamespace.Controls" x:Class="YourAppNamespace.Views.PasswordPage"> <StackLayout Padding="20" VerticalOptions="Center"> <controls:CustomPasswordEntry Placeholder="Enter your password" IsPassword="True" IsPasswordToggleEnabled="True" LeadingIcon="lock_icon.png" Margin="0 0 0 15"/> </StackLayout> </ContentPage>
Important Notes
- Make sure your icon assets (
lock_icon.png,ic_eye.png,ic_eye_off.png) are properly added to each platform's resource folders:- Android: Place drawables in the
Resources/drawable(and density-specific folders likedrawable-hdpi) - iOS: Add images to the
Resourcesfolder and set their build action toBundleResource
- Android: Place drawables in the
- By handling both icons in a single renderer, you avoid conflicts that happen when using separate effects (which often overwrite each other's changes to the Entry's UI elements)
- Test on both platforms to ensure icons are positioned correctly and the password toggle works as expected
内容的提问来源于stack exchange,提问作者Phill

