[RenderWith (typeof (ButtonRenderer))]
internal class _ButtonRenderer { }
+#if __ANDROID__
+ [RenderWith(typeof(ImageButtonRenderer))]
+#elif !TIZEN4_0
+ [RenderWith(typeof(ImageButtonRenderer))]
+#endif
+ internal class _ImageButtonRenderer { }
+
[RenderWith (typeof (TableViewRenderer))]
internal class _TableViewRenderer { }
<Compile Include="_60122ImageRenderer.cs" />
<Content Include="Assets\Fonts\OFL.txt" />
<Content Include="bank.png" />
+ <Content Include="calculator.png" />
<Content Include="coffee.png" />
<Content Include="cover1.jpg" />
<Content Include="cover1small.jpg" />
<Content Include="crimson.jpg" />
<Content Include="crimsonsmall.jpg" />
<Content Include="default.css" />
+ <Content Include="Fruits.jpg" />
<Content Include="invalidimage.jpg" />
<Content Include="local.html" />
<Content Include="test.jpg" />
--- /dev/null
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+using System;
+using System.Linq;
+using System.Threading;
+using System.Collections.Generic;
+using Xamarin.Forms.PlatformConfiguration;
+using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 1724, "[Enhancement] ImageButton", PlatformAffected.All)]
+ public class Issue1724 : TestContentPage
+ {
+ bool animation = false;
+
+ protected override void Init()
+ {
+ int sizes = 200;
+ var radius = new ImageButton()
+ {
+ BorderColor = Color.Brown,
+ BorderWidth = 5,
+ BackgroundColor = Color.Yellow,
+ Aspect = Aspect.Fill,
+ CornerRadius = 10,
+ Source = "coffee.png",
+ HorizontalOptions = LayoutOptions.Center,
+ HeightRequest = sizes,
+ WidthRequest = sizes,
+
+ };
+
+ radius.On<Android>().SetShadowColor(Color.Green);
+ radius.On<Android>().SetIsShadowEnabled(true);
+ radius.On<Android>().SetShadowOffset(new Size(25, 25));
+
+ var radiusBackground = new ImageButton()
+ {
+ BorderColor = Color.Brown,
+ BorderWidth = 5,
+ Source = "coffee.png",
+ CornerRadius = 10,
+ HorizontalOptions = LayoutOptions.Center,
+ BackgroundColor = Color.Pink,
+ HeightRequest = sizes,
+ WidthRequest = sizes
+ };
+
+ radiusBackground.On<Android>().SetShadowColor(Color.Green);
+ radiusBackground.On<Android>().SetIsShadowEnabled(true);
+ radiusBackground.On<Android>().SetShadowOffset(new Size(5, 5));
+
+
+ StackLayout layout = new StackLayout()
+ {
+ Children =
+ {
+ new Label(){ Text = "No padding?" },
+ new ImageButton()
+ {
+ Source = "coffee.png",
+ BackgroundColor = Color.GreenYellow
+ },
+ new Label(){ Text = "Do I have left padding? I should have left padding." },
+ new ImageButton()
+ {
+ Source = "coffee.png",
+ BackgroundColor = Color.Green,
+ Padding = new Thickness(100, 0, 0, 0)
+ },
+ new Label(){ Text = "Do I have top padding? I should have top padding." },
+ new ImageButton()
+ {
+ Source = "coffee.png",
+ BackgroundColor = Color.LawnGreen,
+ Padding = new Thickness(0, 30, 0, 0)
+ },
+ new Label(){ Text = "Do I have right padding? I should have right padding."},
+ new ImageButton()
+ {
+ Source = "coffee.png",
+ BackgroundColor = Color.LightGreen,
+ Padding = new Thickness(0, 0, 100, 0)
+ },
+ new Label(){ Text = "Do I have bottom padding? I should have bottom padding." },
+ new ImageButton()
+ {
+ Source = "coffee.png",
+ BackgroundColor = Color.ForestGreen,
+ Padding = new Thickness(0, 0, 0, 30)
+ },
+ new Label(){ Text = "Do you see image from a Uri?" },
+ new ImageButton()
+ {
+ Source = "https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Xamarin.Forms.Controls/coffee.png",
+ BackgroundColor = Color.ForestGreen
+ },
+ new Label(){ Text = "Invalid Image Uri just to test it doesn't crash" },
+ new ImageButton()
+ {
+ Source = "http://xamarin.com/imginvalidf@#$R(P&fb.png",
+ BackgroundColor = Color.ForestGreen
+ },
+ new Label(){ Text = "Aspect: Aspect.Fill with shadows" },
+ radius,
+ new Label(){ Text = "Aspect: Aspect.AspectFit with shadows" },
+ radiusBackground,
+ new Label(){ Text = "BorderColor:Color.Green, BorderWidth:10" },
+ new ImageButton()
+ {
+ Source = "coffee.png",
+ HorizontalOptions = LayoutOptions.Center,
+ HeightRequest = sizes,
+ WidthRequest = sizes,
+ BorderColor = Color.Green,
+ BorderWidth = 10
+ },
+ new Label(){ Text = "BorderColor:Color.Green, BorderWidth:10, Aspect:Aspect.Fill" },
+ new ImageButton()
+ {
+ Source = "coffee.png",
+ HorizontalOptions = LayoutOptions.Center,
+ HeightRequest = sizes,
+ WidthRequest = sizes,
+ BorderColor = Color.Green,
+ BorderWidth = 10,
+ Aspect = Aspect.Fill
+ },
+ new Label(){ Text = "BackgroundColor:Green" },
+ new ImageButton()
+ {
+ Source = "coffee.png",
+ HorizontalOptions = LayoutOptions.Center,
+ HeightRequest = sizes,
+ WidthRequest = sizes,
+ BackgroundColor = Color.Green
+ },
+ new Label(){ Text = "BorderWidth: 5, CornerRadius:10, BorderColor:Brown" },
+ new ImageButton()
+ {
+ BorderColor = Color.Brown,
+ BorderWidth = 5,
+ Source = "coffee.png",
+ CornerRadius = 10,
+ HorizontalOptions = LayoutOptions.Center,
+ HeightRequest = sizes,
+ WidthRequest = sizes
+ }
+ }
+ };
+
+ var buttons = layout.Children.OfType<ImageButton>();
+ layout.Children.Insert(0, ActionGrid(buttons.ToList()));
+ PaddingAnimation(buttons).Start();
+
+ Content = new ScrollView() { Content = layout };
+ }
+
+ Grid ActionGrid(List<ImageButton> buttons)
+ {
+ ImageButton firstButton = buttons.FirstOrDefault();
+ Grid actionGrid = new Grid();
+ actionGrid.AddChild(new Button()
+ {
+ Text = "Add Right",
+ Command = new Command(() =>
+ {
+ var button = firstButton;
+ button.Padding = new Thickness(button.Padding.Left, 0, button.Padding.Right + 10, 0);
+ })
+ }, 0, 0);
+ actionGrid.AddChild(new Button()
+ {
+ Text = "Add Left",
+ Command = new Command(() =>
+ {
+ var button = firstButton;
+ button.Padding = new Thickness(button.Padding.Left + 10, 0, button.Padding.Right, 0);
+ })
+ }, 0, 1);
+
+ actionGrid.AddChild(new Button()
+ {
+ Text = "Animation",
+ Command = new Command(() => animation = !animation)
+ }, 1, 1);
+ actionGrid.AddChild(new Button()
+ {
+ Text = "Add Top",
+ Command = new Command(() =>
+ {
+ var button = firstButton;
+ button.Padding = new Thickness(0, button.Padding.Top + 10, 0, button.Padding.Bottom);
+ })
+ }, 2, 0);
+ actionGrid.AddChild(new Button()
+ {
+ Text = "Add Bottom",
+ Command = new Command(() =>
+ {
+ var button = firstButton;
+ button.Padding = new Thickness(0, button.Padding.Top, 0, button.Padding.Bottom + 10);
+ })
+ }, 2, 1);
+ return actionGrid;
+ }
+
+ Thread PaddingAnimation(IEnumerable<ImageButton> buttons)
+ {
+ return new Thread(() =>
+ {
+ int increment = 1;
+ int current = 0;
+ int max = 15;
+ int FPS = 30;
+ int sleep = 1000 / FPS;
+
+ while (true)
+ {
+ Thread.Sleep(sleep);
+
+ if (!animation)
+ continue;
+
+ current += increment;
+ if (current > max || current < 0)
+ {
+ increment *= -1;
+ current += increment * 2;
+ }
+
+ Device.BeginInvokeOnMainThread(() =>
+ {
+ foreach (var button in buttons)
+ {
+ var padding = button.Padding;
+ button.Padding = padding = new Thickness(
+ padding.Left + increment,
+ padding.Top + increment,
+ padding.Right + increment,
+ padding.Bottom + increment);
+ }
+ });
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
<Compile Include="$(MSBuildThisFileDirectory)Issue3306.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue3308.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue3788.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Issue1724.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue3524.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue2004.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue3333.cs" />
new GalleryPageFactory(() => new EditorCoreGalleryPage(), "Editor Gallery"),
new GalleryPageFactory(() => new FrameCoreGalleryPage(), "Frame Gallery"),
new GalleryPageFactory(() => new ImageCoreGalleryPage(), "Image Gallery"),
+ new GalleryPageFactory(() => new ImageButtonCoreGalleryPage(), "Image Button Gallery"),
new GalleryPageFactory(() => new KeyboardCoreGallery(), "Keyboard Gallery"),
new GalleryPageFactory(() => new LabelCoreGalleryPage(), "Label Gallery"),
new GalleryPageFactory(() => new ListViewCoreGalleryPage(), "ListView Gallery"),
);
clickedContainer.View.Clicked += (sender, args) => clickedContainer.EventFired ();
+ var pressedContainer = new EventViewContainer<Button>(Test.Button.Pressed,
+ new Button
+ {
+ Text = "Pressed"
+ }
+ );
+ pressedContainer.View.Pressed += (sender, args) => pressedContainer.EventFired();
+
var commandContainer = new ViewContainer<Button> (Test.Button.Command,
new Button {
Text = "Command",
Add (borderRadiusContainer);
Add (borderWidthContainer);
Add (clickedContainer);
+ Add(pressedContainer);
Add (commandContainer);
Add (fontContainer);
Add (imageContainer);
--- /dev/null
+using System;
+
+using Xamarin.Forms.CustomAttributes;
+
+namespace Xamarin.Forms.Controls
+{
+ internal class ImageButtonCoreGalleryPage : CoreGalleryPage<ImageButton>
+ {
+ protected override bool SupportsFocus => false;
+
+ protected override bool SupportsTapGestureRecognizer => false;
+
+ protected override void InitializeElement(ImageButton element)
+ {
+ element.Source = "oasissmall.jpg";
+ }
+
+ protected override void Build(StackLayout stackLayout)
+ {
+ base.Build(stackLayout);
+
+
+ IsEnabledStateViewContainer.View.Clicked += (sender, args) =>
+ {
+ IsEnabledStateViewContainer.TitleLabel.Text += " (Tapped)";
+ };
+
+ var aspectFillContainer = new ViewContainer<ImageButton>(Test.ImageButton.AspectFill, new ImageButton { Aspect = Aspect.AspectFill });
+ var aspectFitContainer = new ViewContainer<ImageButton>(Test.ImageButton.AspectFit, new ImageButton { Aspect = Aspect.AspectFit });
+ var fillContainer = new ViewContainer<ImageButton>(Test.ImageButton.Fill, new ImageButton { Aspect = Aspect.Fill });
+ var isLoadingContainer = new StateViewContainer<ImageButton>(Test.ImageButton.IsLoading, new ImageButton());
+ var isOpaqueContainer = new StateViewContainer<ImageButton>(Test.ImageButton.IsOpaque, new ImageButton());
+
+
+ var borderButtonContainer = new ViewContainer<ImageButton>(Test.ImageButton.BorderColor,
+ new ImageButton
+ {
+ BackgroundColor = Color.Transparent,
+ BorderColor = Color.Red,
+ BorderWidth = 1,
+ Source = "oasissmall.jpg"
+ }
+ );
+
+ var corderRadiusContainer = new ViewContainer<ImageButton>(Test.ImageButton.CornerRadius,
+ new ImageButton
+ {
+ Source = "oasissmall.jpg",
+ BackgroundColor = Color.Transparent,
+ BorderColor = Color.Red,
+ CornerRadius = 20,
+ BorderWidth = 1,
+ }
+ );
+
+ var borderWidthContainer = new ViewContainer<ImageButton>(Test.ImageButton.BorderWidth,
+ new ImageButton
+ {
+ Source = "oasissmall.jpg",
+ BackgroundColor = Color.Transparent,
+ BorderColor = Color.Red,
+ BorderWidth = 15,
+ }
+ );
+
+ var clickedContainer = new EventViewContainer<ImageButton>(Test.ImageButton.Clicked,
+ new ImageButton
+ {
+ Source = "oasissmall.jpg"
+ }
+ );
+ clickedContainer.View.Clicked += (sender, args) => clickedContainer.EventFired();
+
+ var pressedContainer = new EventViewContainer<ImageButton>(Test.ImageButton.Pressed,
+ new ImageButton
+ {
+ Source = "oasissmall.jpg"
+ }
+ );
+ pressedContainer.View.Pressed += (sender, args) => pressedContainer.EventFired();
+
+ var commandContainer = new ViewContainer<ImageButton>(Test.ImageButton.Command,
+ new ImageButton
+ {
+ Command = new Command(() => DisplayActionSheet("Hello Command", "Cancel", "Destroy")),
+ Source = "oasissmall.jpg"
+ }
+ );
+
+ var imageContainer = new ViewContainer<ImageButton>(Test.ImageButton.Image,
+ new ImageButton
+ {
+ Source = new FileImageSource { File = "bank.png" }
+ }
+ );
+
+
+ InitializeElement(aspectFillContainer.View);
+ InitializeElement(aspectFitContainer.View);
+ InitializeElement(fillContainer.View);
+ InitializeElement(isLoadingContainer.View);
+ InitializeElement(isOpaqueContainer.View);
+
+ var sourceContainer = new ViewContainer<ImageButton>(Test.ImageButton.Source, new ImageButton { Source = "https://raw.githubusercontent.com/xamarin/Xamarin.Forms/master/Xamarin.Forms.Controls/coffee.png" });
+
+ Add(aspectFillContainer);
+ Add(aspectFitContainer);
+ Add(fillContainer);
+ Add(isLoadingContainer);
+ Add(isOpaqueContainer);
+ Add(sourceContainer);
+
+ Add(borderButtonContainer);
+ Add(borderWidthContainer);
+ Add(clickedContainer);
+ Add(commandContainer);
+ Add(corderRadiusContainer);
+ Add(imageContainer);
+ Add(pressedContainer);
+ }
+ }
+}
\ No newline at end of file
{ Button.FontProperty, Tuple.Create (new[] { "titleLabel", "font" }, false) },
{ Button.TextProperty, Tuple.Create (new[] { "titleLabel", "text" }, false) },
{ Button.TextColorProperty, Tuple.Create (new[] { "titleLabel", "textColor" }, false) },
+ { ImageButton.CornerRadiusProperty, Tuple.Create (new[] { "layer", "cornerRadius" }, false) },
+ { ImageButton.BorderWidthProperty, Tuple.Create (new[] { "layer", "borderWidth" }, false) },
{ View.AnchorXProperty, Tuple.Create (new[] { "layer", "transform" }, true) },
{ View.AnchorYProperty, Tuple.Create (new[] { "layer", "transform" }, true) },
{ View.BackgroundColorProperty, Tuple.Create (new[] { "backgroundColor" }, false) },
{
{ ActivityIndicator.ColorProperty, Tuple.Create(new[] { "getProgressDrawable", "getColor" }, false) },
{ ActivityIndicator.IsRunningProperty, Tuple.Create(new[] { "isIndeterminate" }, false) },
- { Button.BorderColorProperty, Tuple.Create(new[] { "getBackground" }, false) },
+ { BorderElement.BorderColorProperty, Tuple.Create(new[] { "getBackground" }, false) },
{ Button.CornerRadiusProperty, Tuple.Create(new[] { "getBackground" }, false) },
{ Button.BorderWidthProperty, Tuple.Create(new[] { "getBackground" }, false) },
{ Button.ImageProperty, Tuple.Create(new[] { "getBackground" }, false) },
{ Button.FontProperty, Tuple.Create(new[] { "getTypeface", "isBold" }, false) },
{ Button.TextProperty, Tuple.Create(new[] { "getText" }, false) },
{ Button.TextColorProperty, Tuple.Create(new[] { "getCurrentTextColor" }, false) },
+ { ImageButton.CornerRadiusProperty, Tuple.Create(new[] { "getBackground" }, false) },
+ { ImageButton.BorderWidthProperty, Tuple.Create(new[] { "getBackground" }, false) },
+ { ImageButton.SourceProperty, Tuple.Create(new[] { "getBackground" }, false) },
{ View.AnchorXProperty, Tuple.Create(new[] { "getPivotX" }, true) },
{ View.AnchorYProperty, Tuple.Create(new[] { "getPivotY" }, true) },
{ View.BackgroundColorProperty, Tuple.Create(new[] { "getBackground", "getColor" }, true) },
public static readonly string Entry = "UITextField";
public static readonly string Frame = "view:'Xamarin_Forms_Platform_iOS_FrameRenderer'";
public static readonly string Image = "UIImageView";
+ public static readonly string ImageButton = "UIButton";
public static readonly string Label = "UILabel";
public static readonly string ListView = "UITableView";
public static readonly string Map = "MKMapView";
public static readonly string Entry = "xamarin.forms.platform.android.EntryEditText";
public static readonly string Frame = "xamarin.forms.platform.android.appcompat.FrameRenderer";
public static readonly string Image = "android.widget.ImageView";
+ public static readonly string ImageButton = "android.widget.ImageButton";
public static readonly string Label = "android.widget.TextView";
public static readonly string ListView = "android.widget.ListView";
public static readonly string Map = "android.gms.maps.GoogleMap";
using Xamarin.UITest.Queries;
namespace Xamarin.Forms.Core.UITests
-{
+{
internal static class GalleryQueries
{
public const string AutomationIDGallery = "* marked:'AutomationID Gallery'";
public const string EntryGallery = "* marked:'Entry Gallery'";
public const string FrameGallery = "* marked:'Frame Gallery'";
public const string ImageGallery = "* marked:'Image Gallery'";
+ public const string ImageButtonGallery = "* marked:'Image Button Gallery'";
public const string LabelGallery = "* marked:'Label Gallery'";
public const string ListViewGallery = "* marked:'ListView Gallery'";
public const string OpenGLViewGallery = "* marked:'OpenGLView Gallery'";
{
#region Platform queries
- public static Func<AppQuery, AppQuery> NavigationBarBackButton ()
+ public static Func<AppQuery, AppQuery> NavigationBarBackButton()
{
return PlatformQueries.NavigationBarBackButton;
}
- public static Func<AppQuery, AppQuery> PageWithoutNavigationBar ()
+ public static Func<AppQuery, AppQuery> PageWithoutNavigationBar()
{
return PlatformQueries.PageWithoutNavigationBar;
}
- public static Func<AppQuery, AppQuery> Root ()
+ public static Func<AppQuery, AppQuery> Root()
{
return PlatformQueries.Root;
}
public static readonly string Entry = PlatformViews.Entry;
public static readonly string Frame = PlatformViews.Frame;
public static readonly string Image = PlatformViews.Image;
+ public static readonly string ImageButton = PlatformViews.ImageButton;
public static readonly string Label = PlatformViews.Label;
public static readonly string ListView = PlatformViews.ListView;
public static readonly string Map = PlatformViews.Map;
internal static class Rects
{
- public static AppRect RootViewRect (this IApp app)
+ public static AppRect RootViewRect(this IApp app)
{
#if __WINDOWS__
return app.Query(WinDriverApp.AppName)[0].Rect;
#else
- return app.Query (q => q.Raw ("* index:0"))[0].Rect;
+ return app.Query(q => q.Raw("* index:0"))[0].Rect;
#endif
}
}
App.Tap(q => q.Raw(ViewQuery));
}
+ public void TouchAndHoldView()
+ {
+ App.TouchAndHold(q => q.Raw(ViewQuery));
+ }
+
public void DismissPopOver()
{
App.Screenshot("About to dismiss pop over");
--- /dev/null
+using System.Threading;
+using NUnit.Framework;
+using Xamarin.Forms.CustomAttributes;
+
+namespace Xamarin.Forms.Core.UITests
+{
+ [TestFixture]
+ [Category(UITestCategories.Image)]
+ internal class ImageButtonUITests : _ViewUITests
+ {
+ public ImageButtonUITests()
+ {
+ PlatformViewType = Views.ImageButton;
+ }
+
+ protected override void NavigateToGallery()
+ {
+ App.NavigateToGallery(GalleryQueries.ImageButtonGallery);
+
+ // let remote images load
+ Thread.Sleep(2000);
+ }
+
+ [UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
+ public override void _Focus()
+ {
+ }
+
+ // TODO
+ public override void _GestureRecognizers()
+ {
+ }
+
+ [UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
+ public override void _IsFocused()
+ {
+ }
+
+ [UiTestExempt(ExemptReason.CannotTest, "Invalid interaction")]
+ public override void _UnFocus()
+ {
+ }
+
+ // TODO
+ // Implement control specific ui tests
+
+ // TODO
+ // Tests for remote images
+
+ protected override void FixtureTeardown()
+ {
+ App.NavigateBack();
+ base.FixtureTeardown();
+ }
+
+
+
+
+ // ImageButton Tests
+ [Test]
+ [UiTest(typeof(ImageButton), "BorderColor")]
+ [UiTestBroken(BrokenReason.CalabashAndroidUnsupported, "Figure out how to get Android Drawables")]
+ [UiTestBroken(BrokenReason.CalabashiOSUnsupported, "iOS nil result")]
+ public void BorderColor()
+ {
+ //TODO iOS
+ var remote = new ViewContainerRemote(App, Test.ImageButton.BorderColor, PlatformViewType);
+ remote.GoTo();
+ }
+
+ [Test]
+ [UiTest(typeof(ImageButton), "BorderWidth")]
+ [UiTestBroken(BrokenReason.CalabashAndroidUnsupported, "Figure out how to get Android Drawables")]
+ public void BorderWidth()
+ {
+ var remote = new ViewContainerRemote(App, Test.ImageButton.BorderWidth, PlatformViewType);
+ remote.GoTo();
+
+#if __IOS__
+ var borderWidth = remote.GetProperty<float>(ImageButton.BorderWidthProperty);
+ Assert.AreEqual(15.0f, borderWidth);
+#endif
+ }
+
+ [Test]
+ [UiTest(typeof(ImageButton), "Clicked")]
+ public void Clicked()
+ {
+ var remote = new EventViewContainerRemote(App, Test.ImageButton.Clicked, PlatformViewType);
+ remote.GoTo();
+
+ var textBeforeClick = remote.GetEventLabel().Text;
+ Assert.AreEqual("Event: Clicked (none)", textBeforeClick);
+
+ // Click ImageButton
+ remote.TapView();
+
+ var textAfterClick = remote.GetEventLabel().Text;
+ Assert.AreEqual("Event: Clicked (fired 1)", textAfterClick);
+ }
+
+ [Test]
+ [UiTest(typeof(ImageButton), "Pressed")]
+ public void Pressed()
+ {
+ var remote = new EventViewContainerRemote(App, Test.ImageButton.Pressed, PlatformViewType);
+ remote.GoTo();
+
+ var textBeforeClick = remote.GetEventLabel().Text;
+ Assert.AreEqual("Event: Pressed (none)", textBeforeClick);
+
+ // Press ImageButton
+ remote.TouchAndHoldView();
+
+ var textAfterClick = remote.GetEventLabel().Text;
+ Assert.AreEqual("Event: Pressed (fired 1)", textAfterClick);
+ }
+
+ [Test]
+ [UiTest(typeof(ImageButton), "Command")]
+ public void Command()
+ {
+ var remote = new ViewContainerRemote(App, Test.ImageButton.Command, PlatformViewType);
+ remote.GoTo();
+
+ remote.TapView();
+
+ App.WaitForElement(q => q.Marked("Hello Command"));
+ App.Tap(q => q.Marked("Destroy"));
+ }
+
+ [Test]
+ [UiTest(typeof(ImageButton), "CornerRadius")]
+ [UiTestBroken(BrokenReason.CalabashAndroidUnsupported, "Figure out how to get Android Drawables")]
+ public void CornerRadius()
+ {
+ var remote = new ViewContainerRemote(App, Test.ImageButton.CornerRadius, PlatformViewType);
+ remote.GoTo();
+
+#if __IOS__
+ var cornerRadius = remote.GetProperty<float>(ImageButton.CornerRadiusProperty);
+ Assert.AreEqual(20.0f, cornerRadius);
+#endif
+ }
+
+ [Test]
+ [UiTest(typeof(ImageButton), "Image")]
+ [UiTestExempt(ExemptReason.TimeConsuming, "Need way to check Android resources")]
+ public void Image()
+ {
+ //TODO iOS
+ var remote = new ViewContainerRemote(App, Test.ImageButton.Image, PlatformViewType);
+ remote.GoTo();
+ }
+ }
+}
\ No newline at end of file
<Compile Include="$(MSBuildThisFileDirectory)Tests\EditorUITests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Tests\EntryUITests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Tests\FrameUITests.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Tests\ImageButtonUITests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Tests\ImageUITests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Tests\LabelUITests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Tests\Legacy-CellsUITests.cs" />
--- /dev/null
+using System;
+using NUnit.Framework;
+using System.IO;
+using System.Threading.Tasks;
+using System.Threading;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ImageButtonTests
+ : CommandSourceTests<ImageButton>
+ {
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup();
+ Device.PlatformServices = new MockPlatformServices(getStreamAsync: GetStreamAsync);
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void TestSizing()
+ {
+ var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+ var result = image.GetSizeRequest(double.PositiveInfinity, double.PositiveInfinity);
+
+ Assert.AreEqual(100, result.Request.Width);
+ Assert.AreEqual(20, result.Request.Height);
+ }
+
+ [Test]
+ public void TestAspectSizingWithConstrainedHeight()
+ {
+ var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+ var result = image.GetSizeRequest(double.PositiveInfinity, 10);
+
+ Assert.AreEqual(50, result.Request.Width);
+ Assert.AreEqual(10, result.Request.Height);
+ }
+
+ [Test]
+ public void TestAspectSizingWithConstrainedWidth()
+ {
+ var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+ var result = image.GetSizeRequest(25, double.PositiveInfinity);
+
+ Assert.AreEqual(25, result.Request.Width);
+ Assert.AreEqual(5, result.Request.Height);
+ }
+
+ [Test]
+ public void TestAspectFillSizingWithConstrainedHeight()
+ {
+ var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+ image.Aspect = Aspect.AspectFill;
+ var result = image.GetSizeRequest(double.PositiveInfinity, 10);
+
+ Assert.AreEqual(50, result.Request.Width);
+ Assert.AreEqual(10, result.Request.Height);
+ }
+
+ [Test]
+ public void TestAspectFillSizingWithConstrainedWidth()
+ {
+ var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+ image.Aspect = Aspect.AspectFill;
+ var result = image.GetSizeRequest(25, double.PositiveInfinity);
+
+ Assert.AreEqual(25, result.Request.Width);
+ Assert.AreEqual(5, result.Request.Height);
+ }
+
+ [Test]
+ public void TestFillSizingWithConstrainedHeight()
+ {
+ var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+ image.Aspect = Aspect.AspectFill;
+ var result = image.GetSizeRequest(double.PositiveInfinity, 10);
+
+ Assert.AreEqual(50, result.Request.Width);
+ Assert.AreEqual(10, result.Request.Height);
+ }
+
+ [Test]
+ public void TestFillSizingWithConstrainedWidth()
+ {
+ var image = new ImageButton { Source = ImageSource.FromFile("File.png"), Platform = new UnitPlatform(), IsPlatformEnabled = true };
+
+ image.Aspect = Aspect.AspectFill;
+ var result = image.GetSizeRequest(25, double.PositiveInfinity);
+
+ Assert.AreEqual(25, result.Request.Width);
+ Assert.AreEqual(5, result.Request.Height);
+ }
+
+ [Test]
+ public void TestSizeChanged()
+ {
+ var image = new ImageButton { Source = "File0.png" };
+ Assert.AreEqual("File0.png", ((FileImageSource)image.Source).File);
+
+ var preferredSizeChanged = false;
+ image.MeasureInvalidated += (sender, args) => preferredSizeChanged = true;
+
+ image.Source = "File1.png";
+ Assert.AreEqual("File1.png", ((FileImageSource)image.Source).File);
+ Assert.True(preferredSizeChanged);
+ }
+
+ [Test]
+ public void TestSource()
+ {
+ var image = new ImageButton();
+
+ Assert.IsNull(image.Source);
+
+ bool signaled = false;
+ image.PropertyChanged += (sender, e) =>
+ {
+ if (e.PropertyName == "Source")
+ signaled = true;
+ };
+
+ var source = ImageSource.FromFile("File.png");
+ image.Source = source;
+
+ Assert.AreEqual(source, image.Source);
+ Assert.True(signaled);
+ }
+
+ [Test]
+ public void TestSourceDoubleSet()
+ {
+ var image = new ImageButton { Source = ImageSource.FromFile("File.png") };
+
+ bool signaled = false;
+ image.PropertyChanged += (sender, e) =>
+ {
+ if (e.PropertyName == "Source")
+ signaled = true;
+ };
+
+ image.Source = image.Source;
+
+ Assert.False(signaled);
+ }
+
+ [Test]
+ public void TestFileImageSourceChanged()
+ {
+ var source = (FileImageSource)ImageSource.FromFile("File.png");
+
+ bool signaled = false;
+ source.SourceChanged += (sender, e) =>
+ {
+ signaled = true;
+ };
+
+ source.File = "Other.png";
+ Assert.AreEqual("Other.png", source.File);
+
+ Assert.True(signaled);
+ }
+
+ [Test]
+ public void TestFileImageSourcePropertiesChangedTriggerResize()
+ {
+ var source = new FileImageSource();
+ var image = new ImageButton { Source = source };
+ bool fired = false;
+ image.MeasureInvalidated += (sender, e) => fired = true;
+ Assert.Null(source.File);
+ source.File = "foo.png";
+ Assert.NotNull(source.File);
+ Assert.True(fired);
+ }
+
+ [Test]
+ public void TestStreamImageSourcePropertiesChangedTriggerResize()
+ {
+ var source = new StreamImageSource();
+ var image = new ImageButton { Source = source };
+ bool fired = false;
+ image.MeasureInvalidated += (sender, e) => fired = true;
+ Assert.Null(source.Stream);
+ source.Stream = token => Task.FromResult<Stream>(null);
+ Assert.NotNull(source.Stream);
+ Assert.True(fired);
+ }
+
+ [Test]
+ public void TestImageSourceToNullCancelsLoading()
+ {
+ var image = new ImageButton();
+ var mockImageRenderer = new MockImageRenderer(image);
+ var loader = new UriImageSource { Uri = new Uri("http://www.public-domain-image.com/free-images/miscellaneous/big-high-border-fence.jpg") };
+ image.Source = loader;
+ Assert.IsTrue(image.IsLoading);
+ image.Source = null;
+ Assert.IsFalse(image.IsLoading);
+ Assert.IsTrue(cancelled);
+ }
+
+ static bool cancelled;
+
+ static async Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken)
+ {
+ try
+ {
+ await Task.Delay(5000, cancellationToken);
+ }
+ catch (TaskCanceledException ex)
+ {
+ cancelled = true;
+ throw ex;
+ }
+
+ if (cancellationToken.IsCancellationRequested)
+ {
+ cancelled = true;
+ throw new TaskCanceledException();
+ }
+
+ var stream = typeof(ImageTests).Assembly.GetManifestResourceStream(uri.LocalPath.Substring(1));
+ return stream;
+ }
+
+ class MockImageRenderer
+ {
+ public MockImageRenderer(ImageButton element)
+ {
+ Element = element;
+ Element.PropertyChanged += (sender, e) =>
+ {
+ if (e.PropertyName == nameof(ImageButton.Source))
+ Load();
+ };
+ }
+
+ public ImageButton Element { get; set; }
+
+ public async void Load()
+ {
+ if (initialLoad && Element.Source != null)
+ {
+ initialLoad = false;
+ Element.SetIsLoading(true);
+ await (Element.Source as UriImageSource).GetStreamAsync();
+ Element.SetIsLoading(false);
+ }
+ }
+
+ bool initialLoad = true;
+ }
+
+ [Test]
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestClickedvent(bool isEnabled)
+ {
+ var view = new ImageButton()
+ {
+ IsEnabled = isEnabled,
+ };
+
+ bool activated = false;
+ view.Clicked += (sender, e) => activated = true;
+
+ ((IButtonController)view).SendClicked();
+
+ Assert.True(activated == isEnabled ? true : false);
+ }
+
+ [Test]
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestPressedEvent(bool isEnabled)
+ {
+ var view = new ImageButton()
+ {
+ IsEnabled = isEnabled,
+ };
+
+ bool pressed = false;
+ view.Pressed += (sender, e) => pressed = true;
+
+ ((IButtonController)view).SendPressed();
+
+ Assert.True(pressed == isEnabled ? true : false);
+ }
+
+ [Test]
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestReleasedEvent(bool isEnabled)
+ {
+ var view = new ImageButton()
+ {
+ IsEnabled = isEnabled,
+ };
+
+ bool released = false;
+ view.Released += (sender, e) => released = true;
+
+ ((IButtonController)view).SendReleased();
+
+ Assert.True(released == isEnabled ? true : false);
+ }
+
+ protected override ImageButton CreateSource()
+ {
+ return new ImageButton();
+ }
+
+ protected override void Activate(ImageButton source)
+ {
+ ((IButtonController)source).SendClicked();
+ }
+
+ protected override BindableProperty IsEnabledProperty
+ {
+ get { return ImageButton.IsEnabledProperty; }
+ }
+
+ protected override BindableProperty CommandProperty
+ {
+ get { return ImageButton.CommandProperty; }
+ }
+
+ protected override BindableProperty CommandParameterProperty
+ {
+ get { return ImageButton.CommandParameterProperty; }
+ }
+
+
+ [Test]
+ public void TestBindingContextPropagation()
+ {
+ var context = new object();
+ var button = new ImageButton();
+ button.BindingContext = context;
+ var source = new FileImageSource();
+ button.Source = source;
+ Assert.AreSame(context, source.BindingContext);
+
+ button = new ImageButton();
+ source = new FileImageSource();
+ button.Source = source;
+ button.BindingContext = context;
+ Assert.AreSame(context, source.BindingContext);
+ }
+
+ [Test]
+ public void TestImageSourcePropertiesChangedTriggerResize()
+ {
+ var source = new FileImageSource();
+ var button = new ImageButton { Source = source };
+ bool fired = false;
+ button.MeasureInvalidated += (sender, e) => fired = true;
+ Assert.Null(source.File);
+ source.File = "foo.png";
+ Assert.NotNull(source.File);
+ Assert.True(fired);
+ }
+
+
+ [Test]
+ public void CommandCanExecuteUpdatesEnabled()
+ {
+ var button = new ImageButton();
+
+ bool result = false;
+
+ var bindingContext = new
+ {
+ Command = new Command(() => { }, () => result)
+ };
+
+ button.SetBinding(ImageButton.CommandProperty, "Command");
+ button.BindingContext = bindingContext;
+
+ Assert.False(button.IsEnabled);
+
+ result = true;
+
+ bindingContext.Command.ChangeCanExecute();
+
+ Assert.True(button.IsEnabled);
+ }
+
+ [Test]
+ public void ButtonClickWhenCommandCanExecuteFalse()
+ {
+ bool invoked = false;
+ var button = new ImageButton()
+ {
+ Command = new Command(() => invoked = true
+ , () => false),
+ };
+
+ (button as IButtonController)
+ ?.SendClicked();
+
+ Assert.False(invoked);
+ }
+ }
+}
<Compile Include="DependencyResolutionTests.cs" />
<Compile Include="EffectiveFlowDirectionExtensions.cs" />
<Compile Include="FlowDirectionTests.cs" />
+ <Compile Include="ImageButtonUnitTest.cs" />
<Compile Include="LayoutChildIntoBoundingRegionTests.cs" />
<Compile Include="MenuUnitTests.cs" />
<Compile Include="RegionTests.cs" />
namespace Xamarin.Forms
{
[RenderWith(typeof(_ButtonRenderer))]
- public class Button : View, IFontElement, ITextElement, IBorderElement, IButtonController, IElementConfiguration<Button>, IPaddingElement
+ public class Button : View, IFontElement, ITextElement, IBorderElement, IButtonController, IElementConfiguration<Button>, IPaddingElement, IBorderController, IImageController, IViewController
{
const double DefaultSpacing = 10;
const int DefaultBorderRadius = 5;
const int DefaultCornerRadius = -1;
- public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(Button), null, propertyChanged: (bo, o, n) => ((Button)bo).OnCommandChanged());
+ public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(Button), null, propertyChanging: OnCommandChanging, propertyChanged: OnCommandChanged);
- public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(Button), null,
- propertyChanged: (bindable, oldvalue, newvalue) => ((Button)bindable).CommandCanExecuteChanged(bindable, EventArgs.Empty));
+ public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(Button), null,
+ propertyChanged: (bindable, oldvalue, newvalue) => ButtonElementManager.CommandCanExecuteChanged(bindable, EventArgs.Empty));
public static readonly BindableProperty ContentLayoutProperty =
BindableProperty.Create("ContentLayout", typeof(ButtonContentLayout), typeof(Button), new ButtonContentLayout(ButtonContentLayout.ImagePosition.Left, DefaultSpacing));
public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create("CornerRadius", typeof(int), typeof(Button), defaultValue: DefaultCornerRadius,
propertyChanged: CornerRadiusPropertyChanged);
- public static readonly BindableProperty ImageProperty = BindableProperty.Create("Image", typeof(FileImageSource), typeof(Button), default(FileImageSource),
- propertyChanging: (bindable, oldvalue, newvalue) => ((Button)bindable).OnSourcePropertyChanging((ImageSource)oldvalue, (ImageSource)newvalue),
- propertyChanged: (bindable, oldvalue, newvalue) => ((Button)bindable).OnSourcePropertyChanged((ImageSource)oldvalue, (ImageSource)newvalue));
+ public static readonly BindableProperty ImageProperty = BindableProperty.Create(nameof(Image), typeof(FileImageSource), typeof(Button), default(FileImageSource),
+ propertyChanging: OnImageSourceChanging,
+ propertyChanged: OnImageSourceChanged);
+
public static readonly BindableProperty PaddingProperty = PaddingElement.PaddingProperty;
public Thickness Padding
{
- get { return (Thickness)GetValue (PaddingElement.PaddingProperty); }
- set { SetValue (PaddingElement.PaddingProperty, value); }
+ get { return (Thickness)GetValue(PaddingElement.PaddingProperty); }
+ set { SetValue(PaddingElement.PaddingProperty, value); }
}
- Thickness IPaddingElement.PaddingDefaultValueCreator ()
+ Thickness IPaddingElement.PaddingDefaultValueCreator()
{
- return default (Thickness);
+ return default(Thickness);
}
- void IPaddingElement.OnPaddingPropertyChanged (Thickness oldValue, Thickness newValue)
+ void IPaddingElement.OnPaddingPropertyChanged(Thickness oldValue, Thickness newValue)
{
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
}
+
+ internal static readonly BindablePropertyKey IsPressedPropertyKey = BindableProperty.CreateReadOnly(nameof(IsPressed), typeof(bool), typeof(Button), default(bool));
+ public static readonly BindableProperty IsPressedProperty = IsPressedPropertyKey.BindableProperty;
+
+
readonly Lazy<PlatformConfigurationRegistry<Button>> _platformConfigurationRegistry;
public Color BorderColor
set { SetValue(TextElement.TextColorProperty, value); }
}
- bool IsEnabledCore
+ bool IButtonController.IsEnabledCore
{
set { SetValueCore(IsEnabledProperty, value); }
}
[EditorBrowsable(EditorBrowsableState.Never)]
- public void SendClicked()
- {
- if (IsEnabled == true)
- {
- Command?.Execute(CommandParameter);
- Clicked?.Invoke(this, EventArgs.Empty);
- }
- }
+ public void SendClicked() => ButtonElementManager.ElementClicked(this, this);
+
+ public bool IsPressed => (bool)GetValue(IsPressedProperty);
[EditorBrowsable(EditorBrowsableState.Never)]
- public void SendPressed()
- {
- if (IsEnabled == true)
- {
- Pressed?.Invoke(this, EventArgs.Empty);
- }
- }
+ void IButtonController.SetIsPressed(bool isPressed) => SetValue(IsPressedPropertyKey, isPressed);
[EditorBrowsable(EditorBrowsableState.Never)]
- public void SendReleased()
- {
- if (IsEnabled == true)
- {
- Released?.Invoke(this, EventArgs.Empty);
- }
- }
+ public void SendPressed() => ButtonElementManager.ElementPressed(this, this);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SendReleased() => ButtonElementManager.ElementReleased(this, this);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void IButtonController.PropagateUpClicked() => Clicked?.Invoke(this, EventArgs.Empty);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void IButtonController.PropagateUpPressed() => Pressed?.Invoke(this, EventArgs.Empty);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void IButtonController.PropagateUpReleased() => Released?.Invoke(this, EventArgs.Empty);
public FontAttributes FontAttributes
{
}
public event EventHandler Clicked;
+ BindableProperty IBorderController.CornerRadiusProperty => Button.CornerRadiusProperty;
+ BindableProperty IBorderController.BorderColorProperty => Button.BorderColorProperty;
+ BindableProperty IBorderController.BorderWidthProperty => Button.BorderWidthProperty;
public event EventHandler Pressed;
return _platformConfigurationRegistry.Value.On<T>();
}
+ protected internal override void ChangeVisualState()
+ {
+ if (IsEnabled && IsPressed)
+ {
+ VisualStateManager.GoToState(this, ButtonElementManager.PressedVisualState);
+ }
+ else
+ {
+ base.ChangeVisualState();
+ }
+ }
+
protected override void OnBindingContextChanged()
{
FileImageSource image = Image;
base.OnBindingContextChanged();
}
- protected override void OnPropertyChanging(string propertyName = null)
- {
- if (propertyName == CommandProperty.PropertyName)
- {
- ICommand cmd = Command;
- if (cmd != null)
- cmd.CanExecuteChanged -= CommandCanExecuteChanged;
- }
-
- base.OnPropertyChanging(propertyName);
- }
-
- void CommandCanExecuteChanged(object sender, EventArgs eventArgs)
- {
- ICommand cmd = Command;
- if (cmd != null)
- IsEnabledCore = cmd.CanExecute(CommandParameter);
- }
-
void IFontElement.OnFontFamilyChanged(string oldValue, string newValue) =>
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
void IFontElement.OnFontChanged(Font oldValue, Font newValue) =>
InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
- void OnCommandChanged()
- {
- if (Command != null)
- {
- Command.CanExecuteChanged += CommandCanExecuteChanged;
- CommandCanExecuteChanged(this, EventArgs.Empty);
- }
- else
- IsEnabledCore = true;
- }
+ Aspect IImageController.Aspect => Aspect.AspectFit;
+ ImageSource IImageController.Source => Image;
+ bool IImageController.IsOpaque => false;
- void OnSourceChanged(object sender, EventArgs eventArgs)
- {
- OnPropertyChanged(ImageProperty.PropertyName);
- InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
- }
+ BindableProperty IImageController.SourceProperty => ImageProperty;
+ BindableProperty IImageController.AspectProperty => null;
+ BindableProperty IImageController.IsOpaqueProperty => null;
- void OnSourcePropertyChanged(ImageSource oldvalue, ImageSource newvalue)
- {
- if (newvalue != null)
- {
- newvalue.SourceChanged += OnSourceChanged;
- SetInheritedBindingContext(newvalue, BindingContext);
- }
- InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
- }
- void OnSourcePropertyChanging(ImageSource oldvalue, ImageSource newvalue)
- {
- if (oldvalue != null)
- oldvalue.SourceChanged -= OnSourceChanged;
- }
+ void IImageController.RaiseImageSourcePropertyChanged() => OnPropertyChanged(ImageProperty.PropertyName);
+
/// <summary>
/// Flag to prevent overwriting the value of CornerRadius
{
}
+ void OnImageSourcesSourceChanged(object sender, EventArgs e) =>
+ ImageElementManager.ImageSourcesSourceChanged(this, EventArgs.Empty);
+
+ static void OnImageSourceChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ ImageSource newSource = (ImageSource)newValue;
+ Button button = (Button)bindable;
+ if (newSource != null)
+ {
+ newSource.SourceChanged += button.OnImageSourcesSourceChanged;
+ }
+ ImageElementManager.ImageSourceChanged(bindable, newSource);
+ }
+
+ static void OnImageSourceChanging(BindableObject bindable, object oldValue, object newValue)
+ {
+ ImageSource oldSource = (ImageSource)oldValue;
+ Button button = (Button)bindable;
+
+ if (oldSource != null)
+ {
+ oldSource.SourceChanged -= button.OnImageSourcesSourceChanged;
+ }
+ ImageElementManager.ImageSourceChanging(oldSource);
+ }
+
+
+ void OnCommandCanExecuteChanged(object sender, EventArgs e) =>
+ ButtonElementManager.CommandCanExecuteChanged(this, EventArgs.Empty);
+
+ static void OnCommandChanged(BindableObject bo, object o, object n)
+ {
+ var button = (Button)bo;
+ if (n is ICommand newCommand)
+ newCommand.CanExecuteChanged += button.OnCommandCanExecuteChanged;
+
+ ButtonElementManager.CommandChanged(button);
+ }
+
+ static void OnCommandChanging(BindableObject bo, object o, object n)
+ {
+ var button = (Button)bo;
+ if (o != null)
+ {
+ (o as ICommand).CanExecuteChanged -= button.OnCommandCanExecuteChanged;
+ }
+ }
+
+ void IImageController.SetIsLoading(bool isLoading)
+ {
+ }
+
[DebuggerDisplay("Image Position = {Position}, Spacing = {Spacing}")]
[TypeConverter(typeof(ButtonContentTypeConverter))]
public sealed class ButtonContentLayout
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Windows.Input;
+
+namespace Xamarin.Forms
+{
+ internal static class ButtonElementManager
+ {
+ public const string PressedVisualState = "Pressed";
+
+ public static void CommandChanged(IButtonController sender)
+ {
+ if (sender.Command != null)
+ {
+ CommandCanExecuteChanged(sender, EventArgs.Empty);
+ }
+ else
+ {
+ sender.IsEnabledCore = true;
+ }
+ }
+
+ public static void CommandCanExecuteChanged(object sender, EventArgs e)
+ {
+ IButtonController ButtonElementManager = (IButtonController)sender;
+ ICommand cmd = ButtonElementManager.Command;
+ if (cmd != null)
+ {
+ ButtonElementManager.IsEnabledCore = cmd.CanExecute(ButtonElementManager.CommandParameter);
+ }
+ }
+
+
+ public static void ElementClicked(VisualElement visualElement, IButtonController ButtonElementManager)
+ {
+ if (visualElement.IsEnabled == true)
+ {
+ ButtonElementManager.Command?.Execute(ButtonElementManager.CommandParameter);
+ ButtonElementManager.PropagateUpClicked();
+ }
+ }
+
+ public static void ElementPressed(VisualElement visualElement, IButtonController ButtonElementManager)
+ {
+ if (visualElement.IsEnabled == true)
+ {
+ ButtonElementManager.SetIsPressed(true);
+ visualElement.ChangeVisualStateInternal();
+ ButtonElementManager.PropagateUpPressed();
+ }
+ }
+
+ public static void ElementReleased(VisualElement visualElement, IButtonController ButtonElementManager)
+ {
+ if (visualElement.IsEnabled == true)
+ {
+ ButtonElementManager.SetIsPressed(false);
+ visualElement.ChangeVisualStateInternal();
+ ButtonElementManager.PropagateUpReleased();
+ }
+ }
+
+
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Text;
+
+namespace Xamarin.Forms
+{
+ public interface IBorderController : INotifyPropertyChanged
+ {
+ BindableProperty CornerRadiusProperty { get; }
+ BindableProperty BorderColorProperty { get; }
+ BindableProperty BorderWidthProperty { get; }
+ int CornerRadius { get; }
+ Color BorderColor { get; }
+ Color BackgroundColor { get; }
+ double BorderWidth { get; }
+ bool IsSet(BindableProperty targetProperty);
+ }
+}
+using System;
+using System.Windows.Input;
+
namespace Xamarin.Forms
{
public interface IButtonController : IViewController
void SendClicked();
void SendPressed();
void SendReleased();
+ object CommandParameter { get; set; }
+ ICommand Command { get; set; }
+ bool IsEnabledCore { set; }
+ void PropagateUpClicked();
+ void PropagateUpPressed();
+ void PropagateUpReleased();
+ bool IsPressed { get; }
+ void SetIsPressed(bool isPressed);
+
}
}
\ No newline at end of file
+using System;
+
namespace Xamarin.Forms
{
- public interface IImageController
+ public interface IImageController : IViewController
{
void SetIsLoading(bool isLoading);
+ Aspect Aspect { get; }
+ ImageSource Source { get; }
+ bool IsOpaque { get; }
+ void RaiseImageSourcePropertyChanged();
+ BindableProperty SourceProperty { get; }
+ BindableProperty AspectProperty { get; }
+ BindableProperty IsOpaqueProperty { get; }
}
}
\ No newline at end of file
namespace Xamarin.Forms
{
[RenderWith(typeof(_ImageRenderer))]
- public class Image : View, IImageController, IElementConfiguration<Image>
+ public class Image : View, IImageController, IElementConfiguration<Image>, IViewController
{
- public static readonly BindableProperty SourceProperty = BindableProperty.Create("Source", typeof(ImageSource), typeof(Image), default(ImageSource),
- propertyChanging: OnSourcePropertyChanging, propertyChanged: OnSourcePropertyChanged);
+ public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(ImageSource), typeof(Image), default(ImageSource),
+ propertyChanging: OnImageSourceChanging, propertyChanged: OnImageSourceChanged);
- public static readonly BindableProperty AspectProperty = BindableProperty.Create("Aspect", typeof(Aspect), typeof(Image), Aspect.AspectFit);
+ public static readonly BindableProperty AspectProperty = BindableProperty.Create(nameof(Aspect), typeof(Aspect), typeof(Image), Aspect.AspectFit);
- public static readonly BindableProperty IsOpaqueProperty = BindableProperty.Create("IsOpaque", typeof(bool), typeof(Image), false);
+ public static readonly BindableProperty IsOpaqueProperty = BindableProperty.Create(nameof(IsOpaque), typeof(bool), typeof(Image), false);
- internal static readonly BindablePropertyKey IsLoadingPropertyKey = BindableProperty.CreateReadOnly("IsLoading", typeof(bool), typeof(Image), default(bool));
+ internal static readonly BindablePropertyKey IsLoadingPropertyKey = BindableProperty.CreateReadOnly(nameof(IsLoading), typeof(bool), typeof(Image), default(bool));
public static readonly BindableProperty IsLoadingProperty = IsLoadingPropertyKey.BindableProperty;
protected override void OnBindingContextChanged()
{
- if (Source != null)
- SetInheritedBindingContext(Source, BindingContext);
-
+ ImageElementManager.OnBindingContextChanged(this, this);
base.OnBindingContextChanged();
}
protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
{
SizeRequest desiredSize = base.OnSizeRequest(double.PositiveInfinity, double.PositiveInfinity);
-
- double desiredAspect = desiredSize.Request.Width / desiredSize.Request.Height;
- double constraintAspect = widthConstraint / heightConstraint;
-
- double desiredWidth = desiredSize.Request.Width;
- double desiredHeight = desiredSize.Request.Height;
-
- if (desiredWidth == 0 || desiredHeight == 0)
- return new SizeRequest(new Size(0, 0));
-
- double width = desiredWidth;
- double height = desiredHeight;
- if (constraintAspect > desiredAspect)
- {
- // constraint area is proportionally wider than image
- switch (Aspect)
- {
- case Aspect.AspectFit:
- case Aspect.AspectFill:
- height = Math.Min(desiredHeight, heightConstraint);
- width = desiredWidth * (height / desiredHeight);
- break;
- case Aspect.Fill:
- width = Math.Min(desiredWidth, widthConstraint);
- height = desiredHeight * (width / desiredWidth);
- break;
- }
- }
- else if (constraintAspect < desiredAspect)
- {
- // constraint area is proportionally taller than image
- switch (Aspect)
- {
- case Aspect.AspectFit:
- case Aspect.AspectFill:
- width = Math.Min(desiredWidth, widthConstraint);
- height = desiredHeight * (width / desiredWidth);
- break;
- case Aspect.Fill:
- height = Math.Min(desiredHeight, heightConstraint);
- width = desiredWidth * (height / desiredHeight);
- break;
- }
- }
- else
- {
- // constraint area is same aspect as image
- width = Math.Min(desiredWidth, widthConstraint);
- height = desiredHeight * (width / desiredWidth);
- }
-
- return new SizeRequest(new Size(width, height));
+ return ImageElementManager.Measure(this, desiredSize, widthConstraint, heightConstraint);
}
- void OnSourceChanged(object sender, EventArgs eventArgs)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetIsLoading(bool isLoading)
{
- OnPropertyChanged(SourceProperty.PropertyName);
- InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
+ SetValue(IsLoadingPropertyKey, isLoading);
}
- static void OnSourcePropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
+ public IPlatformElementConfiguration<T, Image> On<T>() where T : IConfigPlatform
{
- ((Image)bindable).OnSourcePropertyChanged((ImageSource)oldvalue, (ImageSource)newvalue);
+ return _platformConfigurationRegistry.Value.On<T>();
}
- void OnSourcePropertyChanged(ImageSource oldvalue, ImageSource newvalue)
+ BindableProperty IImageController.SourceProperty => SourceProperty;
+ BindableProperty IImageController.AspectProperty => AspectProperty;
+ BindableProperty IImageController.IsOpaqueProperty => IsOpaqueProperty;
+
+ void OnImageSourcesSourceChanged(object sender, EventArgs e) =>
+ ImageElementManager.ImageSourcesSourceChanged(this, EventArgs.Empty);
+
+ static void OnImageSourceChanged(BindableObject bindable, object oldValue, object newValue)
{
- if (newvalue != null)
+ ImageSource newSource = (ImageSource)newValue;
+ Image image = (Image)bindable;
+ if (newSource != null)
{
- newvalue.SourceChanged += OnSourceChanged;
- SetInheritedBindingContext(newvalue, BindingContext);
+ newSource.SourceChanged += image.OnImageSourcesSourceChanged;
}
-
- InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
+ ImageElementManager.ImageSourceChanged(bindable, newSource);
}
- static void OnSourcePropertyChanging(BindableObject bindable, object oldvalue, object newvalue)
+ static void OnImageSourceChanging(BindableObject bindable, object oldValue, object newValue)
{
- ((Image)bindable).OnSourcePropertyChanging((ImageSource)oldvalue, (ImageSource)newvalue);
- }
+ ImageSource oldSource = (ImageSource)oldValue;
+ Image image = (Image)bindable;
- async void OnSourcePropertyChanging(ImageSource oldvalue, ImageSource newvalue)
- {
- if (oldvalue == null)
- return;
-
- oldvalue.SourceChanged -= OnSourceChanged;
- try
+ if (oldSource != null)
{
- await oldvalue.Cancel();
- }
- catch(ObjectDisposedException)
- {
- // Workaround bugzilla 37792 https://bugzilla.xamarin.com/show_bug.cgi?id=37792
+ oldSource.SourceChanged -= image.OnImageSourcesSourceChanged;
}
+ ImageElementManager.ImageSourceChanging(oldSource);
}
- [EditorBrowsable(EditorBrowsableState.Never)]
- public void SetIsLoading(bool isLoading)
- {
- SetValue(IsLoadingPropertyKey, isLoading);
- }
-
- public IPlatformElementConfiguration<T, Image> On<T>() where T : IConfigPlatform
- {
- return _platformConfigurationRegistry.Value.On<T>();
- }
+ void IImageController.RaiseImageSourcePropertyChanged() => OnPropertyChanged(nameof(Source));
}
}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Reflection;
+using System.Windows.Input;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Platform;
+
+namespace Xamarin.Forms
+{
+ [RenderWith(typeof(_ImageButtonRenderer))]
+ public class ImageButton : View, IImageController, IElementConfiguration<ImageButton>, IBorderElement, IButtonController, IBorderController, IViewController, IPaddingElement
+ {
+ const int DefaultCornerRadius = -1;
+
+ public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(Button), null, propertyChanging: OnCommandChanging, propertyChanged: OnCommandChanged);
+
+ public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(nameof(CornerRadius), typeof(int), typeof(Button), defaultValue: DefaultCornerRadius);
+
+ public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(ImageButton), null,
+ propertyChanged: (bindable, oldvalue, newvalue) => ButtonElementManager.CommandCanExecuteChanged(bindable, EventArgs.Empty));
+
+ public static readonly BindableProperty BorderWidthProperty = BindableProperty.Create(nameof(BorderWidth), typeof(double), typeof(Button), -1d);
+
+ public static readonly BindableProperty BorderColorProperty = BorderElement.BorderColorProperty;
+
+ public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(ImageSource), typeof(ImageButton), default(ImageSource),
+ propertyChanging: OnImageSourceChanging, propertyChanged: OnImageSourceChanged);
+
+ public static readonly BindableProperty AspectProperty = BindableProperty.Create(nameof(Aspect), typeof(Aspect), typeof(ImageButton), Aspect.AspectFit);
+
+ public static readonly BindableProperty IsOpaqueProperty = BindableProperty.Create(nameof(IsOpaque), typeof(bool), typeof(ImageButton), false);
+
+ internal static readonly BindablePropertyKey IsLoadingPropertyKey = BindableProperty.CreateReadOnly(nameof(IsLoading), typeof(bool), typeof(ImageButton), default(bool));
+
+ public static readonly BindableProperty IsLoadingProperty = IsLoadingPropertyKey.BindableProperty;
+
+ internal static readonly BindablePropertyKey IsPressedPropertyKey = BindableProperty.CreateReadOnly(nameof(IsPressed), typeof(bool), typeof(ImageButton), default(bool));
+
+ public static readonly BindableProperty IsPressedProperty = IsPressedPropertyKey.BindableProperty;
+
+ public static readonly BindableProperty PaddingProperty = PaddingElement.PaddingProperty;
+ public event EventHandler Clicked;
+ public event EventHandler Pressed;
+ public event EventHandler Released;
+
+ readonly Lazy<PlatformConfigurationRegistry<ImageButton>> _platformConfigurationRegistry;
+
+
+ public ImageButton()
+ {
+ _platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<ImageButton>>(() => new PlatformConfigurationRegistry<ImageButton>(this));
+ }
+
+ public Color BorderColor
+ {
+ get { return (Color)GetValue(BorderElement.BorderColorProperty); }
+ set { SetValue(BorderElement.BorderColorProperty, value); }
+ }
+
+ public int CornerRadius
+ {
+ get { return (int)GetValue(CornerRadiusProperty); }
+ set { SetValue(CornerRadiusProperty, value); }
+ }
+
+ public double BorderWidth
+ {
+ get { return (double)GetValue(BorderWidthProperty); }
+ set { SetValue(BorderWidthProperty, value); }
+ }
+
+ public Aspect Aspect
+ {
+ get { return (Aspect)GetValue(AspectProperty); }
+ set { SetValue(AspectProperty, value); }
+ }
+
+ public bool IsLoading => (bool)GetValue(IsLoadingProperty);
+
+ public bool IsPressed => (bool)GetValue(IsPressedProperty);
+
+ public bool IsOpaque
+ {
+ get { return (bool)GetValue(IsOpaqueProperty); }
+ set { SetValue(IsOpaqueProperty, value); }
+ }
+ public ICommand Command
+ {
+ get { return (ICommand)GetValue(CommandProperty); }
+ set { SetValue(CommandProperty, value); }
+ }
+
+ public object CommandParameter
+ {
+ get { return GetValue(CommandParameterProperty); }
+ set { SetValue(CommandParameterProperty, value); }
+ }
+
+ [TypeConverter(typeof(ImageSourceConverter))]
+ public ImageSource Source
+ {
+ get { return (ImageSource)GetValue(SourceProperty); }
+ set { SetValue(SourceProperty, value); }
+ }
+
+ bool IButtonController.IsEnabledCore
+ {
+ set { SetValueCore(IsEnabledProperty, value); }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ ImageElementManager.OnBindingContextChanged(this, this);
+ base.OnBindingContextChanged();
+ }
+
+ protected internal override void ChangeVisualState()
+ {
+ if (IsEnabled && IsPressed)
+ {
+ VisualStateManager.GoToState(this, ButtonElementManager.PressedVisualState);
+ }
+ else
+ {
+ base.ChangeVisualState();
+ }
+ }
+
+ protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
+ {
+ SizeRequest desiredSize = base.OnMeasure(double.PositiveInfinity, double.PositiveInfinity);
+ return ImageElementManager.Measure(this, desiredSize, widthConstraint, heightConstraint);
+ }
+
+ public IPlatformElementConfiguration<T, ImageButton> On<T>() where T : IConfigPlatform => _platformConfigurationRegistry.Value.On<T>();
+
+ void IBorderElement.OnBorderColorPropertyChanged(Color oldValue, Color newValue)
+ {
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetIsLoading(bool isLoading) => SetValue(IsLoadingPropertyKey, isLoading);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SetIsPressed(bool isPressed) =>
+ SetValue(IsPressedPropertyKey, isPressed);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SendClicked() =>
+ ButtonElementManager.ElementClicked(this, this);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SendPressed() =>
+ ButtonElementManager.ElementPressed(this, this);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public void SendReleased() =>
+ ButtonElementManager.ElementReleased(this, this);
+
+ public void PropagateUpClicked() =>
+ Clicked?.Invoke(this, EventArgs.Empty);
+
+ public void PropagateUpPressed() =>
+ Pressed?.Invoke(this, EventArgs.Empty);
+
+ public void PropagateUpReleased() =>
+ Released?.Invoke(this, EventArgs.Empty);
+
+ public void RaiseImageSourcePropertyChanged() =>
+ OnPropertyChanged(nameof(Source));
+
+ public Thickness Padding
+ {
+ get { return (Thickness)GetValue(PaddingElement.PaddingProperty); }
+ set { SetValue(PaddingElement.PaddingProperty, value); }
+ }
+
+ Thickness IPaddingElement.PaddingDefaultValueCreator()
+ {
+ return default(Thickness);
+ }
+
+ void IPaddingElement.OnPaddingPropertyChanged(Thickness oldValue, Thickness newValue)
+ {
+ InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
+ }
+ BindableProperty IBorderController.CornerRadiusProperty => ImageButton.CornerRadiusProperty;
+
+ BindableProperty IBorderController.BorderColorProperty => ImageButton.BorderColorProperty;
+
+ BindableProperty IBorderController.BorderWidthProperty => ImageButton.BorderWidthProperty;
+
+ BindableProperty IImageController.SourceProperty => SourceProperty;
+
+ BindableProperty IImageController.AspectProperty => AspectProperty;
+
+ BindableProperty IImageController.IsOpaqueProperty => IsOpaqueProperty;
+
+ void OnImageSourcesSourceChanged(object sender, EventArgs e) =>
+ ImageElementManager.ImageSourcesSourceChanged(this, EventArgs.Empty);
+
+ static void OnImageSourceChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ ImageSource newSource = (ImageSource)newValue;
+ ImageButton button = (ImageButton)bindable;
+ if (newSource != null)
+ {
+ newSource.SourceChanged += button.OnImageSourcesSourceChanged;
+ }
+ ImageElementManager.ImageSourceChanged(bindable, newSource);
+ }
+
+ static void OnImageSourceChanging(BindableObject bindable, object oldValue, object newValue)
+ {
+ ImageSource oldSource = (ImageSource)oldValue;
+ ImageButton button = (ImageButton)bindable;
+
+ if (oldSource != null)
+ {
+ oldSource.SourceChanged -= button.OnImageSourcesSourceChanged;
+ }
+ ImageElementManager.ImageSourceChanging(oldSource);
+ }
+
+ void OnCommandCanExecuteChanged(object sender, EventArgs e) =>
+ ButtonElementManager.CommandCanExecuteChanged(this, EventArgs.Empty);
+
+ static void OnCommandChanged(BindableObject bo, object o, object n)
+ {
+ var button = (ImageButton)bo;
+ if (n is ICommand newCommand)
+ newCommand.CanExecuteChanged += button.OnCommandCanExecuteChanged;
+
+ ButtonElementManager.CommandChanged(button);
+ }
+
+ static void OnCommandChanging(BindableObject bo, object o, object n)
+ {
+ var button = (ImageButton)bo;
+ if (o != null)
+ {
+ (o as ICommand).CanExecuteChanged -= button.OnCommandCanExecuteChanged;
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms
+{
+
+ internal static class ImageElementManager
+ {
+ public static SizeRequest Measure(
+ IImageController ImageElementManager,
+ SizeRequest desiredSize,
+ double widthConstraint,
+ double heightConstraint)
+ {
+ double desiredAspect = desiredSize.Request.Width / desiredSize.Request.Height;
+ double constraintAspect = widthConstraint / heightConstraint;
+
+ double desiredWidth = desiredSize.Request.Width;
+ double desiredHeight = desiredSize.Request.Height;
+
+ if (desiredWidth == 0 || desiredHeight == 0)
+ return new SizeRequest(new Size(0, 0));
+
+ double width = desiredWidth;
+ double height = desiredHeight;
+ if (constraintAspect > desiredAspect)
+ {
+ // constraint area is proportionally wider than image
+ switch (ImageElementManager.Aspect)
+ {
+ case Aspect.AspectFit:
+ case Aspect.AspectFill:
+ height = Math.Min(desiredHeight, heightConstraint);
+ width = desiredWidth * (height / desiredHeight);
+ break;
+ case Aspect.Fill:
+ width = Math.Min(desiredWidth, widthConstraint);
+ height = desiredHeight * (width / desiredWidth);
+ break;
+ }
+ }
+ else if (constraintAspect < desiredAspect)
+ {
+ // constraint area is proportionally taller than image
+ switch (ImageElementManager.Aspect)
+ {
+ case Aspect.AspectFit:
+ case Aspect.AspectFill:
+ width = Math.Min(desiredWidth, widthConstraint);
+ height = desiredHeight * (width / desiredWidth);
+ break;
+ case Aspect.Fill:
+ height = Math.Min(desiredHeight, heightConstraint);
+ width = desiredWidth * (height / desiredHeight);
+ break;
+ }
+ }
+ else
+ {
+ // constraint area is same aspect as image
+ width = Math.Min(desiredWidth, widthConstraint);
+ height = desiredHeight * (width / desiredWidth);
+ }
+
+ return new SizeRequest(new Size(width, height));
+ }
+
+ internal static void OnBindingContextChanged(IImageController image, VisualElement visualElement)
+ {
+ if (image.Source != null)
+ BindableObject.SetInheritedBindingContext(image.Source, visualElement?.BindingContext);
+ }
+
+
+ public static async void ImageSourceChanging(ImageSource oldImageSource)
+ {
+ if (oldImageSource == null) return;
+ try
+ {
+ await oldImageSource.Cancel().ConfigureAwait(false);
+ }
+ catch (ObjectDisposedException)
+ {
+ // Workaround bugzilla 37792 https://bugzilla.xamarin.com/show_bug.cgi?id=37792
+ }
+ }
+
+ public static void ImageSourceChanged(BindableObject bindable, ImageSource newSource)
+ {
+ var visualElement = (VisualElement)bindable;
+ if (newSource != null)
+ BindableObject.SetInheritedBindingContext(newSource, visualElement?.BindingContext);
+
+ visualElement?.InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
+ }
+
+ public static void ImageSourcesSourceChanged(object sender, EventArgs e)
+ {
+ ((IImageController)sender).RaiseImageSourcePropertyChanged();
+ ((VisualElement)sender).InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+namespace Xamarin.Forms.PlatformConfiguration.AndroidSpecific
+{
+ using FormsImageButton = Forms.ImageButton;
+
+ public static class ImageButton
+ {
+ #region Shadow
+ public static readonly BindableProperty IsShadowEnabledProperty = BindableProperty.Create("IsShadowEnabled", typeof(bool), typeof(Forms.ImageButton), false);
+
+ public static bool GetIsShadowEnabled(BindableObject element)
+ {
+ return (bool)element.GetValue(IsShadowEnabledProperty);
+ }
+
+ public static void SetIsShadowEnabled(BindableObject element, bool value)
+ {
+ element.SetValue(IsShadowEnabledProperty, value);
+ }
+
+ public static bool GetIsShadowEnabled(this IPlatformElementConfiguration<Android, FormsImageButton> config)
+ {
+ return GetIsShadowEnabled(config.Element);
+ }
+
+ public static IPlatformElementConfiguration<Android, FormsImageButton> SetIsShadowEnabled(this IPlatformElementConfiguration<Android, FormsImageButton> config, bool value)
+ {
+ SetIsShadowEnabled(config.Element, value);
+ return config;
+ }
+
+ public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create("ShadowColor", typeof(Color), typeof(ImageButton), Color.Default);
+
+ public static Color GetShadowColor(BindableObject element)
+ {
+ return (Color)element.GetValue(ShadowColorProperty);
+ }
+
+ public static void SetShadowColor(BindableObject element, Color value)
+ {
+ element.SetValue(ShadowColorProperty, value);
+ }
+
+ public static Color GetShadowColor(this IPlatformElementConfiguration<Android, FormsImageButton> config)
+ {
+ return GetShadowColor(config.Element);
+ }
+
+ public static IPlatformElementConfiguration<Android, FormsImageButton> SetShadowColor(this IPlatformElementConfiguration<Android, FormsImageButton> config, Color value)
+ {
+ SetShadowColor(config.Element, value);
+ return config;
+ }
+
+ public static readonly BindableProperty ShadowRadiusProperty = BindableProperty.Create("ShadowRadius", typeof(double), typeof(ImageButton), 10.0);
+
+ public static double GetShadowRadius(BindableObject element)
+ {
+ return (double)element.GetValue(ShadowRadiusProperty);
+ }
+
+ public static void SetShadowRadius(BindableObject element, double value)
+ {
+ element.SetValue(ShadowRadiusProperty, value);
+ }
+
+ public static double GetShadowRadius(this IPlatformElementConfiguration<Android, FormsImageButton> config)
+ {
+ return GetShadowRadius(config.Element);
+ }
+
+ public static IPlatformElementConfiguration<Android, FormsImageButton> SetShadowRadius(this IPlatformElementConfiguration<Android, FormsImageButton> config, double value)
+ {
+ SetShadowRadius(config.Element, value);
+ return config;
+ }
+
+ public static readonly BindableProperty ShadowOffsetProperty = BindableProperty.Create("ShadowOffset", typeof(Size), typeof(VisualElement), Size.Zero);
+
+ public static Size GetShadowOffset(BindableObject element)
+ {
+ return (Size)element.GetValue(ShadowOffsetProperty);
+ }
+
+ public static void SetShadowOffset(BindableObject element, Size value)
+ {
+ element.SetValue(ShadowOffsetProperty, value);
+ }
+
+ public static Size GetShadowOffset(this IPlatformElementConfiguration<Android, FormsImageButton> config)
+ {
+ return GetShadowOffset(config.Element);
+ }
+
+ public static IPlatformElementConfiguration<Android, FormsImageButton> SetShadowOffset(this IPlatformElementConfiguration<Android, FormsImageButton> config, Size value)
+ {
+ SetShadowOffset(config.Element, value);
+ return config;
+ }
+ #endregion
+ }
+}
set { SetValue(RotationYProperty, value); }
}
- public double Scale {
+ public double Scale
+ {
get => (double)GetValue(ScaleProperty);
set => SetValue(ScaleProperty, value);
}
- public double ScaleX {
+ public double ScaleX
+ {
get => (double)GetValue(ScaleXProperty);
set => SetValue(ScaleXProperty, value);
}
- public double ScaleY {
+ public double ScaleY
+ {
get => (double)GetValue(ScaleYProperty);
set => SetValue(ScaleYProperty, value);
}
focus(this, new FocusEventArgs(this, true));
}
+ internal void ChangeVisualStateInternal() => ChangeVisualState();
+
+
protected internal virtual void ChangeVisualState()
{
if (!IsEnabled)
namespace Xamarin.Forms.CustomAttributes
{
- [Conditional ("DEBUG")]
- [AttributeUsage (
+ [Conditional("DEBUG")]
+ [AttributeUsage(
AttributeTargets.Class |
- AttributeTargets.Event |
+ AttributeTargets.Event |
AttributeTargets.Property |
AttributeTargets.Method |
- AttributeTargets.Delegate,
+ AttributeTargets.Delegate,
AllowMultiple = true
)]
public class PlatformAttribute : Attribute
{
readonly string _platform;
- public PlatformAttribute (object platform)
+ public PlatformAttribute(object platform)
{
- _platform = platform.ToString ();
+ _platform = platform.ToString();
}
public string Platform => "Issue: " + _platform;
public enum IssueTracker
{
- Github,
+ Github,
Bugzilla,
None
}
Default
}
- [Conditional ("DEBUG")]
- [AttributeUsage (
+ [Conditional("DEBUG")]
+ [AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Method,
AllowMultiple = true
{
bool _modal;
- public IssueAttribute (IssueTracker issueTracker, int issueNumber, string description,
+ public IssueAttribute(IssueTracker issueTracker, int issueNumber, string description,
NavigationBehavior navigationBehavior = NavigationBehavior.Default, int issueTestNumber = 0)
{
IssueTracker = issueTracker;
IssueTestNumber = issueTestNumber;
}
- public IssueAttribute (IssueTracker issueTracker, int issueNumber, string description,
- PlatformAffected platformsAffected, NavigationBehavior navigationBehavior = NavigationBehavior.Default,
+ public IssueAttribute(IssueTracker issueTracker, int issueNumber, string description,
+ PlatformAffected platformsAffected, NavigationBehavior navigationBehavior = NavigationBehavior.Default,
int issueTestNumber = 0)
{
IssueTracker = issueTracker;
: $"{IssueTracker.ToString().Substring(0, 1)}{IssueNumber} ({IssueTestNumber})";
}
- [Conditional ("DEBUG")]
+ [Conditional("DEBUG")]
public class UiTestExemptAttribute : Attribute
{
// optional string reason
readonly string _exemptReason;
readonly string _description;
- public UiTestExemptAttribute (ExemptReason exemptReason, string description = null)
+ public UiTestExemptAttribute(ExemptReason exemptReason, string description = null)
{
- _exemptReason = Enum.GetName (typeof(ExemptReason), exemptReason);
+ _exemptReason = Enum.GetName(typeof(ExemptReason), exemptReason);
_description = description;
}
public string Description => "Description: " + _description;
}
- [Conditional ("DEBUG")]
+ [Conditional("DEBUG")]
public class UiTestFragileAttribute : Attribute
{
// optional string reason
readonly string _description;
- public UiTestFragileAttribute (string description = null)
+ public UiTestFragileAttribute(string description = null)
{
_description = description;
}
public string Description => "Description: " + _description;
}
- [Conditional ("DEBUG")]
- [AttributeUsage (AttributeTargets.All, AllowMultiple = true)]
+ [Conditional("DEBUG")]
+ [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class UiTestBrokenAttribute : Attribute
{
// optional string reason
readonly string _exemptReason;
readonly string _description;
- public UiTestBrokenAttribute (BrokenReason exemptReason, string description = null)
+ public UiTestBrokenAttribute(BrokenReason exemptReason, string description = null)
{
- _exemptReason = Enum.GetName (typeof(ExemptReason), exemptReason);
+ _exemptReason = Enum.GetName(typeof(ExemptReason), exemptReason);
_description = description;
}
CannotTest
}
- public enum BrokenReason
+ public enum BrokenReason
{
UITestBug,
CalabashBug,
BorderColor,
BorderRadius,
Image,
- Padding
+ Padding,
+ Pressed
}
public enum VisualElement
Fill
}
+ public enum ImageButton
+ {
+ Source,
+ Aspect,
+ IsOpaque,
+ IsLoading,
+ AspectFill,
+ AspectFit,
+ Fill,
+ BorderColor,
+ CornerRadius,
+ BorderWidth,
+ Clicked,
+ Command,
+ Image,
+ Pressed
+ }
+
public enum ImageSource
{
FromFile,
MaxLines
}
- public enum MasterDetailPage {
+ public enum MasterDetailPage
+ {
Master,
Detail,
IsGestureEnabled,
MasterBehavior
}
- public enum OpenGlView {
+ public enum OpenGlView
+ {
OnDisplay,
HasRenderLoop,
Display
}
- public enum ProgressBar {
+ public enum ProgressBar
+ {
Progress,
ProgressColor
}
- public enum RelativeLayout {
+ public enum RelativeLayout
+ {
Children,
SetBoundsConstraint
}
- public enum ScrollView {
+ public enum ScrollView
+ {
ContentSize,
Orientation,
Content
PlaceholderColor
}
- public enum Slider {
+ public enum Slider
+ {
Minimum,
Maximum,
Value,
ThumbImage
}
- public enum StackLayout {
+ public enum StackLayout
+ {
Orientation,
Spacing
}
- public enum Stepper {
+ public enum Stepper
+ {
Maximum,
Minimum,
Value,
Increment
}
- public enum Switch {
+ public enum Switch
+ {
IsToggled
}
- public enum TimePicker {
+ public enum TimePicker
+ {
Format,
Time,
Focus,
FontSize
}
- public enum WebView {
+ public enum WebView
+ {
UrlWebViewSource,
HtmlWebViewSource,
LoadHtml,
EvaluateJavaScript
}
- public enum UrlWebViewSource {
+ public enum UrlWebViewSource
+ {
Url
}
- public enum HtmlWebViewSource {
+ public enum HtmlWebViewSource
+ {
BaseUrl,
Html
}
- public enum Grid {
+ public enum Grid
+ {
Children,
SetRow,
SetRowSpan,
RowDefinitions
}
- public enum ContentPage {
+ public enum ContentPage
+ {
Content
}
- public enum Picker {
+ public enum Picker
+ {
Title,
Items,
SelectedIndex,
FontSize
}
- public enum FileImageSource {
+ public enum FileImageSource
+ {
File,
Cancel
}
- public enum StreamImageSource {
+ public enum StreamImageSource
+ {
Stream
}
- public enum OnPlatform {
+ public enum OnPlatform
+ {
WinPhone,
Android,
iOS
}
- public enum OnIdiom {
+ public enum OnIdiom
+ {
Phone,
Tablet
}
- public enum Span {
+ public enum Span
+ {
Text,
ForeGroundColor,
BackgroundColor,
PropertyChanged
}
- public enum FormattedString {
+ public enum FormattedString
+ {
ToStringOverride,
Spans,
PropertyChanged
using Android.Graphics;
using Android.Support.V7.Widget;
using Android.Util;
-using Xamarin.Forms.Internals;
-using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
-using GlobalResource = Android.Resource;
using Object = Java.Lang.Object;
using AView = Android.Views.View;
using AMotionEvent = Android.Views.MotionEvent;
using AMotionEventActions = Android.Views.MotionEventActions;
using static System.String;
+using AColor = Android.Graphics.Color;
+using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
namespace Xamarin.Forms.Platform.Android.AppCompat
{
- public class ButtonRenderer : ViewRenderer<Button, AppCompatButton>, AView.IOnAttachStateChangeListener
+ public class ButtonRenderer : ViewRenderer<Button, AppCompatButton>, AView.IOnAttachStateChangeListener, IBorderVisualElementRenderer
{
- ButtonBackgroundTracker _backgroundTracker;
+ BorderBackgroundManager _backgroundTracker;
TextColorSwitcher _textColorSwitcher;
float _defaultFontSize;
Thickness? _defaultPadding;
bool _isDisposed;
int _imageHeight = -1;
Thickness _paddingDeltaPix = new Thickness();
+ IVisualElementRenderer _visualElementRenderer;
public ButtonRenderer(Context context) : base(context)
{
AutoPackage = false;
+ _visualElementRenderer = this;
+ _backgroundTracker = new BorderBackgroundManager(this);
}
[Obsolete("This constructor is obsolete as of version 2.5. Please use ButtonRenderer(Context) instead.")]
public ButtonRenderer()
{
AutoPackage = false;
+ _visualElementRenderer = this;
+ _backgroundTracker = new BorderBackgroundManager(this);
}
global::Android.Widget.Button NativeButton => Control;
}
_backgroundTracker?.Dispose();
_backgroundTracker = null;
+ _visualElementRenderer = null;
}
base.Dispose(disposing);
{
base.OnElementChanged(e);
- if (e.OldElement != null)
- {
- }
-
if (e.NewElement != null)
{
if (Control == null)
button.AddOnAttachStateChangeListener(this);
}
- if (_backgroundTracker == null)
- _backgroundTracker = new ButtonBackgroundTracker(Element, Control);
- else
- _backgroundTracker.Button = e.NewElement;
_defaultFontSize = 0f;
_defaultPadding = null;
void UpdateContentEdge(Thickness? delta = null)
{
_paddingDeltaPix = delta ?? new Thickness();
- UpdatePadding();
+ UpdatePadding();
+ }
+
+ float IBorderVisualElementRenderer.ShadowRadius => Control.ShadowRadius;
+ float IBorderVisualElementRenderer.ShadowDx => Control.ShadowDx;
+ float IBorderVisualElementRenderer.ShadowDy => Control.ShadowDy;
+ AColor IBorderVisualElementRenderer.ShadowColor => Control.ShadowColor;
+ bool IBorderVisualElementRenderer.UseDefaultPadding() => Element.OnThisPlatform().UseDefaultPadding();
+ bool IBorderVisualElementRenderer.UseDefaultShadow() => Element.OnThisPlatform().UseDefaultShadow();
+ bool IBorderVisualElementRenderer.IsShadowEnabled() => true;
+ VisualElement IBorderVisualElementRenderer.Element => Element;
+ AView IBorderVisualElementRenderer.View => Control;
+ event EventHandler<VisualElementChangedEventArgs> IBorderVisualElementRenderer.ElementChanged
+ {
+ add => _visualElementRenderer.ElementChanged += value;
+ remove => _visualElementRenderer.ElementChanged -= value;
}
class ButtonClickListener : Object, AView.IOnClickListener
--- /dev/null
+using System;
+using System.ComponentModel;
+using Android.Content;
+using Android.Support.V7.Widget;
+using AView = Android.Views.View;
+using Android.Views;
+using Xamarin.Forms.Internals;
+using AColor = Android.Graphics.Color;
+using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+using Android.Graphics.Drawables;
+using Android.Graphics;
+using Xamarin.Forms.Platform.Android.FastRenderers;
+using Android.OS;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ public class ImageButtonRenderer :
+ AppCompatImageButton,
+ IVisualElementRenderer,
+ IBorderVisualElementRenderer,
+ IImageRendererController,
+ AView.IOnFocusChangeListener,
+ AView.IOnClickListener,
+ AView.IOnTouchListener
+ {
+ bool _inputTransparent;
+ bool _disposed;
+ bool _skipInvalidate;
+ int? _defaultLabelFor;
+ VisualElementTracker _tracker;
+ VisualElementRenderer _visualElementRenderer;
+ BorderBackgroundManager _backgroundTracker;
+ IPlatformElementConfiguration<PlatformConfiguration.Android, ImageButton> _platformElementConfiguration;
+ private ImageButton _imageButton;
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+ public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
+
+ void IVisualElementRenderer.UpdateLayout() => _tracker?.UpdateLayout();
+ VisualElement IVisualElementRenderer.Element => ImageButton;
+ AView IVisualElementRenderer.View => this;
+ ViewGroup IVisualElementRenderer.ViewGroup => null;
+ VisualElementTracker IVisualElementRenderer.Tracker => _tracker;
+
+ ImageButton ImageButton
+ {
+ get => _imageButton;
+ set
+ {
+ _imageButton = value;
+ _platformElementConfiguration = null;
+ }
+ }
+
+ void IImageRendererController.SkipInvalidate() => _skipInvalidate = true;
+ bool IImageRendererController.IsDisposed => _disposed;
+
+ AppCompatImageButton Control => this;
+ public ImageButtonRenderer(Context context) : base(context)
+ {
+ // These set the defaults so visually it matches up with other platforms
+ SetPadding(0, 0, 0, 0);
+ SoundEffectsEnabled = false;
+ SetOnClickListener(this);
+ SetOnTouchListener(this);
+ OnFocusChangeListener = this;
+
+ Tag = this;
+ _backgroundTracker = new BorderBackgroundManager(this, false);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+
+ if (disposing)
+ {
+
+ ImageElementManager.Dispose(this);
+
+ _tracker?.Dispose();
+ _tracker = null;
+
+ _backgroundTracker?.Dispose();
+ _backgroundTracker = null;
+
+ if (ImageButton != null)
+ {
+ ImageButton.PropertyChanged -= OnElementPropertyChanged;
+
+ if (Android.Platform.GetRenderer(ImageButton) == this)
+ {
+ ImageButton.ClearValue(Android.Platform.RendererProperty);
+ }
+
+ ImageButton = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ public override void Invalidate()
+ {
+ if (_skipInvalidate)
+ {
+ _skipInvalidate = false;
+ return;
+ }
+
+ base.Invalidate();
+ }
+
+ Size MinimumSize()
+ {
+ return new Size();
+ }
+
+ SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
+ {
+ if (_disposed)
+ {
+ return new SizeRequest();
+ }
+ Measure(widthConstraint, heightConstraint);
+ return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight), MinimumSize());
+ }
+
+ void IVisualElementRenderer.SetElement(VisualElement element)
+ {
+
+ if (element == null)
+ {
+ throw new ArgumentNullException(nameof(element));
+ }
+
+ if (!(element is ImageButton image))
+ {
+ throw new ArgumentException("Element is not of type " + typeof(ImageButton), nameof(element));
+ }
+
+ ImageButton oldElement = ImageButton;
+ ImageButton = image;
+
+ Performance.Start(out string reference);
+
+ if (oldElement != null)
+ {
+ oldElement.PropertyChanged -= OnElementPropertyChanged;
+ }
+
+ element.PropertyChanged += OnElementPropertyChanged;
+
+ if (_tracker == null)
+ {
+ _tracker = new VisualElementTracker(this);
+ ImageElementManager.Init(this);
+
+ }
+
+ if (_visualElementRenderer == null)
+ {
+ _visualElementRenderer = new VisualElementRenderer(this);
+ }
+
+ Performance.Stop(reference);
+ this.EnsureId();
+
+ UpdateInputTransparent();
+ UpdatePadding();
+
+ ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(oldElement, ImageButton));
+ ImageButton?.SendViewInitialized(Control);
+ }
+
+ public override void Draw(Canvas canvas)
+ {
+ if (ImageButton == null)
+ return;
+
+ var backgroundDrawable = _backgroundTracker?.BackgroundDrawable;
+
+ RectF drawableBounds = null;
+
+ if ((int)Build.VERSION.SdkInt >= 18 && backgroundDrawable != null)
+ {
+ var outlineBounds = backgroundDrawable.GetPaddingBounds(canvas.Width, canvas.Height);
+ var width = (float)MeasuredWidth;
+ var height = (float)MeasuredHeight;
+
+ var widthRatio = 1f;
+ var heightRatio = 1f;
+
+ if (ImageButton.Aspect == Aspect.AspectFill && OnThisPlatform().GetIsShadowEnabled())
+ Internals.Log.Warning(nameof(ImageButtonRenderer), "AspectFill isn't fully supported when using shadows. Image may be clipped incorrectly to Border");
+
+ switch (ImageButton.Aspect)
+ {
+ case Aspect.Fill:
+ break;
+ case Aspect.AspectFill:
+ case Aspect.AspectFit:
+ heightRatio = (float)Drawable.IntrinsicHeight / height;
+ widthRatio = (float)Drawable.IntrinsicWidth / width;
+ break;
+ }
+
+ drawableBounds = new RectF(outlineBounds.Left * widthRatio, outlineBounds.Top * heightRatio, outlineBounds.Right * widthRatio, outlineBounds.Bottom * heightRatio);
+ }
+
+ if (drawableBounds != null)
+ Drawable.SetBounds((int)drawableBounds.Left, (int)drawableBounds.Top, (int)drawableBounds.Right, (int)drawableBounds.Bottom);
+
+
+
+ base.Draw(canvas);
+ if (_backgroundTracker.BackgroundDrawable != null)
+ _backgroundTracker.BackgroundDrawable.DrawOutline(canvas, canvas.Width, canvas.Height);
+ }
+
+ void IVisualElementRenderer.SetLabelFor(int? id)
+ {
+ if (_defaultLabelFor == null)
+ _defaultLabelFor = LabelFor;
+
+ LabelFor = (int)(id ?? _defaultLabelFor);
+ }
+
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ if (!Enabled || (_inputTransparent && Enabled))
+ return false;
+
+ return base.OnTouchEvent(e);
+ }
+
+
+ void UpdatePadding()
+ {
+ SetPadding(
+ (int)(Context.ToPixels(ImageButton.Padding.Left)),
+ (int)(Context.ToPixels(ImageButton.Padding.Top)),
+ (int)(Context.ToPixels(ImageButton.Padding.Right)),
+ (int)(Context.ToPixels(ImageButton.Padding.Bottom))
+ );
+ }
+
+ void UpdateInputTransparent()
+ {
+ if (ImageButton == null || _disposed)
+ {
+ return;
+ }
+
+ _inputTransparent = ImageButton.InputTransparent;
+ }
+
+ // Image related
+ void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName)
+ UpdateInputTransparent();
+ else if (e.PropertyName == ImageButton.PaddingProperty.PropertyName)
+ UpdatePadding();
+
+ ElementPropertyChanged?.Invoke(this, e);
+ }
+
+
+ // general state related
+ void IOnFocusChangeListener.OnFocusChange(AView v, bool hasFocus)
+ {
+ ((IElementController)ImageButton).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, hasFocus);
+ }
+ // general state related
+
+
+ // Button related
+ void IOnClickListener.OnClick(AView v) =>
+ ButtonElementManager.OnClick(ImageButton, ImageButton, v);
+
+ bool IOnTouchListener.OnTouch(AView v, MotionEvent e) =>
+ ButtonElementManager.OnTouch(ImageButton, ImageButton, v, e);
+ // Button related
+
+
+ float IBorderVisualElementRenderer.ShadowRadius => Context.ToPixels(OnThisPlatform().GetShadowRadius());
+ float IBorderVisualElementRenderer.ShadowDx => Context.ToPixels(OnThisPlatform().GetShadowOffset().Width);
+ float IBorderVisualElementRenderer.ShadowDy => Context.ToPixels(OnThisPlatform().GetShadowOffset().Height);
+ AColor IBorderVisualElementRenderer.ShadowColor => OnThisPlatform().GetShadowColor().ToAndroid();
+ bool IBorderVisualElementRenderer.IsShadowEnabled() => OnThisPlatform().GetIsShadowEnabled();
+ bool IBorderVisualElementRenderer.UseDefaultPadding() => false;
+ bool IBorderVisualElementRenderer.UseDefaultShadow() => false;
+ VisualElement IBorderVisualElementRenderer.Element => ImageButton;
+ AView IBorderVisualElementRenderer.View => this;
+
+ IPlatformElementConfiguration<PlatformConfiguration.Android, ImageButton> OnThisPlatform()
+ {
+ if (_platformElementConfiguration == null)
+ _platformElementConfiguration = ImageButton.OnThisPlatform();
+
+ return _platformElementConfiguration;
+ }
+ }
+}
--- /dev/null
+using System;
+using System.ComponentModel;
+using Xamarin.Forms.Internals;
+using AView = Android.Views.View;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ internal static class BackgroundManager
+ {
+ public static void Init(IVisualElementRenderer renderer)
+ {
+ _ = renderer ?? throw new ArgumentNullException($"{nameof(BackgroundManager)}.{nameof(Init)} {nameof(renderer)} cannot be null");
+
+ renderer.ElementPropertyChanged += OnElementPropertyChanged;
+ renderer.ElementChanged += OnElementChanged;
+ }
+
+ public static void Dispose(IVisualElementRenderer renderer)
+ {
+ _ = renderer ?? throw new ArgumentNullException($"{nameof(BackgroundManager)}.{nameof(Init)} {nameof(renderer)} cannot be null");
+
+ renderer.ElementPropertyChanged -= OnElementPropertyChanged;
+ renderer.ElementChanged -= OnElementChanged;
+ }
+
+ static void UpdateBackgroundColor(AView Control, VisualElement Element, Color? color = null)
+ {
+ if (Element == null || Control == null)
+ return;
+
+ Control.SetBackgroundColor((color ?? Element.BackgroundColor).ToAndroid());
+ }
+
+
+ static void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+ {
+ Performance.Start(out string reference);
+ if (e.OldElement != null)
+ {
+ e.OldElement.PropertyChanged -= OnElementPropertyChanged;
+ }
+
+ if (e.NewElement != null)
+ {
+ var renderer = (sender as IVisualElementRenderer);
+ e.NewElement.PropertyChanged += OnElementPropertyChanged;
+ UpdateBackgroundColor(renderer?.View, renderer?.Element);
+ }
+
+ Performance.Stop(reference);
+ }
+
+
+ static void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ {
+ var renderer = (sender as IVisualElementRenderer);
+ UpdateBackgroundColor(renderer?.View, renderer?.Element);
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.ComponentModel;
+using Android.Content.Res;
+using AView = Android.Views.View;
+using Android.Graphics.Drawables;
+using Android.OS;
+using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+using Specifics = Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+using AButton = Android.Widget.Button;
+using AColor = Android.Graphics.Color;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ internal class BorderBackgroundManager : IDisposable
+ {
+ Drawable _defaultDrawable;
+ BorderDrawable _backgroundDrawable;
+ RippleDrawable _rippleDrawable;
+ bool _drawableEnabled;
+ bool _disposed;
+ IBorderVisualElementRenderer _renderer;
+ VisualElement Element => _renderer?.Element;
+ AView Control => _renderer?.View;
+ readonly bool _drawOutlineWithBackground;
+
+ public bool DrawOutlineWithBackground { get; set; } = true;
+ public BorderDrawable BackgroundDrawable => _backgroundDrawable;
+
+ public BorderBackgroundManager(IBorderVisualElementRenderer renderer) : this(renderer, true)
+ {
+ }
+
+ public BorderBackgroundManager(IBorderVisualElementRenderer renderer, bool drawOutlineWithBackground)
+ {
+ _renderer = renderer;
+ _renderer.ElementChanged += OnElementChanged;
+ _drawOutlineWithBackground = drawOutlineWithBackground;
+ }
+
+ void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+ {
+ if (e.OldElement != null)
+ {
+ (e.OldElement as IBorderController).PropertyChanged -= BorderElementPropertyChanged;
+ }
+
+ if (e.NewElement != null)
+ {
+ if (BorderElement != null)
+ {
+ BorderElement.PropertyChanged -= BorderElementPropertyChanged;
+ }
+ BorderElement = (IBorderController)e.NewElement;
+ BorderElement.PropertyChanged += BorderElementPropertyChanged;
+ }
+
+ Reset();
+ UpdateDrawable();
+ }
+
+ public IBorderController BorderElement
+ {
+ get;
+ private set;
+ }
+
+ public void UpdateDrawable()
+ {
+ if (BorderElement == null || Control == null)
+ return;
+
+ bool cornerRadiusIsDefault = !BorderElement.IsSet(BorderElement.CornerRadiusProperty) || (BorderElement.CornerRadius == (int)BorderElement.CornerRadiusProperty.DefaultValue || BorderElement.CornerRadius == BorderDrawable.DefaultCornerRadius);
+ bool backgroundColorIsDefault = !BorderElement.IsSet(VisualElement.BackgroundColorProperty) || BorderElement.BackgroundColor == (Color)VisualElement.BackgroundColorProperty.DefaultValue;
+ bool borderColorIsDefault = !BorderElement.IsSet(BorderElement.BorderColorProperty) || BorderElement.BorderColor == (Color)BorderElement.BorderColorProperty.DefaultValue;
+ bool borderWidthIsDefault = !BorderElement.IsSet(BorderElement.BorderWidthProperty) || BorderElement.BorderWidth == (double)BorderElement.BorderWidthProperty.DefaultValue;
+
+ if (backgroundColorIsDefault
+ && cornerRadiusIsDefault
+ && borderColorIsDefault
+ && borderWidthIsDefault)
+ {
+ if (!_drawableEnabled)
+ return;
+
+ if (_defaultDrawable != null)
+ Control.SetBackground(_defaultDrawable);
+
+ _drawableEnabled = false;
+ Reset();
+ }
+ else
+ {
+ if (_backgroundDrawable == null)
+ _backgroundDrawable = new BorderDrawable(Control.Context.ToPixels, Forms.GetColorButtonNormal(Control.Context), _drawOutlineWithBackground);
+
+ _backgroundDrawable.BorderController = BorderElement;
+
+ var useDefaultPadding = _renderer.UseDefaultPadding();
+
+ int paddingTop = useDefaultPadding ? Control.PaddingTop : 0;
+ int paddingLeft = useDefaultPadding ? Control.PaddingLeft : 0;
+
+ var useDefaultShadow = _renderer.UseDefaultShadow();
+
+ // Use no shadow by default for API < 16
+ float shadowRadius = 0;
+ float shadowDy = 0;
+ float shadowDx = 0;
+ AColor shadowColor = Color.Transparent.ToAndroid();
+ // Add Android's default material shadow if we want it
+ if (useDefaultShadow)
+ {
+ shadowRadius = 2;
+ shadowDy = 4;
+ shadowDx = 0;
+ shadowColor = _backgroundDrawable.PressedBackgroundColor.ToAndroid();
+ }
+ // Otherwise get values from the control (but only for supported APIs)
+ else if ((int)Build.VERSION.SdkInt >= 16)
+ {
+ shadowRadius = _renderer.ShadowRadius;
+ shadowDy = _renderer.ShadowDy;
+ shadowDx = _renderer.ShadowDx;
+ shadowColor = _renderer.ShadowColor;
+ }
+
+ _backgroundDrawable.SetPadding(paddingTop, paddingLeft);
+ if (_renderer.IsShadowEnabled())
+ {
+ _backgroundDrawable
+ .SetShadow(shadowDy, shadowDx, shadowColor, shadowRadius);
+ }
+
+ if (_drawableEnabled)
+ return;
+
+ if (_defaultDrawable == null)
+ _defaultDrawable = Control.Background;
+
+ if (!backgroundColorIsDefault || _drawOutlineWithBackground)
+ {
+ if (Forms.IsLollipopOrNewer)
+ {
+ var rippleColor = _backgroundDrawable.PressedBackgroundColor.ToAndroid();
+
+ _rippleDrawable = new RippleDrawable(ColorStateList.ValueOf(rippleColor), _backgroundDrawable, null);
+ Control.SetBackground(_rippleDrawable);
+ }
+ else
+ {
+ Control.SetBackground(_backgroundDrawable);
+ }
+ }
+
+ _drawableEnabled = true;
+ }
+
+ Control.Invalidate();
+ }
+
+ public void Reset()
+ {
+ if (_drawableEnabled)
+ {
+ _drawableEnabled = false;
+ _backgroundDrawable?.Reset();
+ _backgroundDrawable = null;
+ _rippleDrawable = null;
+ }
+ }
+
+ public void UpdateBackgroundColor()
+ {
+ UpdateDrawable();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ _backgroundDrawable?.Dispose();
+ _backgroundDrawable = null;
+ _defaultDrawable?.Dispose();
+ _defaultDrawable = null;
+ _rippleDrawable?.Dispose();
+ _rippleDrawable = null;
+ if (BorderElement != null)
+ {
+ BorderElement.PropertyChanged -= BorderElementPropertyChanged;
+ BorderElement = null;
+ }
+
+ if (_renderer != null)
+ {
+ _renderer.ElementChanged -= OnElementChanged;
+ _renderer = null;
+ }
+ }
+ _disposed = true;
+ }
+ }
+
+ void BorderElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName.Equals(BorderElement.BorderColorProperty.PropertyName) ||
+ e.PropertyName.Equals(BorderElement.BorderWidthProperty.PropertyName) ||
+ e.PropertyName.Equals(BorderElement.CornerRadiusProperty.PropertyName) ||
+ e.PropertyName.Equals(VisualElement.BackgroundColorProperty.PropertyName) ||
+ e.PropertyName.Equals(Specifics.Button.UseDefaultPaddingProperty.PropertyName) ||
+ e.PropertyName.Equals(Specifics.Button.UseDefaultShadowProperty.PropertyName) ||
+ e.PropertyName.Equals(Specifics.ImageButton.IsShadowEnabledProperty.PropertyName) ||
+ e.PropertyName.Equals(Specifics.ImageButton.ShadowColorProperty.PropertyName) ||
+ e.PropertyName.Equals(Specifics.ImageButton.ShadowOffsetProperty.PropertyName) ||
+ e.PropertyName.Equals(Specifics.ImageButton.ShadowRadiusProperty.PropertyName))
+ {
+ Reset();
+ UpdateDrawable();
+ }
+ }
+
+ }
+}
\ No newline at end of file
+++ /dev/null
-using System;
-using System.ComponentModel;
-using Android.Content.Res;
-using Android.Graphics.Drawables;
-using Android.OS;
-using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
-using Specifics = Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
-using AButton = Android.Widget.Button;
-using AColor = Android.Graphics.Color;
-
-namespace Xamarin.Forms.Platform.Android
-{
- internal class ButtonBackgroundTracker : IDisposable
- {
- Drawable _defaultDrawable;
- ButtonDrawable _backgroundDrawable;
- RippleDrawable _rippleDrawable;
- Button _button;
- AButton _nativeButton;
- bool _drawableEnabled;
- bool _disposed;
-
- public ButtonBackgroundTracker(Button button, AButton nativeButton)
- {
- Button = button;
- _nativeButton = nativeButton;
- }
-
- public Button Button
- {
- get { return _button; }
- set
- {
- if (_button == value)
- return;
- if (_button != null)
- _button.PropertyChanged -= ButtonPropertyChanged;
- _button = value;
- _button.PropertyChanged += ButtonPropertyChanged;
- }
- }
-
- public void UpdateDrawable()
- {
- if (_button == null || _nativeButton == null)
- return;
-
- bool cornerRadiusIsDefault = !_button.IsSet(Button.CornerRadiusProperty) || (_button.CornerRadius == (int)Button.CornerRadiusProperty.DefaultValue || _button.CornerRadius == ButtonDrawable.DefaultCornerRadius);
- bool backgroundColorIsDefault = !_button.IsSet(VisualElement.BackgroundColorProperty) || _button.BackgroundColor == (Color)VisualElement.BackgroundColorProperty.DefaultValue;
- bool borderColorIsDefault = !_button.IsSet(Button.BorderColorProperty) || _button.BorderColor == (Color)Button.BorderColorProperty.DefaultValue;
- bool borderWidthIsDefault = !_button.IsSet(Button.BorderWidthProperty) || _button.BorderWidth == (double)Button.BorderWidthProperty.DefaultValue;
-
- if (backgroundColorIsDefault
- && cornerRadiusIsDefault
- && borderColorIsDefault
- && borderWidthIsDefault)
- {
- if (!_drawableEnabled)
- return;
-
- if (_defaultDrawable != null)
- _nativeButton.SetBackground(_defaultDrawable);
-
- _drawableEnabled = false;
- }
- else
- {
- if (_backgroundDrawable == null)
- _backgroundDrawable = new ButtonDrawable(_nativeButton.Context.ToPixels, Forms.GetColorButtonNormal(_nativeButton.Context));
-
- _backgroundDrawable.Button = _button;
-
- var useDefaultPadding = _button.OnThisPlatform().UseDefaultPadding();
-
- int paddingTop = useDefaultPadding ? _nativeButton.PaddingTop : 0;
- int paddingLeft = useDefaultPadding ? _nativeButton.PaddingLeft : 0;
-
- var useDefaultShadow = _button.OnThisPlatform().UseDefaultShadow();
-
- // Use no shadow by default for API < 16
- float shadowRadius = 0;
- float shadowDy = 0;
- float shadowDx = 0;
- AColor shadowColor = Color.Transparent.ToAndroid();
- // Add Android's default material shadow if we want it
- if (useDefaultShadow)
- {
- shadowRadius = 2;
- shadowDy = 4;
- shadowDx = 0;
- shadowColor = _backgroundDrawable.PressedBackgroundColor.ToAndroid();
- }
- // Otherwise get values from the control (but only for supported APIs)
- else if ((int)Build.VERSION.SdkInt >= 16)
- {
- shadowRadius = _nativeButton.ShadowRadius;
- shadowDy = _nativeButton.ShadowDy;
- shadowDx = _nativeButton.ShadowDx;
- shadowColor = _nativeButton.ShadowColor;
- }
-
- _backgroundDrawable.SetPadding(paddingTop, paddingLeft)
- .SetShadow(shadowDy, shadowDx, shadowColor, shadowRadius);
-
- if (_drawableEnabled)
- return;
-
- if (_defaultDrawable == null)
- _defaultDrawable = _nativeButton.Background;
-
- if (Forms.IsLollipopOrNewer)
- {
- var rippleColor = _backgroundDrawable.PressedBackgroundColor.ToAndroid();
-
- _rippleDrawable = new RippleDrawable(ColorStateList.ValueOf(rippleColor), _backgroundDrawable, null);
- _nativeButton.SetBackground(_rippleDrawable);
- }
- else
- {
- _nativeButton.SetBackground(_backgroundDrawable);
- }
-
- _drawableEnabled = true;
- }
-
- _nativeButton.Invalidate();
- }
-
- public void Reset()
- {
- if (_drawableEnabled)
- {
- _drawableEnabled = false;
- _backgroundDrawable?.Reset();
- _backgroundDrawable = null;
- _rippleDrawable = null;
- }
- }
-
- public void Dispose()
- {
- Dispose(true);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposed)
- {
- if (disposing)
- {
- _backgroundDrawable?.Dispose();
- _backgroundDrawable = null;
- _defaultDrawable?.Dispose();
- _defaultDrawable = null;
- _rippleDrawable?.Dispose();
- _rippleDrawable = null;
- if (_button != null)
- {
- _button.PropertyChanged -= ButtonPropertyChanged;
- _button = null;
- }
- _nativeButton = null;
- }
- _disposed = true;
- }
- }
-
- void ButtonPropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- if (e.PropertyName.Equals(Button.BorderColorProperty.PropertyName) ||
- e.PropertyName.Equals(Button.BorderWidthProperty.PropertyName) ||
- e.PropertyName.Equals(Button.CornerRadiusProperty.PropertyName) ||
- e.PropertyName.Equals(VisualElement.BackgroundColorProperty.PropertyName) ||
- e.PropertyName.Equals(Specifics.Button.UseDefaultPaddingProperty.PropertyName) ||
- e.PropertyName.Equals(Specifics.Button.UseDefaultShadowProperty.PropertyName))
- {
- Reset();
- UpdateDrawable();
- }
- }
-
- }
-}
\ No newline at end of file
{
try
{
- await _imageView.UpdateBitmap(null, source, null, previousSource);
+ await _imageView.UpdateBitmap(source, previousSource).ConfigureAwait(false);
}
catch (Exception ex)
{
{
internal static class ImageViewExtensions
{
+ public static Task UpdateBitmap(this AImageView imageView, IImageController newView, IImageController previousView) =>
+ imageView.UpdateBitmap(newView, previousView, null, null);
+
+ public static Task UpdateBitmap(this AImageView imageView, ImageSource newImageSource, ImageSource previousImageSourc) =>
+ imageView.UpdateBitmap(null, null, newImageSource, previousImageSourc);
+
// TODO hartez 2017/04/07 09:33:03 Review this again, not sure it's handling the transition from previousImage to 'null' newImage correctly
- public static async Task UpdateBitmap(this AImageView imageView, Image newImage, ImageSource source, Image previousImage = null, ImageSource previousImageSource = null)
+ static async Task UpdateBitmap(
+ this AImageView imageView,
+ IImageController newView,
+ IImageController previousView,
+ ImageSource newImageSource,
+ ImageSource previousImageSource)
{
- if (imageView == null || imageView.IsDisposed())
- return;
- if (Device.IsInvokeRequired)
- throw new InvalidOperationException("Image Bitmap must not be updated from background thread");
+ newImageSource = newView?.Source;
+ previousImageSource = previousView?.Source;
- source = source ?? newImage?.Source;
- previousImageSource = previousImageSource ?? previousImage?.Source;
-
- if (Equals(previousImageSource, source))
+ if (newImageSource == null || imageView.IsDisposed())
return;
- var imageController = newImage as IImageController;
+ if (Equals(previousImageSource, newImageSource))
+ return;
- imageController?.SetIsLoading(true);
+ newView?.SetIsLoading(true);
(imageView as IImageRendererController)?.SkipInvalidate();
-
imageView.SetImageResource(global::Android.Resource.Color.Transparent);
bool setByImageViewHandler = false;
Bitmap bitmap = null;
- if (source != null)
+ try
{
- var imageViewHandler = Internals.Registrar.Registered.GetHandlerForObject<IImageViewHandler>(source);
- if (imageViewHandler != null)
+ if (newImageSource != null)
{
- try
+ var imageViewHandler = Internals.Registrar.Registered.GetHandlerForObject<IImageViewHandler>(newImageSource);
+ if (imageViewHandler != null)
{
- await imageViewHandler.LoadImageAsync(source, imageView);
+ await imageViewHandler.LoadImageAsync(newImageSource, imageView);
setByImageViewHandler = true;
}
- catch (TaskCanceledException)
- {
- imageController?.SetIsLoading(false);
- }
- }
- else
- {
- var imageSourceHandler = Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source);
- try
- {
- bitmap = await imageSourceHandler.LoadImageAsync(source, imageView.Context);
- }
- catch (TaskCanceledException)
+ else
{
- imageController?.SetIsLoading(false);
+ var imageSourceHandler = Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(newImageSource);
+ bitmap = await imageSourceHandler.LoadImageAsync(newImageSource, imageView.Context);
}
}
}
+ catch (TaskCanceledException)
+ {
+ newView?.SetIsLoading(false);
+ }
// Check if the source on the new image has changed since the image was loaded
- if (newImage != null && !Equals(newImage.Source, source))
+ if (!Equals(newView?.Source, newImageSource))
{
bitmap?.Dispose();
return;
if (!setByImageViewHandler && !imageView.IsDisposed())
{
- if (bitmap == null && source is FileImageSource)
- imageView.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)source).File));
+ if (bitmap == null && newImageSource is FileImageSource)
+ imageView.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)newImageSource).File));
else
- {
imageView.SetImageBitmap(bitmap);
- }
}
bitmap?.Dispose();
- imageController?.SetIsLoading(false);
- ((IVisualElementController)newImage)?.NativeSizeChanged();
-
- }
-
- public static async Task UpdateBitmap(this AImageView imageView, Image newImage, Image previousImage = null)
- {
- await UpdateBitmap(imageView, newImage, newImage?.Source, previousImage, previousImage?.Source);
-
+ newView?.SetIsLoading(false);
}
}
}
--- /dev/null
+
+using Android.Views;
+using AView = Android.Views.View;
+using AMotionEventActions = Android.Views.MotionEventActions;
+
+
+namespace Xamarin.Forms.Platform.Android.FastRenderers
+{
+ public static class ButtonElementManager
+ {
+
+ public static bool OnTouch(VisualElement element, IButtonController buttonController, AView v, MotionEvent e)
+ {
+ switch (e.Action)
+ {
+ case AMotionEventActions.Down:
+ buttonController?.SendPressed();
+ break;
+ case AMotionEventActions.Up:
+ buttonController?.SendReleased();
+ break;
+ }
+
+ return false;
+ }
+
+
+
+ public static void OnClick(VisualElement element, IButtonController buttonController, AView v)
+ {
+ buttonController?.SendClicked();
+ }
+ }
+}
\ No newline at end of file
using Android.Views;
using Xamarin.Forms.Internals;
using AView = Android.Views.View;
-using AMotionEventActions = Android.Views.MotionEventActions;
using static System.String;
+using AColor = Android.Graphics.Color;
+using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
+using Android.Support.V4.View;
namespace Xamarin.Forms.Platform.Android.FastRenderers
{
internal sealed class ButtonRenderer : AppCompatButton, IVisualElementRenderer, AView.IOnAttachStateChangeListener,
- AView.IOnFocusChangeListener, IEffectControlProvider, AView.IOnClickListener, AView.IOnTouchListener, IViewRenderer, ITabStop
+ AView.IOnFocusChangeListener, AView.IOnClickListener, AView.IOnTouchListener, IViewRenderer, ITabStop, IBorderVisualElementRenderer
{
float _defaultFontSize;
int? _defaultLabelFor;
bool _inputTransparent;
Lazy<TextColorSwitcher> _textColorSwitcher;
readonly AutomationPropertiesProvider _automationPropertiesProvider;
- readonly EffectControlProvider _effectControlProvider;
VisualElementTracker _tracker;
- ButtonBackgroundTracker _backgroundTracker;
+ VisualElementRenderer _visualElementRenderer;
+ BorderBackgroundManager _backgroundTracker;
Thickness _paddingDeltaPix = new Thickness();
+ IPlatformElementConfiguration<PlatformConfiguration.Android, Button> _platformElementConfiguration;
+ private Button _button;
public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
public ButtonRenderer(Context context) : base(context)
{
_automationPropertiesProvider = new AutomationPropertiesProvider(this);
- _effectControlProvider = new EffectControlProvider(this);
Initialize();
}
public ButtonRenderer() : base(Forms.Context)
{
_automationPropertiesProvider = new AutomationPropertiesProvider(this);
- _effectControlProvider = new EffectControlProvider(this);
Initialize();
}
ViewGroup IVisualElementRenderer.ViewGroup => null;
VisualElementTracker IVisualElementRenderer.Tracker => _tracker;
- Button Button { get; set; }
-
- AView ITabStop.TabStop => this;
-
- public void OnClick(AView v)
- {
- ((IButtonController)Button)?.SendClicked();
- }
-
- public bool OnTouch(AView v, MotionEvent e)
+ Button Button
{
- var buttonController = Element as IButtonController;
- switch (e.Action)
+ get => _button;
+ set
{
- case AMotionEventActions.Down:
- buttonController?.SendPressed();
- break;
- case AMotionEventActions.Up:
- buttonController?.SendReleased();
- break;
+ _button = value;
+ _platformElementConfiguration = null;
}
-
- return false;
}
- void IEffectControlProvider.RegisterEffect(Effect effect)
- {
- _effectControlProvider.RegisterEffect(effect);
- }
+ AView ITabStop.TabStop => this;
+
+ void IOnClickListener.OnClick(AView v) => ButtonElementManager.OnClick(Button, Button, v);
+
+ bool IOnTouchListener.OnTouch(AView v, MotionEvent e) => ButtonElementManager.OnTouch(Button, Button, v, e);
void IOnAttachStateChangeListener.OnViewAttachedToWindow(AView attachedView)
{
void IOnFocusChangeListener.OnFocusChange(AView v, bool hasFocus)
{
- OnNativeFocusChanged(hasFocus);
-
((IElementController)Button).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, hasFocus);
}
oldElement.PropertyChanged -= OnElementPropertyChanged;
}
- if (_backgroundTracker == null)
- _backgroundTracker = new ButtonBackgroundTracker(Button, this);
- else
- _backgroundTracker.Button = Button;
-
- Color currentColor = oldElement?.BackgroundColor ?? Color.Default;
- if (element.BackgroundColor != currentColor)
- {
- UpdateBackgroundColor();
- }
element.PropertyChanged += OnElementPropertyChanged;
// Can't set up the tracker in the constructor because it access the Element (for now)
SetTracker(new VisualElementTracker(this));
}
+ if (_visualElementRenderer == null)
+ {
+ _visualElementRenderer = new VisualElementRenderer(this);
+ }
OnElementChanged(new ElementChangedEventArgs<Button>(oldElement as Button, Button));
SendVisualElementInitialized(element, this);
- EffectUtilities.RegisterEffectControlProvider(this, oldElement, element);
-
Performance.Stop(reference);
}
LabelFor = (int)(id ?? _defaultLabelFor);
}
- void IVisualElementRenderer.UpdateLayout()
- {
- var reference = Guid.NewGuid().ToString();
- _tracker?.UpdateLayout();
- }
+ void IVisualElementRenderer.UpdateLayout() => _tracker?.UpdateLayout();
void IViewRenderer.MeasureExactly()
{
_automationPropertiesProvider?.Dispose();
_tracker?.Dispose();
-
+ _visualElementRenderer?.Dispose();
_backgroundTracker?.Dispose();
_backgroundTracker = null;
void OnElementChanged(ElementChangedEventArgs<Button> e)
{
- if (e.OldElement != null)
- {
- _backgroundTracker?.Reset();
- }
if (e.NewElement != null && !_isDisposed)
{
this.EnsureId();
UpdateText();
UpdateBitmap();
UpdateTextColor();
- UpdateIsEnabled();
UpdateInputTransparent();
UpdateBackgroundColor();
UpdatePadding();
{
UpdateTextColor();
}
- else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
- {
- UpdateIsEnabled();
- }
else if (e.PropertyName == Button.FontProperty.PropertyName)
{
UpdateFont();
OnFocusChangeListener = this;
Tag = this;
+ _backgroundTracker = new BorderBackgroundManager(this);
}
void UpdateBitmap()
}
}
- void UpdateIsEnabled()
- {
- if (Element == null || _isDisposed)
- {
- return;
- }
-
- Enabled = Element.IsEnabled;
- }
-
void UpdateInputTransparent()
{
if (Element == null || _isDisposed)
UpdatePadding();
}
+ float IBorderVisualElementRenderer.ShadowRadius => ShadowRadius;
+ float IBorderVisualElementRenderer.ShadowDx => ShadowDx;
+ float IBorderVisualElementRenderer.ShadowDy => ShadowDy;
+ AColor IBorderVisualElementRenderer.ShadowColor => ShadowColor;
+ bool IBorderVisualElementRenderer.UseDefaultPadding() => OnThisPlatform().UseDefaultPadding();
+ bool IBorderVisualElementRenderer.UseDefaultShadow() => OnThisPlatform().UseDefaultShadow();
+ bool IBorderVisualElementRenderer.IsShadowEnabled() => true;
+ AView IBorderVisualElementRenderer.View => this;
+
+ IPlatformElementConfiguration<PlatformConfiguration.Android, Button> OnThisPlatform()
+ {
+ if (_platformElementConfiguration == null)
+ _platformElementConfiguration = Button.OnThisPlatform();
+
+ return _platformElementConfiguration;
+ }
}
}
--- /dev/null
+using System.ComponentModel;
+using System.Threading.Tasks;
+using Android.Widget;
+using AScaleType = Android.Widget.ImageView.ScaleType;
+using ARect = Android.Graphics.Rect;
+using System;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Platform.Android.FastRenderers
+{
+ public static class ImageElementManager
+ {
+ public static void Init(IVisualElementRenderer renderer)
+ {
+ renderer.ElementPropertyChanged += OnElementPropertyChanged;
+ renderer.ElementChanged += OnElementChanged;
+ renderer.LayoutChange += OnLayoutChange;
+ }
+
+ static void OnLayoutChange(object sender, global::Android.Views.View.LayoutChangeEventArgs e)
+ {
+ if(sender is IVisualElementRenderer renderer && renderer.View is ImageView imageView)
+ imageView.ClipBounds = imageView.GetScaleType() == AScaleType.CenterCrop ? new ARect(0, 0, e.Right - e.Left, e.Bottom - e.Top) : null;
+ }
+
+ public static void Dispose(IVisualElementRenderer renderer)
+ {
+ renderer.ElementPropertyChanged -= OnElementPropertyChanged;
+ renderer.ElementChanged -= OnElementChanged;
+ renderer.LayoutChange -= OnLayoutChange;
+ }
+
+ async static void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+ {
+ var renderer = (sender as IVisualElementRenderer);
+ var view = renderer.View as ImageView;
+ var newImageElementManager = e.NewElement as IImageController;
+ var oldImageElementManager = e.OldElement as IImageController;
+ var rendererController = renderer as IImageRendererController;
+
+ await TryUpdateBitmap(rendererController, view, newImageElementManager, oldImageElementManager);
+ UpdateAspect(rendererController, view, newImageElementManager, oldImageElementManager);
+
+ if (!rendererController.IsDisposed)
+ {
+ ElevationHelper.SetElevation(view, renderer.Element);
+ }
+ }
+
+ async static void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ var renderer = (sender as IVisualElementRenderer);
+ var ImageElementManager = (IImageController)renderer.Element;
+ if (e.PropertyName == ImageElementManager.SourceProperty?.PropertyName)
+ {
+ try
+ {
+ await TryUpdateBitmap(renderer as IImageRendererController, (ImageView)renderer.View, (IImageController)renderer.Element).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Log.Warning(renderer.GetType().Name, "Error loading image: {0}", ex);
+ }
+ finally
+ {
+ ImageElementManager?.SetIsLoading(false);
+ }
+ }
+ else if (e.PropertyName == ImageElementManager.AspectProperty?.PropertyName)
+ {
+ UpdateAspect(renderer as IImageRendererController, (ImageView)renderer.View, (IImageController)renderer.Element);
+ }
+ }
+
+
+ async static Task TryUpdateBitmap(IImageRendererController rendererController, ImageView Control, IImageController newImage, IImageController previous = null)
+ {
+ if (newImage == null || rendererController.IsDisposed)
+ {
+ return;
+ }
+
+ await Control.UpdateBitmap(newImage, previous).ConfigureAwait(false);
+ }
+
+ static void UpdateAspect(IImageRendererController rendererController, ImageView Control, IImageController newImage, IImageController previous = null)
+ {
+ if (newImage == null || rendererController.IsDisposed)
+ {
+ return;
+ }
+
+ ImageView.ScaleType type = newImage.Aspect.ToScaleType();
+ Control.SetScaleType(type);
+ }
+ }
+}
\ No newline at end of file
VisualElementRenderer _visualElementRenderer;
readonly MotionEventHelper _motionEventHelper = new MotionEventHelper();
+ bool IImageRendererController.IsDisposed => _disposed;
protected override void Dispose(bool disposing)
{
if (_disposed)
if (disposing)
{
+ ImageElementManager.Dispose(this);
+ BackgroundManager.Dispose(this);
+
if (_visualElementTracker != null)
{
_visualElementTracker.Dispose();
base.Invalidate();
}
- async void OnElementChanged(ElementChangedEventArgs<Image> e)
+ void OnElementChanged(ElementChangedEventArgs<Image> e)
{
- await TryUpdateBitmap(e.OldElement);
- UpdateAspect();
this.EnsureId();
-
- ElevationHelper.SetElevation(this, e.NewElement);
-
ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
}
element.PropertyChanged += OnElementPropertyChanged;
if (_visualElementTracker == null)
+ {
_visualElementTracker = new VisualElementTracker(this);
+ }
if (_visualElementRenderer == null)
{
_visualElementRenderer = new VisualElementRenderer(this);
+ BackgroundManager.Init(this);
+ ImageElementManager.Init(this);
}
Performance.Stop(reference);
_element?.SendViewInitialized(Control);
}
-
+
void IVisualElementRenderer.SetLabelFor(int? id)
{
if (_defaultLabelFor == null)
{
}
- async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
- if (e.PropertyName == Image.SourceProperty.PropertyName)
- await TryUpdateBitmap();
- else if (e.PropertyName == Image.AspectProperty.PropertyName)
- UpdateAspect();
-
ElementPropertyChanged?.Invoke(this, e);
- }
-
- async Task TryUpdateBitmap(Image previous = null)
- {
- // By default we'll just catch and log any exceptions thrown by UpdateBitmap so they don't bring down
- // the application; a custom renderer can override this method and handle exceptions from
- // UpdateBitmap differently if it wants to
-
- try
- {
- await UpdateBitmap(previous);
- }
- catch (Exception ex)
- {
- Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
- }
- finally
- {
- ((IImageController)_element)?.SetIsLoading(false);
- }
- }
-
- async Task UpdateBitmap(Image previous = null)
- {
- if (_element == null || _disposed)
- {
- return;
- }
-
- await Control.UpdateBitmap(_element, previous);
- }
-
- protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
- {
- base.OnLayout(changed, left, top, right, bottom);
- ClipBounds = GetScaleType() == ScaleType.CenterCrop ? new Rect(0, 0, right - left, bottom - top) : null;
- }
-
- void UpdateAspect()
- {
- if (_element == null || _disposed)
- {
- return;
- }
-
- ScaleType type = _element.Aspect.ToScaleType();
- SetScaleType(type);
- }
- }
+ }
+ }
}
{
_labelTextColorDefault = TextColors;
_visualElementRenderer = new VisualElementRenderer(this);
+ BackgroundManager.Init(this);
}
[Obsolete("This constructor is obsolete as of version 2.5. Please use LabelRenderer(Context) instead.")]
- public LabelRenderer(): base(Forms.Context)
+ public LabelRenderer() : base(Forms.Context)
{
_labelTextColorDefault = TextColors;
_visualElementRenderer = new VisualElementRenderer(this);
+ BackgroundManager.Init(this);
}
public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
OnElementChanged(new ElementChangedEventArgs<Label>(oldElement, _element));
- _element?.SendViewInitialized(this);
+ _element?.SendViewInitialized(this);
}
}
SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
{
- if (_disposed)
- {
- return new SizeRequest();
- }
-
+ if (_disposed)
+ {
+ return new SizeRequest();
+ }
+
if (_lastSizeRequest.HasValue)
{
// if we are measuring the same thing, no need to waste the time
if (disposing)
{
+ BackgroundManager.Dispose(this);
if (_visualElementTracker != null)
{
_visualElementTracker.Dispose();
base.Dispose(disposing);
}
- public override bool OnTouchEvent(MotionEvent e)
- {
- if (_visualElementRenderer.OnTouchEvent(e) || base.OnTouchEvent(e))
- {
- return true;
- }
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ if (_visualElementRenderer.OnTouchEvent(e) || base.OnTouchEvent(e))
+ {
+ return true;
+ }
- return _motionEventHelper.HandleMotionEvent(Parent, e);
- }
+ return _motionEventHelper.HandleMotionEvent(Parent, e);
+ }
- void OnElementChanged(ElementChangedEventArgs<Label> e)
+ void OnElementChanged(ElementChangedEventArgs<Label> e)
{
ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
internal sealed class VisualElementRenderer : IDisposable, IEffectControlProvider, ITabStop
{
bool _disposed;
-
+
IVisualElementRenderer _renderer;
readonly GestureManager _gestureManager;
readonly AutomationPropertiesProvider _automationPropertiesProvider;
_effectControlProvider.RegisterEffect(effect);
}
- public void UpdateBackgroundColor(Color? color = null)
- {
- if (_disposed || Element == null || Control == null)
- return;
-
- Control.SetBackgroundColor((color ?? Element.BackgroundColor).ToAndroid());
- }
void UpdateFlowDirection()
{
}
public bool OnTouchEvent(MotionEvent e)
- {
- return _gestureManager.OnTouchEvent(e);
- }
+ {
+ return _gestureManager.OnTouchEvent(e);
+ }
- public void Dispose()
+ public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
void OnElementChanged(object sender, VisualElementChangedEventArgs e)
{
+ Performance.Start(out string reference);
if (e.OldElement != null)
{
e.OldElement.PropertyChanged -= OnElementPropertyChanged;
if (e.NewElement != null)
{
e.NewElement.PropertyChanged += OnElementPropertyChanged;
- UpdateBackgroundColor();
UpdateFlowDirection();
+ UpdateIsEnabled();
}
EffectUtilities.RegisterEffectControlProvider(this, e.OldElement, e.NewElement);
+ Performance.Stop(reference);
+ }
+
+ void UpdateIsEnabled()
+ {
+ if (Element == null || _disposed)
+ {
+ return;
+ }
+
+ Control.Enabled = Element.IsEnabled;
}
void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
- if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
- UpdateBackgroundColor();
- else if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
+ if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
+ {
UpdateFlowDirection();
+ }
+ else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
+ {
+ UpdateIsEnabled();
+ }
}
}
}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Android.App;
+using Android.Content;
+using Android.OS;
+using Android.Runtime;
+using Android.Views;
+using Android.Widget;
+using AColor = Android.Graphics.Color;
+using AView = Android.Views.View;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ public interface IBorderVisualElementRenderer
+ {
+ float ShadowRadius { get; }
+ float ShadowDx { get; }
+ float ShadowDy { get; }
+ AColor ShadowColor { get; }
+ bool UseDefaultPadding();
+ bool UseDefaultShadow();
+ bool IsShadowEnabled();
+ VisualElement Element { get; }
+ AView View { get; }
+ event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+ }
+}
\ No newline at end of file
using System.ComponentModel;
using Android.Views;
using AView = Android.Views.View;
+using ALayoutChangeEventArgs = Android.Views.View.LayoutChangeEventArgs;
namespace Xamarin.Forms.Platform.Android
{
void SetLabelFor(int? id);
void UpdateLayout();
+
+ event EventHandler<ALayoutChangeEventArgs> LayoutChange;
}
}
\ No newline at end of file
[assembly: ExportRenderer (typeof (Label), typeof (LabelRenderer))]
[assembly: ExportRenderer (typeof (Image), typeof (ImageRenderer))]
[assembly: ExportRenderer (typeof (Button), typeof (ButtonRenderer))]
+[assembly: ExportRenderer (typeof (ImageButton), typeof (ImageButtonRenderer))]
[assembly: ExportRenderer (typeof (TableView), typeof (TableViewRenderer))]
[assembly: ExportRenderer (typeof (ListView), typeof (ListViewRenderer))]
[assembly: ExportRenderer (typeof (Slider), typeof (SliderRenderer))]
namespace Xamarin.Forms.Platform.Android
{
- internal class ButtonDrawable : Drawable
+ internal class BorderDrawable : Drawable
{
public const int DefaultCornerRadius = 2; // Default value for Android material button.
float _paddingLeft;
float _paddingTop;
Color _defaultColor;
-
+ readonly bool _drawOutlineWithBackground;
AColor _shadowColor;
float _shadowDx;
float _shadowDy;
set { _paddingTop = value; }
}
- public ButtonDrawable(Func<double, float> convertToPixels, Color defaultColor)
+ public BorderDrawable(Func<double, float> convertToPixels, Color defaultColor, bool drawOutlineWithBackground)
{
_convertToPixels = convertToPixels;
_pressed = false;
_defaultColor = defaultColor;
+ _drawOutlineWithBackground = drawOutlineWithBackground;
}
- public Button Button { get; set; }
+ public IBorderController BorderController
+ {
+ get;
+ set;
+ }
public override bool IsStateful
{
public override void Draw(Canvas canvas)
{
+ //Bounds = new Rect(Bounds.Left, Bounds.Top, Bounds.Right + (int)_convertToPixels(10), Bounds.Bottom + (int)_convertToPixels(10));
int width = Bounds.Width();
int height = Bounds.Height();
_normalBitmap.Width != width)
Reset();
+ if (!_drawOutlineWithBackground && BorderController.BackgroundColor == Color.Default)
+ return;
+
Bitmap bitmap = null;
if (GetState().Contains(global::Android.Resource.Attribute.StatePressed))
{
canvas.DrawBitmap(bitmap, 0, 0, new Paint());
}
- public ButtonDrawable SetShadow(float dy, float dx, AColor color, float radius)
+ public BorderDrawable SetShadow(float dy, float dx, AColor color, float radius)
{
_shadowDx = dx;
_shadowDy = dy;
return this;
}
- public ButtonDrawable SetPadding(float top, float left)
+ public BorderDrawable SetPadding(float top, float left)
{
_paddingTop = top;
_paddingLeft = left;
{
}
- public Color BackgroundColor => Button.BackgroundColor == Color.Default ? _defaultColor : Button.BackgroundColor;
+ public Color BackgroundColor => BorderController.BackgroundColor == Color.Default ? _defaultColor : BorderController.BackgroundColor;
public Color PressedBackgroundColor => BackgroundColor.AddLuminosity(-.12);//<item name="highlight_alpha_material_light" format="float" type="dimen">0.12</item>
protected override void Dispose(bool disposing)
using (var canvas = new Canvas(bitmap))
{
DrawBackground(canvas, width, height, pressed);
- DrawOutline(canvas, width, height);
+ if (_drawOutlineWithBackground)
+ DrawOutline(canvas, width, height);
}
return bitmap;
{
int cornerRadius = DefaultCornerRadius;
- if (Button.IsSet(Button.CornerRadiusProperty) && Button.CornerRadius != (int)Button.CornerRadiusProperty.DefaultValue)
- cornerRadius = Button.CornerRadius;
+ if (BorderController.IsSet(BorderController.CornerRadiusProperty) && BorderController.CornerRadius != (int)BorderController.CornerRadiusProperty.DefaultValue)
+ cornerRadius = BorderController.CornerRadius;
return _convertToPixels(cornerRadius);
}
- void DrawOutline(Canvas canvas, int width, int height)
+ public RectF GetPaddingBounds(int width, int height)
+ {
+ RectF rect = new RectF(0, 0, width, height);
+ rect.Inset(PaddingLeft, PaddingTop);
+ return rect;
+ }
+
+ public void DrawOutline(Canvas canvas, int width, int height)
{
- if (Button.BorderWidth <= 0)
+ if (BorderController.BorderWidth <= 0)
return;
using (var paint = new Paint { AntiAlias = true })
using (var path = new Path())
{
- float borderWidth = _convertToPixels(Button.BorderWidth);
+ float borderWidth = _convertToPixels(BorderController.BorderWidth);
float inset = borderWidth / 2;
// adjust border radius so outer edge of stroke is same radius as border radius of background
path.AddRoundRect(rect, borderRadius, borderRadius, Path.Direction.Cw);
paint.StrokeWidth = borderWidth;
paint.SetStyle(Paint.Style.Stroke);
- paint.Color = Button.BorderColor.ToAndroid();
+ paint.Color = BorderController.BorderColor.ToAndroid();
canvas.DrawPath(path, paint);
}
-using System;
+using System;
using System.ComponentModel;
using Android.Content;
using Android.Graphics;
using AMotionEvent = Android.Views.MotionEvent;
using AMotionEventActions = Android.Views.MotionEventActions;
using Object = Java.Lang.Object;
+using AColor = Android.Graphics.Color;
namespace Xamarin.Forms.Platform.Android
{
- public class ButtonRenderer : ViewRenderer<Button, AButton>, AView.IOnAttachStateChangeListener
+ public class ButtonRenderer : ViewRenderer<Button, AButton>, AView.IOnAttachStateChangeListener, IBorderVisualElementRenderer
{
- ButtonBackgroundTracker _backgroundTracker;
+ BorderBackgroundManager _backgroundTracker;
TextColorSwitcher _textColorSwitcher;
float _defaultFontSize;
Typeface _defaultTypeface;
bool _isDisposed;
int _imageHeight = -1;
Thickness _paddingDeltaPix = new Thickness();
+ IVisualElementRenderer _visualElementRenderer;
public ButtonRenderer(Context context) : base(context)
{
AutoPackage = false;
+ _visualElementRenderer = this;
+ _backgroundTracker = new BorderBackgroundManager(this);
}
[Obsolete("This constructor is obsolete as of version 2.5. Please use ButtonRenderer(Context) instead.")]
public ButtonRenderer()
{
AutoPackage = false;
+ _visualElementRenderer = this;
+ _backgroundTracker = new BorderBackgroundManager(this);
}
AButton NativeButton
{
_backgroundTracker?.Dispose();
_backgroundTracker = null;
+ _visualElementRenderer = null;
}
base.Dispose(disposing);
}
}
- if (_backgroundTracker == null)
- _backgroundTracker = new ButtonBackgroundTracker(Element, Control);
- else
- _backgroundTracker.Button = e.NewElement;
-
UpdateAll();
}
_textColorSwitcher?.UpdateTextColor(Control, Element.TextColor);
}
+ float IBorderVisualElementRenderer.ShadowRadius => Control.ShadowRadius;
+ float IBorderVisualElementRenderer.ShadowDx => Control.ShadowDx;
+ float IBorderVisualElementRenderer.ShadowDy => Control.ShadowDy;
+ AColor IBorderVisualElementRenderer.ShadowColor => Control.ShadowColor;
+ bool IBorderVisualElementRenderer.UseDefaultPadding() => Element.OnThisPlatform().UseDefaultPadding();
+ bool IBorderVisualElementRenderer.UseDefaultShadow() => Element.OnThisPlatform().UseDefaultShadow();
+ bool IBorderVisualElementRenderer.IsShadowEnabled() => true;
+ VisualElement IBorderVisualElementRenderer.Element => Element;
+ AView IBorderVisualElementRenderer.View => Control;
+ event EventHandler<VisualElementChangedEventArgs> IBorderVisualElementRenderer.ElementChanged
+ {
+ add => _visualElementRenderer.ElementChanged += value;
+ remove => _visualElementRenderer.ElementChanged -= value;
+ }
+
void UpdatePadding()
{
Control?.SetPadding(
{
_skipInvalidate = true;
}
+
+ bool IImageRendererController.IsDisposed => false;
}
}
\ No newline at end of file
internal interface IImageRendererController
{
void SkipInvalidate();
+ bool IsDisposed { get; }
}
public class ImageRenderer : ViewRenderer<Image, AImageView>
return;
}
- await Control.UpdateBitmap(Element, previous);
+ await Control.UpdateBitmap(Element, previous).ConfigureAwait(false);
}
public override bool OnTouchEvent(MotionEvent e)
using Android.Widget;
using AView = Android.Views.View;
using Xamarin.Forms.Internals;
+using AImageButton = Android.Widget.ImageButton;
namespace Xamarin.Forms.Platform.Android
{
class MenuElementView : LinearLayout
{
- readonly ImageButton _image;
+ readonly AImageButton _image;
readonly TextView _label;
string _icon;
public MenuElementView(Context context) : base(context)
{
Orientation = Orientation.Vertical;
- _image = new ImageButton(context);
+ _image = new AImageButton(context);
_image.SetScaleType(ImageView.ScaleType.FitCenter);
_image.Click += (object sender, EventArgs e) =>
{
_defaultTrackDrawable = Control.TrackDrawable;
}
else
+ {
UpdateEnabled(); // Normally set by SetNativeControl, but not when the Control is reused.
+ }
e.NewElement.Toggled += HandleToggled;
Control.Checked = e.NewElement.IsToggled;
using System.ComponentModel;
using Android.Content;
using Android.Graphics;
-using Android.Widget;
+using AImageButton = Android.Widget.ImageButton;
namespace Xamarin.Forms.Platform.Android
{
- internal sealed class ToolbarImageButton : ImageButton, IToolbarButton
+ internal sealed class ToolbarImageButton : AImageButton, IToolbarButton
{
IMenuItemController Controller => Item;
public ToolbarImageButton(Context context, ToolbarItem item) : base(context)
TElement oldElement = Element;
Element = element;
- var reference = Guid.NewGuid().ToString();
- Performance.Start(reference);
+ Performance.Start(out string reference);
if (oldElement != null)
{
public void UpdateLayout()
{
- var reference = Guid.NewGuid().ToString();
- Performance.Start(reference);
+ Performance.Start(out string reference);
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
UpdateAnchorX();
else if (e.PropertyName == VisualElement.AnchorYProperty.PropertyName)
UpdateAnchorY();
- else if ( e.PropertyName == VisualElement.ScaleProperty.PropertyName
+ else if (e.PropertyName == VisualElement.ScaleProperty.PropertyName
|| e.PropertyName == VisualElement.ScaleXProperty.PropertyName
|| e.PropertyName == VisualElement.ScaleYProperty.PropertyName)
UpdateScale();
<Compile Include="AndroidApplicationLifecycleState.cs" />
<Compile Include="AndroidTitleBarVisibility.cs" />
<Compile Include="AppCompat\FrameRenderer.cs" />
+ <Compile Include="BackgroundManager.cs" />
<Compile Include="AppCompat\ILifeCycleState.cs" />
- <Compile Include="ButtonBackgroundTracker.cs" />
+ <Compile Include="BorderBackgroundManager.cs" />
<Compile Include="Elevation.cs" />
<Compile Include="Extensions\EntryRendererExtensions.cs" />
<Compile Include="Extensions\FragmentManagerExtensions.cs" />
<Compile Include="FastRenderers\AutomationPropertiesProvider.cs" />
<Compile Include="AppCompat\PageExtensions.cs" />
<Compile Include="Extensions\JavaObjectExtensions.cs" />
+ <Compile Include="FastRenderers\ButtonElementManager.cs" />
<Compile Include="FastRenderers\ButtonRenderer.cs" />
<Compile Include="AppCompat\FormsViewPager.cs" />
<Compile Include="AppCompat\FragmentContainer.cs" />
<Compile Include="ExportImageSourceHandlerAttribute.cs" />
<Compile Include="ExportRendererAttribute.cs" />
<Compile Include="Extensions\FlowDirectionExtensions.cs" />
+ <Compile Include="AppCompat\ImageButtonRenderer.cs" />
+ <Compile Include="FastRenderers\ImageElementManager.cs" />
<Compile Include="GestureManager.cs" />
<Compile Include="FastRenderers\LabelRenderer.cs" />
<Compile Include="FastRenderers\VisualElementRenderer.cs" />
<Compile Include="ColorExtensions.cs" />
<Compile Include="ContextExtensions.cs" />
<Compile Include="GetDesiredSizeDelegate.cs" />
+ <Compile Include="IBorderVisualElementRenderer.cs" />
<Compile Include="IDeviceInfoProvider.cs" />
<Compile Include="ITabStop.cs" />
<Compile Include="Renderers\FormsWebViewClient.cs" />
<Compile Include="LayoutExtensions.cs" />
<Compile Include="PowerSaveModeBroadcastReceiver.cs" />
<Compile Include="Renderers\AHorizontalScrollView.cs" />
- <Compile Include="Renderers\ButtonDrawable.cs" />
<Compile Include="Renderers\AlignmentExtensions.cs" />
+ <Compile Include="Renderers\BorderDrawable.cs" />
+ <Compile Include="Renderers\ButtonRenderer.cs" />
<Compile Include="Renderers\ConditionalFocusLayout.cs" />
<Compile Include="Renderers\DescendantFocusToggler.cs" />
<Compile Include="Renderers\FormsEditText.cs" />
<Compile Include="Renderers\ActionSheetRenderer.cs" />
<Compile Include="Renderers\ActivityIndicatorRenderer.cs" />
<Compile Include="Renderers\BoxRenderer.cs" />
- <Compile Include="Renderers\ButtonRenderer.cs" />
<Compile Include="Renderers\CarouselPageRenderer.cs" />
<Compile Include="Renderers\DatePickerRenderer.cs" />
<Compile Include="Renderers\EditorRenderer.cs" />
using System.ComponentModel;
using Xamarin.Forms.Platform.GTK.Controls;
using Xamarin.Forms.Platform.GTK.Extensions;
+using GtkImageButton = Xamarin.Forms.Platform.GTK.Controls.ImageButton;
namespace Xamarin.Forms.Platform.GTK.Renderers
{
- public class ButtonRenderer : ViewRenderer<Button, ImageButton>
+ public class ButtonRenderer : ViewRenderer<Button, GtkImageButton>
{
private const uint DefaultBorderWidth = 1;
{
if (Control == null)
{
- // To allow all avalaible options in Xamarin.Forms, a custom control has been created.
+ // To allow all available options in Xamarin.Forms, a custom control has been created.
// Can set text, text color, border, image, etc.
- var btn = new ImageButton();
+ var btn = new GtkImageButton();
SetNativeControl(btn);
Control.Clicked += OnButtonClicked;
{
var elemValue = (string)Element?.GetValue(AutomationProperties.NameProperty);
- if (string.IsNullOrWhiteSpace(elemValue)
+ if (string.IsNullOrWhiteSpace(elemValue)
&& Control?.Accessible.Description == Control?.LabelWidget.Text)
return;
{
public class ImageRenderer : ViewRenderer<Image, Controls.ImageControl>
{
- private bool _isDisposed;
+ bool _isDisposed;
protected override void Dispose(bool disposing)
{
Control.SetSizeRequest(allocation.Width, allocation.Height);
}
- private async void SetImage(Image oldElement = null)
+ async void SetImage(Image oldElement = null)
{
var source = Element.Source;
((IImageController)Element).SetIsLoading(false);
}
- private void SetAspect()
+ void SetAspect()
{
switch (Element.Aspect)
{
}
}
- private void SetOpacity()
+ void SetOpacity()
{
var opacity = Element.Opacity;
<Link>Flags.cs</Link>
</Compile>
<Compile Include="Controls\FormsBoxView.cs" />
+ <Compile Include="..\Xamarin.Forms.Platform.iOS\IVisualNativeElementRenderer.cs">
+ <Link>IVisualNativeElementRenderer.cs</Link>
+ </Compile>
<Compile Include="Controls\FormsNSDatePicker.cs" />
<Compile Include="Extensions\FlowDirectionExtensions.cs" />
<Compile Include="FormsApplicationDelegate.cs" />
--- /dev/null
+
+namespace Xamarin.Forms.Platform.UWP
+{
+ public interface IImageVisualElementRenderer : IVisualNativeElementRenderer
+ {
+ bool IsDisposed { get; }
+ void SetImage(Windows.UI.Xaml.Media.ImageSource image);
+ Windows.UI.Xaml.Controls.Image GetImage();
+ }
+}
--- /dev/null
+using System;
+using System.ComponentModel;
+
+namespace Xamarin.Forms.Platform.UWP
+{
+ public interface IVisualNativeElementRenderer : IVisualElementRenderer
+ {
+ event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
+ event EventHandler ControlChanging;
+ event EventHandler ControlChanged;
+ }
+}
--- /dev/null
+using System;
+using System.ComponentModel;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Media.Imaging;
+using Xamarin.Forms.Internals;
+using WThickness = Windows.UI.Xaml.Thickness;
+using WImage = Windows.UI.Xaml.Controls.Image;
+using Windows.UI.Xaml.Input;
+using System.Threading.Tasks;
+using System.Diagnostics;
+
+namespace Xamarin.Forms.Platform.UWP
+{
+ public class ImageButtonRenderer : ViewRenderer<ImageButton, FormsButton>, IImageVisualElementRenderer
+ {
+ bool _measured;
+ bool _disposed;
+ WImage _image;
+ FormsButton _formsButton;
+
+ public ImageButtonRenderer() : base()
+ {
+ ImageElementManager.Init(this);
+ }
+
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+
+ if (disposing)
+ {
+ ImageElementManager.Dispose(this);
+ if (Control != null)
+ {
+ _image.ImageOpened -= OnImageOpened;
+ _image.ImageFailed -= OnImageFailed;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
+ {
+ if (_image?.Source == null)
+ return new SizeRequest();
+
+ _measured = true;
+
+
+ var result = new Size
+ {
+ Width = ((BitmapSource)_image.Source).PixelWidth,
+ Height = ((BitmapSource)_image.Source).PixelHeight
+ };
+
+ return new SizeRequest(result);
+ }
+
+
+ protected async override void OnElementChanged(ElementChangedEventArgs<ImageButton> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ _formsButton = new FormsButton();
+ _formsButton.Padding = new WThickness(0);
+ _formsButton.BorderThickness = new WThickness(0);
+ _formsButton.Background = null;
+
+ _image = new Windows.UI.Xaml.Controls.Image()
+ {
+ VerticalAlignment = VerticalAlignment.Center,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ Stretch = Stretch.Uniform,
+ };
+
+ _image.ImageOpened += OnImageOpened;
+ _image.ImageFailed += OnImageFailed;
+ _formsButton.Content = _image;
+
+ _formsButton.Click += OnButtonClick;
+ _formsButton.AddHandler(PointerPressedEvent, new PointerEventHandler(OnPointerPressed), true);
+ _formsButton.Loaded += ButtonOnLoaded;
+
+ SetNativeControl(_formsButton);
+
+ }
+ else
+ {
+ WireUpFormsVsm();
+ }
+
+ //TODO: We may want to revisit this strategy later. If a user wants to reset any of these to the default, the UI won't update.
+ if (Element.IsSet(VisualElement.BackgroundColorProperty) && Element.BackgroundColor != (Color)VisualElement.BackgroundColorProperty.DefaultValue)
+ UpdateBackground();
+
+ if (Element.IsSet(ImageButton.BorderColorProperty) && Element.BorderColor != (Color)ImageButton.BorderColorProperty.DefaultValue)
+ UpdateBorderColor();
+
+ if (Element.IsSet(ImageButton.BorderWidthProperty) && Element.BorderWidth != (double)ImageButton.BorderWidthProperty.DefaultValue)
+ UpdateBorderWidth();
+
+ if (Element.IsSet(ImageButton.CornerRadiusProperty) && Element.CornerRadius != (int)ImageButton.CornerRadiusProperty.DefaultValue)
+ UpdateBorderRadius();
+ if (Element.IsSet(Button.PaddingProperty) && Element.Padding != (Thickness)Button.PaddingProperty.DefaultValue)
+ UpdatePadding();
+
+ await TryUpdateSource().ConfigureAwait(false);
+
+ }
+ }
+
+ protected virtual async Task TryUpdateSource()
+ {
+ // By default we'll just catch and log any exceptions thrown by UpdateSource so we don't bring down
+ // the application; a custom renderer can override this method and handle exceptions from
+ // UpdateSource differently if it wants to
+
+ try
+ {
+ await UpdateSource().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
+ }
+ finally
+ {
+ ((IImageController)Element)?.SetIsLoading(false);
+ }
+ }
+
+ protected async Task UpdateSource()
+ {
+ await ImageElementManager.UpdateSource(this).ConfigureAwait(false);
+ }
+
+
+ void OnImageOpened(object sender, RoutedEventArgs routedEventArgs)
+ {
+ if (_measured)
+ {
+ ImageElementManager.RefreshImage(Element);
+ }
+
+ Element?.SetIsLoading(false);
+ }
+
+ protected virtual void OnImageFailed(object sender, ExceptionRoutedEventArgs exceptionRoutedEventArgs)
+ {
+ Log.Warning("Image Loading", $"Image failed to load: {exceptionRoutedEventArgs.ErrorMessage}");
+ Element?.SetIsLoading(false);
+ }
+
+
+
+ void ButtonOnLoaded(object o, RoutedEventArgs routedEventArgs)
+ {
+ WireUpFormsVsm();
+ }
+
+ void WireUpFormsVsm()
+ {
+ if (Element.UseFormsVsm())
+ {
+ InterceptVisualStateManager.Hook(Control.GetFirstDescendant<Windows.UI.Xaml.Controls.Grid>(), Control, Element);
+ }
+ }
+
+ protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ {
+ UpdateBackground();
+ }
+ else if (e.PropertyName == ImageButton.BorderColorProperty.PropertyName)
+ {
+ UpdateBorderColor();
+ }
+ else if (e.PropertyName == ImageButton.BorderWidthProperty.PropertyName)
+ {
+ UpdateBorderWidth();
+ }
+ else if (e.PropertyName == ImageButton.CornerRadiusProperty.PropertyName)
+ {
+ UpdateBorderRadius();
+ }
+ else if (e.PropertyName == ImageButton.PaddingProperty.PropertyName)
+ {
+ UpdatePadding();
+ }
+ else if (e.PropertyName == ImageButton.SourceProperty.PropertyName)
+ await TryUpdateSource().ConfigureAwait(false);
+ }
+ void UpdatePadding()
+ {
+ _image.Margin = new WThickness(0);
+
+ Control.Padding = new WThickness(
+ Element.Padding.Left,
+ Element.Padding.Top,
+ Element.Padding.Right,
+ Element.Padding.Bottom
+ );
+ }
+ protected override void UpdateBackgroundColor()
+ {
+ // Button is a special case; we don't want to set the Control's background
+ // because it goes outside the bounds of the Border/ContentPresenter,
+ // which is where we might change the BorderRadius to create a rounded shape.
+ return;
+ }
+
+ protected override bool PreventGestureBubbling { get; set; } = true;
+
+ bool IImageVisualElementRenderer.IsDisposed => _disposed;
+
+ void OnButtonClick(object sender, RoutedEventArgs e)
+ {
+ ((IButtonController)Element)?.SendReleased();
+ ((IButtonController)Element)?.SendClicked();
+ }
+
+ void OnPointerPressed(object sender, RoutedEventArgs e)
+ {
+ ((IButtonController)Element)?.SendPressed();
+ }
+
+ void UpdateBackground()
+ {
+ Control.BackgroundColor = Element.BackgroundColor != Color.Default ? Element.BackgroundColor.ToBrush() : (Brush)Windows.UI.Xaml.Application.Current.Resources["ButtonBackgroundThemeBrush"];
+ }
+
+ void UpdateBorderColor()
+ {
+ Control.BorderBrush = Element.BorderColor != Color.Default ? Element.BorderColor.ToBrush() : (Brush)Windows.UI.Xaml.Application.Current.Resources["ButtonBorderThemeBrush"];
+ }
+
+ void UpdateBorderRadius()
+ {
+ Control.BorderRadius = Element.CornerRadius;
+ }
+
+ void UpdateBorderWidth()
+ {
+ Control.BorderThickness = Element.BorderWidth == (double)ImageButton.BorderWidthProperty.DefaultValue ? new WThickness(3) : new WThickness(Element.BorderWidth);
+ }
+
+ void IImageVisualElementRenderer.SetImage(Windows.UI.Xaml.Media.ImageSource image)
+ {
+ _image.Source = image;
+ }
+
+ WImage IImageVisualElementRenderer.GetImage() =>
+ Control?.Content as WImage;
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Media;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Platform.UWP;
+
+namespace Xamarin.Forms.Platform.UWP
+{
+ public static class ImageElementManager
+ {
+ public static void Init(IImageVisualElementRenderer renderer)
+ {
+ renderer.ElementPropertyChanged += OnElementPropertyChanged;
+ renderer.ElementChanged += OnElementChanged;
+ renderer.ControlChanged += OnControlChanged;
+ }
+
+ internal static void Dispose(IImageVisualElementRenderer renderer)
+ {
+ renderer.ElementPropertyChanged -= OnElementPropertyChanged;
+ renderer.ElementChanged -= OnElementChanged;
+ renderer.ControlChanged -= OnControlChanged;
+ }
+
+ static void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ IImageVisualElementRenderer renderer = sender as IImageVisualElementRenderer;
+ var controller = renderer.Element as IImageController;
+
+ if (e.PropertyName == controller.AspectProperty?.PropertyName)
+ UpdateAspect(renderer, controller);
+ }
+
+ static void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+ {
+ if (e.NewElement != null)
+ {
+ IImageVisualElementRenderer renderer = sender as IImageVisualElementRenderer;
+ var controller = renderer.Element as IImageController;
+
+ UpdateAspect(renderer, controller);
+ }
+ }
+
+
+
+ static void OnControlChanged(object sender, EventArgs e)
+ {
+ IImageVisualElementRenderer renderer = sender as IImageVisualElementRenderer;
+
+ var controller = renderer.Element as IImageController;
+
+ UpdateAspect(renderer, controller);
+ }
+
+ public static void UpdateAspect(IImageVisualElementRenderer renderer, IImageController controller)
+ {
+ var Element = renderer.Element;
+ var Control = renderer.GetNativeElement();
+ var image = renderer.GetImage();
+
+ if (renderer.IsDisposed || Element == null || Control == null)
+ {
+ return;
+ }
+
+ image.Stretch = GetStretch(controller.Aspect);
+ if (controller.Aspect == Aspect.AspectFill || controller.Aspect == Aspect.AspectFit)
+
+ {
+ image.HorizontalAlignment = HorizontalAlignment.Center;
+ image.VerticalAlignment = VerticalAlignment.Center;
+ }
+ else
+ {
+ image.HorizontalAlignment = HorizontalAlignment.Left;
+ image.VerticalAlignment = VerticalAlignment.Top;
+ }
+ }
+
+ static Stretch GetStretch(Aspect aspect)
+ {
+ switch (aspect)
+ {
+ case Aspect.Fill:
+ return Stretch.Fill;
+ case Aspect.AspectFill:
+ return Stretch.UniformToFill;
+ default:
+ case Aspect.AspectFit:
+ return Stretch.Uniform;
+ }
+ }
+
+ public static async Task UpdateSource(IImageVisualElementRenderer renderer)
+ {
+ var Element = renderer.Element;
+ var Control = renderer.GetNativeElement();
+ var controller = Element as IImageController;
+
+ if (renderer.IsDisposed || Element == null || Control == null)
+ {
+ return;
+ }
+
+ controller.SetIsLoading(true);
+
+ ImageSource source = controller.Source;
+ IImageSourceHandler handler;
+ if (source != null && (handler = Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
+ {
+ Windows.UI.Xaml.Media.ImageSource imagesource;
+
+ try
+ {
+ imagesource = await handler.LoadImageAsync(source);
+ }
+ catch (OperationCanceledException)
+ {
+ imagesource = null;
+ }
+
+ // In the time it takes to await the imagesource, some zippy little app
+ // might have disposed of this Image already.
+ if (Control != null)
+ {
+ renderer.SetImage(imagesource);
+ }
+
+ RefreshImage(controller as IViewController);
+ }
+ else
+ {
+ renderer.SetImage(null);
+ controller.SetIsLoading(false);
+ }
+ }
+
+
+ static internal void RefreshImage(IViewController element)
+ {
+ element?.InvalidateMeasure(InvalidationTrigger.RendererReady);
+ }
+
+
+ }
+}
using System.ComponentModel;
using System.Threading.Tasks;
using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.UWP
{
- public class ImageRenderer : ViewRenderer<Image, Windows.UI.Xaml.Controls.Image>
+ public class ImageRenderer : ViewRenderer<Image, Windows.UI.Xaml.Controls.Image>, IImageVisualElementRenderer
{
bool _measured;
bool _disposed;
+ public ImageRenderer() : base()
+ {
+ ImageElementManager.Init(this);
+ }
+
+ bool IImageVisualElementRenderer.IsDisposed => _disposed;
+
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
if (Control.Source == null)
if (disposing)
{
+ ImageElementManager.Dispose(this);
if (Control != null)
{
Control.ImageOpened -= OnImageOpened;
SetNativeControl(image);
}
- await TryUpdateSource();
- UpdateAspect();
+ await TryUpdateSource().ConfigureAwait(false);
}
}
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Image.SourceProperty.PropertyName)
- await TryUpdateSource();
- else if (e.PropertyName == Image.AspectProperty.PropertyName)
- UpdateAspect();
+ await TryUpdateSource().ConfigureAwait(false);
}
- static Stretch GetStretch(Aspect aspect)
- {
- switch (aspect)
- {
- case Aspect.Fill:
- return Stretch.Fill;
- case Aspect.AspectFill:
- return Stretch.UniformToFill;
- default:
- case Aspect.AspectFit:
- return Stretch.Uniform;
- }
- }
void OnImageOpened(object sender, RoutedEventArgs routedEventArgs)
{
if (_measured)
{
- RefreshImage();
+ ImageElementManager.RefreshImage(Element);
}
Element?.SetIsLoading(false);
protected virtual void OnImageFailed(object sender, ExceptionRoutedEventArgs exceptionRoutedEventArgs)
{
- Log.Warning("Image Loading", $"Image failed to load: {exceptionRoutedEventArgs.ErrorMessage}" );
+ Log.Warning("Image Loading", $"Image failed to load: {exceptionRoutedEventArgs.ErrorMessage}");
Element?.SetIsLoading(false);
}
- void RefreshImage()
- {
- ((IVisualElementController)Element)?.InvalidateMeasure(InvalidationTrigger.RendererReady);
- }
-
- void UpdateAspect()
- {
- if (_disposed || Element == null || Control == null)
- {
- return;
- }
-
- Control.Stretch = GetStretch(Element.Aspect);
- if (Element.Aspect == Aspect.AspectFill || Element.Aspect == Aspect.AspectFit) // Then Center Crop
- {
- Control.HorizontalAlignment = HorizontalAlignment.Center;
- Control.VerticalAlignment = VerticalAlignment.Center;
- }
- else // Default
- {
- Control.HorizontalAlignment = HorizontalAlignment.Left;
- Control.VerticalAlignment = VerticalAlignment.Top;
- }
- }
protected virtual async Task TryUpdateSource()
{
protected async Task UpdateSource()
{
- if (_disposed || Element == null || Control == null)
- {
- return;
- }
-
- Element.SetIsLoading(true);
-
- ImageSource source = Element.Source;
- IImageSourceHandler handler;
- if (source != null && (handler = Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
- {
- Windows.UI.Xaml.Media.ImageSource imagesource;
-
- try
- {
- imagesource = await handler.LoadImageAsync(source);
- }
- catch (OperationCanceledException)
- {
- imagesource = null;
- }
-
- // In the time it takes to await the imagesource, some zippy little app
- // might have disposed of this Image already.
- if (Control != null)
- {
- Control.Source = imagesource;
- }
+ await ImageElementManager.UpdateSource(this).ConfigureAwait(false); }
- RefreshImage();
- }
- else
- {
- Control.Source = null;
- Element.SetIsLoading(false);
- }
+ void IImageVisualElementRenderer.SetImage(Windows.UI.Xaml.Media.ImageSource image)
+ {
+ Control.Source = image;
}
+
+ Windows.UI.Xaml.Controls.Image IImageVisualElementRenderer.GetImage() => Control;
}
}
[assembly: ExportRenderer(typeof(Layout), typeof(LayoutRenderer))]
[assembly: ExportRenderer(typeof(BoxView), typeof(BoxViewRenderer))]
[assembly: ExportRenderer(typeof(Image), typeof(ImageRenderer))]
+[assembly: ExportRenderer(typeof(ImageButton), typeof(ImageButtonRenderer))]
[assembly: ExportRenderer(typeof(Label), typeof(LabelRenderer))]
[assembly: ExportRenderer(typeof(Button), typeof(ButtonRenderer))]
[assembly: ExportRenderer(typeof(ListView), typeof(ListViewRenderer))]
namespace Xamarin.Forms.Platform.UWP
{
- public class VisualElementRenderer<TElement, TNativeElement> : Panel, IVisualElementRenderer, IDisposable, IEffectControlProvider where TElement : VisualElement
+ public class VisualElementRenderer<TElement, TNativeElement> : Panel, IVisualNativeElementRenderer, IDisposable, IEffectControlProvider where TElement : VisualElement
where TNativeElement : FrameworkElement
{
string _defaultAutomationPropertiesName;
bool _disposed;
FocusNavigationDirection focusDirection;
EventHandler<VisualElementChangedEventArgs> _elementChangedHandlers;
+ event EventHandler<PropertyChangedEventArgs> _elementPropertyChanged;
+ event EventHandler _controlChanging;
+ event EventHandler _controlChanged;
VisualElementTracker<TElement, TNativeElement> _tracker;
Windows.UI.Xaml.Controls.Page _containingPage; // Cache of containing page used for unfocusing
Control _control => Control as Control;
if (AutoTrack && Tracker == null)
{
- Tracker = new VisualElementTracker<TElement, TNativeElement>();
+ Tracker = new VisualElementTracker<TElement, TNativeElement>();
}
// Disabled until reason for crashes with unhandled exceptions is discovered
}
public event EventHandler<ElementChangedEventArgs<TElement>> ElementChanged;
+ event EventHandler<PropertyChangedEventArgs> IVisualNativeElementRenderer.ElementPropertyChanged
+ {
+ add => _elementPropertyChanged += value;
+ remove => _elementPropertyChanged -= value;
+ }
+
+ event EventHandler IVisualNativeElementRenderer.ControlChanging
+ {
+ add { _controlChanging += value; }
+ remove { _controlChanging -= value; }
+ }
+ event EventHandler IVisualNativeElementRenderer.ControlChanged
+ {
+ add { _controlChanged += value; }
+ remove { _controlChanged -= value; }
+ }
protected override Windows.Foundation.Size ArrangeOverride(Windows.Foundation.Size finalSize)
{
SetAutomationPropertiesAccessibilityView();
else if (e.PropertyName == AutomationProperties.LabeledByProperty.PropertyName)
SetAutomationPropertiesLabeledBy();
- else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName ||
+ else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName ||
e.PropertyName == Layout.CascadeInputTransparentProperty.PropertyName)
UpdateInputTransparent();
if (e.PropertyName == Specifics.AccessKeyProperty.PropertyName ||
UpdateTabStop();
else if (e.PropertyName == VisualElement.TabIndexProperty.PropertyName)
UpdateTabIndex();
+
+ _elementPropertyChanged?.Invoke(this, e);
}
protected virtual void OnRegisterEffect(PlatformEffect effect)
protected void SetNativeControl(TNativeElement control)
{
+ _controlChanging?.Invoke(this, EventArgs.Empty);
TNativeElement oldControl = Control;
Control = control;
UpdateTracker();
if (control == null)
+ {
+ _controlChanged?.Invoke(this, EventArgs.Empty);
return;
+ }
Control.HorizontalAlignment = HorizontalAlignment.Stretch;
Control.VerticalAlignment = VerticalAlignment.Stretch;
if (Element != null && !string.IsNullOrEmpty(Element.AutomationId))
SetAutomationId(Element.AutomationId);
+
+ _controlChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void UpdateBackgroundColor()
</NoWarn>
</PropertyGroup>
<ItemGroup>
+ <Compile Include="IImageVisualElementRenderer.cs" />
+ <Compile Include="ImageButtonRenderer.cs" />
<Compile Include="ColorExtensions.cs" />
<Compile Include="FormsCancelButton.cs" />
<Compile Include="AlertDialog.cs" />
<Compile Include="FormsTextBox.cs" />
<Compile Include="FormsComboBox.cs" />
<Compile Include="ITabStopOnDescendants.cs" />
+ <Compile Include="ImageElementManager.cs" />
<Compile Include="InterceptVisualStateManager.cs" />
<Compile Include="HorizontalTextAlignmentConverter.cs" />
<Compile Include="ITitleIconProvider.cs" />
<Compile Include="ITitleViewProvider.cs" />
<Compile Include="ITitleViewRendererController.cs" />
<Compile Include="IToolbarProvider.cs" />
+ <Compile Include="IVisualNativeElementRenderer.cs" />
<Compile Include="NativeBindingExtensions.cs" />
<Compile Include="NativeEventWrapper.cs" />
<Compile Include="NativePropertyListener.cs" />
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+
+#if __MOBILE__
+using NativeColor = UIKit.UIColor;
+using NativeControl = UIKit.UIControl;
+using NativeView = UIKit.UIView;
+
+namespace Xamarin.Forms.Platform.iOS
+#else
+using NativeView = AppKit.NSView;
+using NativeColor = CoreGraphics.CGColor;
+using NativeControl = AppKit.NSControl;
+namespace Xamarin.Forms.Platform.MacOS
+#endif
+{
+ public interface IVisualNativeElementRenderer : IVisualElementRenderer
+ {
+ event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
+ event EventHandler ControlChanging;
+ event EventHandler ControlChanged;
+
+ NativeView Control { get; }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.ComponentModel;
+using NativeView = UIKit.UIView;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal static class BorderElementManager
+ {
+ static nfloat _defaultCornerRadius = 5;
+
+ public static void Init(IVisualNativeElementRenderer renderer)
+ {
+ renderer.ElementPropertyChanged += OnElementPropertyChanged;
+ renderer.ElementChanged += OnElementChanged;
+ renderer.ControlChanged += OnControlChanged;
+ }
+
+ public static void Dispose(IVisualNativeElementRenderer renderer)
+ {
+ renderer.ElementPropertyChanged -= OnElementPropertyChanged;
+ renderer.ElementChanged -= OnElementChanged;
+ renderer.ControlChanged -= OnControlChanged;
+ }
+
+ static void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ IVisualNativeElementRenderer renderer = (IVisualNativeElementRenderer)sender;
+ IBorderController backgroundView = (IBorderController)renderer.Element;
+
+ if (e.PropertyName == backgroundView.BorderWidthProperty.PropertyName || e.PropertyName == backgroundView.CornerRadiusProperty.PropertyName || e.PropertyName == backgroundView.BorderColorProperty.PropertyName)
+ UpdateBorder(renderer, backgroundView);
+ }
+
+ static void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+ {
+ if (e.NewElement != null)
+ {
+ UpdateBorder((IVisualNativeElementRenderer)sender, (IBorderController)e.NewElement);
+ }
+ }
+
+ public static void UpdateBorder(IVisualNativeElementRenderer renderer, IBorderController backgroundView)
+ {
+ var control = renderer.Control;
+ var ImageButton = backgroundView;
+
+ if (control == null)
+ {
+ return;
+ }
+
+ if (ImageButton.BorderColor != Color.Default)
+ control.Layer.BorderColor = ImageButton.BorderColor.ToCGColor();
+
+ control.Layer.BorderWidth = Math.Max(0f, (float)ImageButton.BorderWidth);
+
+ nfloat cornerRadius = _defaultCornerRadius;
+
+ if (ImageButton.IsSet(ImageButton.CornerRadiusProperty) && ImageButton.CornerRadius != (int)ImageButton.CornerRadiusProperty.DefaultValue)
+ cornerRadius = ImageButton.CornerRadius;
+
+ control.Layer.CornerRadius = cornerRadius;
+ }
+
+ static void OnControlChanged(object sender, EventArgs e)
+ {
+ IVisualNativeElementRenderer renderer = (IVisualNativeElementRenderer)sender;
+ IBorderController backgroundView = (IBorderController)renderer.Element;
+ UpdateBorder(renderer, backgroundView);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+
+using Foundation;
+using UIKit;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ internal static class ButtonElementManager
+ {
+ static readonly UIControlState[] s_controlStates = { UIControlState.Normal, UIControlState.Highlighted, UIControlState.Disabled };
+
+ public static void Init(IVisualNativeElementRenderer renderer)
+ {
+ renderer.ElementPropertyChanged += OnElementPropertyChanged;
+ renderer.ControlChanged += OnControlChanged;
+ }
+
+ static void OnControlChanged(object sender, EventArgs e)
+ {
+ var renderer = (IVisualNativeElementRenderer)sender;
+ var control = (UIButton)renderer.Control;
+
+ foreach (UIControlState uiControlState in s_controlStates)
+ {
+ control.SetTitleColor(UIButton.Appearance.TitleColor(uiControlState), uiControlState); // if new values are null, old values are preserved.
+ control.SetTitleShadowColor(UIButton.Appearance.TitleShadowColor(uiControlState), uiControlState);
+ control.SetBackgroundImage(UIButton.Appearance.BackgroundImageForState(uiControlState), uiControlState);
+ }
+
+
+ control.TouchUpInside -= TouchUpInside;
+ control.TouchDown -= TouchDown;
+ control.TouchUpInside += TouchUpInside;
+ control.TouchDown += TouchDown;
+ }
+
+ static void TouchUpInside(object sender, EventArgs eventArgs)
+ {
+ var button = sender as UIButton;
+ var renderer = button.Superview as IVisualNativeElementRenderer;
+ OnButtonTouchUpInside(renderer.Element as IButtonController);
+ }
+
+ static void TouchDown(object sender, EventArgs eventArgs)
+ {
+ var button = sender as UIButton;
+ var renderer = button.Superview as IVisualNativeElementRenderer;
+ OnButtonTouchDown(renderer.Element as IButtonController);
+ }
+
+ public static void Dispose(IVisualNativeElementRenderer renderer)
+ {
+ var control = (UIButton)renderer.Control;
+ renderer.ElementPropertyChanged -= OnElementPropertyChanged;
+ control.TouchUpInside -= TouchUpInside;
+ control.TouchDown -= TouchDown;
+ }
+
+ static void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ }
+
+
+ static void SetControlPropertiesFromProxy(UIButton control)
+ {
+ foreach (UIControlState uiControlState in s_controlStates)
+ {
+ control.SetTitleColor(UIButton.Appearance.TitleColor(uiControlState), uiControlState); // if new values are null, old values are preserved.
+ control.SetTitleShadowColor(UIButton.Appearance.TitleShadowColor(uiControlState), uiControlState);
+ control.SetBackgroundImage(UIButton.Appearance.BackgroundImageForState(uiControlState), uiControlState);
+ }
+ }
+
+ internal static void OnButtonTouchDown(IButtonController element)
+ {
+ element?.SendPressed();
+ }
+
+ internal static void OnButtonTouchUpInside(IButtonController element)
+ {
+ element?.SendReleased();
+ element?.SendClicked();
+ }
+ }
+}
\ No newline at end of file
namespace Xamarin.Forms.Platform.iOS
{
- public class ButtonRenderer : ViewRenderer<Button, UIButton>
+ public class ButtonRenderer : ViewRenderer<Button, UIButton>, IImageVisualElementRenderer
{
+ bool _isDisposed;
UIColor _buttonTextColorDefaultDisabled;
UIColor _buttonTextColorDefaultHighlighted;
UIColor _buttonTextColorDefaultNormal;
// ReSharper disable once BuiltInTypeReferenceStyle
// Under iOS Classic Resharper wants to suggest this use the built-in type ref
// but under iOS that suggestion won't work
- readonly nfloat _minimumButtonHeight = 44; // Apple docs
- readonly nfloat _defaultCornerRadius = 5;
+ readonly nfloat _minimumButtonHeight = 44; // Apple docs
static readonly UIControlState[] s_controlStates = { UIControlState.Normal, UIControlState.Highlighted, UIControlState.Disabled };
+ public bool IsDisposed => _isDisposed;
+
+ protected ButtonRenderer() : base()
+ {
+ BorderElementManager.Init(this);
+ ImageElementManager.Init(this);
+ }
+
public override SizeF SizeThatFits(SizeF size)
{
var result = base.SizeThatFits(size);
if (result.Height < _minimumButtonHeight)
{
- result.Height = _minimumButtonHeight;
+ result.Height = _minimumButtonHeight;
}
return result;
protected override void Dispose(bool disposing)
{
+ if (_isDisposed)
+ return;
if (Control != null)
{
Control.TouchUpInside -= OnButtonTouchUpInside;
Control.TouchDown -= OnButtonTouchDown;
+ BorderElementManager.Dispose(this);
+ ImageElementManager.Dispose(this);
}
+ _isDisposed = true;
+
base.Dispose(disposing);
}
UpdateText();
UpdateFont();
- UpdateBorder();
UpdateImage();
UpdateTextColor();
UpdatePadding();
UpdateTextColor();
else if (e.PropertyName == Button.FontProperty.PropertyName)
UpdateFont();
- else if (e.PropertyName == Button.BorderWidthProperty.PropertyName || e.PropertyName == Button.CornerRadiusProperty.PropertyName || e.PropertyName == Button.BorderColorProperty.PropertyName)
- UpdateBorder();
else if (e.PropertyName == Button.ImageProperty.PropertyName)
UpdateImage();
else if (e.PropertyName == Button.PaddingProperty.PropertyName)
UpdatePadding();
}
-
+
protected override void SetAccessibilityLabel()
{
// If we have not specified an AccessibilityLabel and the AccessibiltyLabel is current bound to the Title,
base.SetAccessibilityLabel();
}
-
+
void SetControlPropertiesFromProxy()
{
foreach (UIControlState uiControlState in s_controlStates)
void OnButtonTouchUpInside(object sender, EventArgs eventArgs)
{
- ((IButtonController)Element)?.SendReleased();
- ((IButtonController)Element)?.SendClicked();
+ ButtonElementManager.OnButtonTouchUpInside(this.Element);
}
void OnButtonTouchDown(object sender, EventArgs eventArgs)
{
- ((IButtonController)Element)?.SendPressed();
- }
-
- void UpdateBorder()
- {
- var uiButton = Control;
- var button = Element;
-
- if (button.BorderColor != Color.Default)
- uiButton.Layer.BorderColor = button.BorderColor.ToCGColor();
-
- uiButton.Layer.BorderWidth = Math.Max(0f, (float)button.BorderWidth);
-
- nfloat cornerRadius = _defaultCornerRadius;
-
- if (button.IsSet(Button.CornerRadiusProperty) && button.CornerRadius != (int)Button.CornerRadiusProperty.DefaultValue)
- cornerRadius = button.CornerRadius;
-
- uiButton.Layer.CornerRadius = cornerRadius;
+ ButtonElementManager.OnButtonTouchDown(this.Element);
}
void UpdateFont()
async void UpdateImage()
{
- IImageSourceHandler handler;
- FileImageSource source = Element.Image;
- if (source != null && (handler = Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
+ try
{
- UIImage uiimage;
- try
- {
- uiimage = await handler.LoadImageAsync(source, scale: (float)UIScreen.MainScreen.Scale);
- }
- catch (OperationCanceledException)
- {
- uiimage = null;
- }
- UIButton button = Control;
- if (button != null && uiimage != null)
- {
- button.SetImage(uiimage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal), UIControlState.Normal);
-
- button.ImageView.ContentMode = UIViewContentMode.ScaleAspectFit;
+ await ImageElementManager.SetImage(this, Element);
+ }
+ catch (Exception ex)
+ {
+ Internals.Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
+ }
+ }
- ComputeEdgeInsets(Control, Element.ContentLayout);
- }
+ public void SetImage(UIImage image)
+ {
+ if (image != null)
+ {
+ UIButton button = Control;
+ button.SetImage(image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal), UIControlState.Normal);
+ button.ImageView.ContentMode = UIViewContentMode.ScaleAspectFit;
+ ComputeEdgeInsets(Control, Element.ContentLayout);
}
else
{
Control.SetImage(null, UIControlState.Normal);
ClearEdgeInsets(Control);
}
- ((IVisualElementController)Element).NativeSizeChanged();
}
+ public UIImageView GetImage() => Control?.ImageView;
+
void UpdateText()
{
var newText = Element.Text;
--- /dev/null
+using UIKit;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public interface IImageVisualElementRenderer : IVisualNativeElementRenderer
+ {
+ void SetImage(UIImage image);
+ bool IsDisposed { get; }
+ UIImageView GetImage();
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using Foundation;
+using UIKit;
+using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
+using SizeF = CoreGraphics.CGSize;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public class ImageButtonRenderer : ViewRenderer<ImageButton, UIButton>, IImageVisualElementRenderer
+ {
+ bool _isDisposed;
+
+ // This looks like it should be a const under iOS Classic,
+ // but that doesn't work under iOS
+ // ReSharper disable once BuiltInTypeReferenceStyle
+ // Under iOS Classic Resharper wants to suggest this use the built-in type ref
+ // but under iOS that suggestion won't work
+ readonly nfloat _minimumButtonHeight = 44; // Apple docs
+
+
+ public ImageButtonRenderer() : base()
+ {
+ ButtonElementManager.Init(this);
+ BorderElementManager.Init(this);
+ ImageElementManager.Init(this);
+ }
+
+ public override SizeF SizeThatFits(SizeF size)
+ {
+ var result = base.SizeThatFits(size);
+
+ if (result.Height < _minimumButtonHeight)
+ {
+ result.Height = _minimumButtonHeight;
+ }
+
+ return result;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_isDisposed)
+ return;
+
+ if (disposing && Control != null)
+ {
+ ButtonElementManager.Dispose(this);
+ BorderElementManager.Dispose(this);
+ ImageElementManager.Dispose(this);
+ }
+
+ _isDisposed = true;
+ base.Dispose(disposing);
+ }
+
+ protected async override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+
+ if (e.PropertyName == ImageButton.SourceProperty.PropertyName)
+ await ImageElementManager.SetImage(this, Element).ConfigureAwait(false);
+ else if (e.PropertyName == ImageButton.PaddingProperty.PropertyName)
+ UpdatePadding();
+ }
+
+ protected async override void OnElementChanged(ElementChangedEventArgs<ImageButton> e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.NewElement != null)
+ {
+ if (Control == null)
+ {
+ SetNativeControl(CreateNativeControl());
+
+ Debug.Assert(Control != null, "Control != null");
+ }
+
+ UpdatePadding();
+ await UpdateImage().ConfigureAwait(false);
+ }
+ }
+
+ void UpdatePadding(UIButton button = null)
+ {
+ var uiElement = button ?? Control;
+ if (uiElement == null)
+ return;
+
+ uiElement.ContentEdgeInsets = new UIEdgeInsets(
+ (float)(Element.Padding.Top),
+ (float)(Element.Padding.Left),
+ (float)(Element.Padding.Bottom),
+ (float)(Element.Padding.Right)
+ );
+ }
+ async Task UpdateImage()
+ {
+ try
+ {
+ await ImageElementManager.SetImage(this, Element).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Internals.Log.Warning(nameof(ImageRenderer), "Error loading image: {0}", ex);
+ }
+ }
+
+ protected override UIButton CreateNativeControl()
+ {
+ return new UIButton(UIButtonType.System);
+ }
+
+ protected override void SetAccessibilityLabel()
+ {
+ // If we have not specified an AccessibilityLabel and the AccessibiltyLabel is current bound to the Title,
+ // exit this method so we don't set the AccessibilityLabel value and break the binding.
+ // This may pose a problem for users who want to explicitly set the AccessibilityLabel to null, but this
+ // will prevent us from inadvertently breaking UI Tests that are using Query.Marked to get the dynamic Title
+ // of the ImageButton.
+
+ var elemValue = (string)Element?.GetValue(AutomationProperties.NameProperty);
+ if (string.IsNullOrWhiteSpace(elemValue) && Control?.AccessibilityLabel == Control?.Title(UIControlState.Normal))
+ return;
+
+ base.SetAccessibilityLabel();
+ }
+
+ bool IImageVisualElementRenderer.IsDisposed => _isDisposed;
+ void IImageVisualElementRenderer.SetImage(UIImage image)
+ {
+ Control.SetImage(image?.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal), UIControlState.Normal);
+ Control.HorizontalAlignment = UIControlContentHorizontalAlignment.Fill;
+ Control.VerticalAlignment = UIControlContentVerticalAlignment.Fill;
+ }
+
+ UIImageView IImageVisualElementRenderer.GetImage()
+ {
+ return Control?.ImageView;
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Foundation;
+using UIKit;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+ public static class ImageElementManager
+ {
+ public static void Init(IImageVisualElementRenderer renderer)
+ {
+ renderer.ElementPropertyChanged += OnElementPropertyChanged;
+ renderer.ElementChanged += OnElementChanged;
+ renderer.ControlChanged += OnControlChanged;
+ }
+
+ public static void Dispose(IImageVisualElementRenderer renderer)
+ {
+ renderer.ElementPropertyChanged -= OnElementPropertyChanged;
+ renderer.ElementChanged -= OnElementChanged;
+ renderer.ControlChanged -= OnControlChanged;
+ }
+
+
+ static void OnControlChanged(object sender, EventArgs e)
+ {
+ var renderer = sender as IImageVisualElementRenderer;
+ var imageElement = renderer.Element as IImageController;
+ SetAspect(renderer, imageElement);
+ SetOpacity(renderer, imageElement);
+ }
+
+ static void OnElementChanged(object sender, VisualElementChangedEventArgs e)
+ {
+ if (e.NewElement != null)
+ {
+ var renderer = sender as IImageVisualElementRenderer;
+ var imageElement = renderer.Element as IImageController;
+
+ SetAspect(renderer, imageElement);
+ SetOpacity(renderer, imageElement);
+ }
+ }
+
+ static void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ var renderer = sender as IImageVisualElementRenderer;
+ var imageElement = renderer.Element as IImageController;
+
+ if (e.PropertyName == imageElement.IsOpaqueProperty?.PropertyName)
+ SetOpacity(renderer, renderer.Element as IImageController);
+ else if (e.PropertyName == imageElement.AspectProperty?.PropertyName)
+ SetAspect(renderer, renderer.Element as IImageController);
+ }
+
+
+
+ public static void SetAspect(IImageVisualElementRenderer renderer, IImageController imageElement)
+ {
+ var Element = renderer.Element;
+ var Control = renderer.GetImage();
+
+
+ if (renderer.IsDisposed || Element == null || Control == null)
+ {
+ return;
+ }
+
+ Control.ContentMode = imageElement.Aspect.ToUIViewContentMode();
+ }
+
+ public static void SetOpacity(IImageVisualElementRenderer renderer, IImageController imageElement)
+ {
+ var Element = renderer.Element;
+ var Control = renderer.GetImage();
+
+ if (renderer.IsDisposed || Element == null || Control == null)
+ {
+ return;
+ }
+
+ Control.Opaque = imageElement.IsOpaque;
+ }
+
+ public static async Task SetImage(IImageVisualElementRenderer renderer, IImageController imageElement, Image oldElement = null)
+ {
+ _ = renderer ?? throw new ArgumentNullException($"{nameof(ImageElementManager)}.{nameof(SetImage)} {nameof(renderer)} cannot be null");
+ _ = imageElement ?? throw new ArgumentNullException($"{nameof(ImageElementManager)}.{nameof(SetImage)} {nameof(imageElement)} cannot be null");
+
+ var Element = renderer.Element;
+ var Control = renderer.GetImage();
+
+ if (renderer.IsDisposed || Element == null || Control == null)
+ {
+ return;
+ }
+
+ var source = imageElement.Source;
+
+ if (oldElement != null)
+ {
+ var oldSource = oldElement.Source;
+ if (Equals(oldSource, source))
+ return;
+
+ if (oldSource is FileImageSource && source is FileImageSource && ((FileImageSource)oldSource).File == ((FileImageSource)source).File)
+ return;
+
+ renderer.SetImage(null);
+ }
+
+ IImageSourceHandler handler;
+ imageElement.SetIsLoading(true);
+ try
+ {
+ if (source != null &&
+ (handler = Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
+ {
+ UIImage uiimage;
+ try
+ {
+ uiimage = await handler.LoadImageAsync(source, scale: (float)UIScreen.MainScreen.Scale);
+ }
+ catch (OperationCanceledException)
+ {
+ uiimage = null;
+ }
+
+ if (renderer.IsDisposed)
+ return;
+
+ var imageView = Control;
+ if (imageView != null)
+ {
+ renderer.SetImage(uiimage);
+ }
+ }
+ else
+ {
+ renderer.SetImage(null);
+ }
+
+ }
+ finally
+ {
+ imageElement.SetIsLoading(false);
+ }
+
+ (imageElement as IViewController)?.NativeSizeChanged();
+ }
+ }
+}
\ No newline at end of file
}
}
- public class ImageRenderer : ViewRenderer<Image, UIImageView>
+ public class ImageRenderer : ViewRenderer<Image, UIImageView>, IImageVisualElementRenderer
{
bool _isDisposed;
+ public ImageRenderer() : base()
+ {
+ ImageElementManager.Init(this);
+ }
+
protected override void Dispose(bool disposing)
{
if (_isDisposed)
UIImage oldUIImage;
if (Control != null && (oldUIImage = Control.Image) != null)
{
+ ImageElementManager.Dispose(this);
oldUIImage.Dispose();
}
}
if (e.NewElement != null)
{
- SetAspect();
- await TrySetImage(e.OldElement);
- SetOpacity();
+ await TrySetImage(e.OldElement as Image);
}
base.OnElementChanged(e);
protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
- if (e.PropertyName == Image.SourceProperty.PropertyName)
- await TrySetImage();
- else if (e.PropertyName == Image.IsOpaqueProperty.PropertyName)
- SetOpacity();
- else if (e.PropertyName == Image.AspectProperty.PropertyName)
- SetAspect();
- }
- void SetAspect()
- {
- if (_isDisposed || Element == null || Control == null)
- {
- return;
- }
-
- Control.ContentMode = Element.Aspect.ToUIViewContentMode();
+ if (e.PropertyName == Image.SourceProperty.PropertyName)
+ await TrySetImage().ConfigureAwait(false);
}
protected virtual async Task TrySetImage(Image previous = null)
protected async Task SetImage(Image oldElement = null)
{
- if (_isDisposed || Element == null || Control == null)
- {
- return;
- }
-
- var source = Element.Source;
-
- if (oldElement != null)
- {
- var oldSource = oldElement.Source;
- if (Equals(oldSource, source))
- return;
-
- if (oldSource is FileImageSource && source is FileImageSource && ((FileImageSource)oldSource).File == ((FileImageSource)source).File)
- return;
-
- Control.Image = null;
- }
-
- IImageSourceHandler handler;
-
- Element.SetIsLoading(true);
-
- if (source != null &&
- (handler = Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source)) != null)
- {
- UIImage uiimage;
- try
- {
- uiimage = await handler.LoadImageAsync(source, scale: (float)UIScreen.MainScreen.Scale);
- }
- catch (OperationCanceledException)
- {
- uiimage = null;
- }
-
- if (_isDisposed)
- return;
-
- var imageView = Control;
- if (imageView != null)
- imageView.Image = uiimage;
-
- ((IVisualElementController)Element).NativeSizeChanged();
- }
- else
- {
- Control.Image = null;
- }
-
- Element.SetIsLoading(false);
+ await ImageElementManager.SetImage(this, Element, oldElement).ConfigureAwait(false);
}
- void SetOpacity()
- {
- if (_isDisposed || Element == null || Control == null)
- {
- return;
- }
+ void IImageVisualElementRenderer.SetImage(UIImage image) => Control.Image = image;
- Control.Opaque = Element.IsOpaque;
- }
+ bool IImageVisualElementRenderer.IsDisposed => _isDisposed;
+
+ UIImageView IImageVisualElementRenderer.GetImage() => Control;
}
+
public interface IImageSourceHandler : IRegisterable
{
Task<UIImage> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = default(CancellationToken), float scale = 1);
{
}
- public abstract class ViewRenderer<TView, TNativeView> : VisualElementRenderer<TView>, ITabStop where TView : View where TNativeView : NativeView
+ public abstract class ViewRenderer<TView, TNativeView> : VisualElementRenderer<TView>, IVisualNativeElementRenderer, ITabStop where TView : View where TNativeView : NativeView
{
#if __MOBILE__
string _defaultAccessibilityLabel;
#endif
NativeColor _defaultColor;
+ event EventHandler<PropertyChangedEventArgs> _elementPropertyChanged;
+ event EventHandler _controlChanging;
+ event EventHandler _controlChanged;
+
+
+
protected virtual TNativeView CreateNativeControl()
{
return default(TNativeView);
}
public TNativeView Control { get; private set; }
+ NativeView IVisualNativeElementRenderer.Control => Control;
+
+
+ event EventHandler<PropertyChangedEventArgs> IVisualNativeElementRenderer.ElementPropertyChanged
+ {
+ add { _elementPropertyChanged += value; }
+ remove { _elementPropertyChanged -= value; }
+ }
+
+ event EventHandler IVisualNativeElementRenderer.ControlChanging
+ {
+ add { _controlChanging += value; }
+ remove { _controlChanging -= value; }
+ }
+ event EventHandler IVisualNativeElementRenderer.ControlChanged
+ {
+ add { _controlChanged += value; }
+ remove { _controlChanged -= value; }
+ }
+
NativeView ITabStop.TabStop => Control;
#if __MOBILE__
}
base.OnElementPropertyChanged(sender, e);
+ _elementPropertyChanged?.Invoke(this, e);
}
protected override void OnRegisterEffect(PlatformEffect effect)
protected void SetNativeControl(TNativeView uiview)
{
+ _controlChanging?.Invoke(this, EventArgs.Empty);
#if __MOBILE__
_defaultColor = uiview.BackgroundColor;
UpdateFlowDirection();
AddSubview(uiview);
+
+ _controlChanged?.Invoke(this, EventArgs.Empty);
}
#if __MOBILE__
remove { _elementChangedHandlers.Remove(value); }
}
+
+
public virtual SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
public NativeView NativeView => this;
+
+ protected internal virtual NativeView GetControl() => NativeView;
+
void IVisualElementRenderer.SetElement(VisualElement element)
{
SetElement((TElement)element);
else if (e.PropertyName == AutomationProperties.IsInAccessibleTreeProperty.PropertyName)
SetIsAccessibilityElement();
#endif
+
}
protected virtual void OnRegisterEffect(PlatformEffect effect)
<Compile Include="Extensions\LabelExtensions.cs" />
<Compile Include="Extensions\VisualElementExtensions.cs" />
<Compile Include="Flags.cs" />
+ <Compile Include="Renderers\ImageElementManager.cs" />
+ <Compile Include="IVisualNativeElementRenderer.cs" />
<Compile Include="NativeViewWrapper.cs" />
<Compile Include="NativeViewWrapperRenderer.cs" />
<Compile Include="PlatformEffect.cs" />
<Compile Include="Forms.cs" />
<Compile Include="PageExtensions.cs" />
<Compile Include="Renderers\WkWebViewRenderer.cs" />
+ <Compile Include="Renderers\BorderElementManager.cs" />
+ <Compile Include="Renderers\ButtonElementManager.cs" />
+ <Compile Include="Renderers\IImageVisualElementRenderer.cs" />
+ <Compile Include="Renderers\ImageButtonRenderer.cs" />
<Compile Include="Resources\StringResources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
{
}
+ internal class _ImageButtonRenderer
+ {
+ }
+
internal class _ButtonRenderer
{
}