UPDATED:
So, I'm unable to create IOS custom height ProgressBar.
I use the latest version of Xamarin.Forms.
.cs file:
public class SplashScreenProgressBar : ProgressBar
{
public static readonly BindableProperty TintColorProperty =
BindableProperty.Create<CustomProgressBar, Color>( p => p.TintColor, Color.Green);
public Color TintColor
{
get { return (Color) GetValue(TintColorProperty); }
set { SetValue(TintColorProperty, value); }
}
public static readonly BindableProperty HeightExtendedProperty =
BindableProperty.Create("HeightExtended", typeof(double), typeof(SplashScreenProgressBar), 10.0);
public double HeightExtended
{
get { return (double) GetValue(HeightExtendedProperty); }
set { SetValue(HeightExtendedProperty, value); }
}
public static readonly BindableProperty BackgroundColorExtendedProperty =
BindableProperty.Create("BackgroundColorExtended", typeof(Color), typeof(SplashScreenProgressBar),
Color.White);
public Color BackgroundColorExtended
{
get { return (Color) GetValue(BackgroundColorExtendedProperty); }
set { SetValue(BackgroundColorExtendedProperty, value); }
}
}
Here is iOS renderer:
public class SplashScreenProgressBarRenderer : ProgressBarRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<ProgressBar> e)
{
base.OnElementChanged(e);
var element = (SplashScreenProgressBar)Element;
this.Control.ProgressTintColor = element.TintColor.ToUIColor();
this.Control.TrackTintColor = element.BackgroundColorExtended.ToUIColor();
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
var element = (SplashScreenProgressBar)Element;
var X = 1.0f;
var Y = (System.nfloat)element.HeightExtended;
CGAffineTransform transform = CGAffineTransform.MakeScale(X, Y);
this.Control.Transform = transform;
this.Control.ClipsToBounds = true;
this.Control.Layer.MasksToBounds = true;
this.Control.CornerRadius = 5;
}
}
xaml file:
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" BackgroundColor="White" >
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Margin="50" BackgroundColor="White" >
<views:SplashScreenProgressBar x:Name="Progress"
TintColor="#5FA5F9"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center"
BackgroundColorExtended="#FFF" />
</StackLayout>
</StackLayout>
But this way doesn't work.
I googled and tried almost all samples which I've found, but nothing happened.
Screenshot:
As you see on the screenshot corner radius is applied to ProgressBar, but height(scale) isn't applied.
In PCL
StackLayout is overlapped with status bar.
Add Margin on it.
<StackLayout Margin="50" xxxxx
In Renderer
ClipsToBounds ,Layer.MasksToBounds ,Layer.CornerRadius should be set on the Control not the Renderer
this.Control.Transform = transform;
this.Control.ClipsToBounds = true;
this.Control.Layer.MasksToBounds = true;
this.Control.Layer.CornerRadius = 5;
When use the custom renderer in ios, it always occupy the whole area of parent element. So, u need to update the progress bar frame once again in layoutsubview.
bool is rendered;
public override void LayoutSubviews()
{
if(!rendered)
{
Frame = new CGRect(x,y,width,height);
setNeedsdisplay
}
rendered=true;
}
So, I spent a lot of hours to researching and investigation.
And seems there xamarin.forms iOS bug for progress bar rounded corners.
Related
I'm having a problem when I show an image on .NET MAUI, the size of the image is always bigger than it actually is (blue part in the image below).
Screenshot
My code is as follows:
<Grid>
<ScrollView>
<VerticalStackLayout>
<Image Source="https://cdn-5e5150f5f911c807c41ebdc8.closte.com/wp-content/uploads/IoT-development-kit-article-banner-scaled-900x400.jpg"
Aspect="AspectFit" BackgroundColor="Blue">
<Image.Margin>
<OnIdiom Phone="10" Tablet="20" Desktop="20"/>
</Image.Margin>
</Image>
</VerticalStackLayout>
</ScrollView>
</Grid>
Is there a way to keep the size of the image in proportion to the actual size?
I made some changes:
I tried using data binding in MVVM way.
I tried counting image ratio using platform code.
The following is my code,
For MainPage.xaml, the difference is that i use data binding for image Source and AspectRatio property which would be claimed in MainPageVeiwModel.
<Grid>
<ScrollView>
<VerticalStackLayout>
<a:AspectImage Source="{Binding ImageUrl}"
AspectRatio="{Binding AspectRatio}" Aspect="AspectFit" BackgroundColor="Blue">
<a:AspectImage.Margin>
<OnIdiom Phone="10" Tablet="20" Desktop="20"/>
</a:AspectImage.Margin>
</a:AspectImage>
</VerticalStackLayout>
</ScrollView>
</Grid>
For custom control AspectImage, the difference is that I changed AspectRatio to Bindable property as we use binding for this property. More info Bindable properties.
public class AspectImage : Image
{
public static readonly BindableProperty AspectRatioProperty = BindableProperty.Create("AspectRatio", typeof(double), typeof(AspectRatioContainer), null);
public double AspectRatio
{
get { return (double)GetValue(AspectRatioProperty); }
set { SetValue(AspectRatioProperty, value); }
}
public AspectImage()
{
SizeChanged += HandleSizeChanged;
}
private void HandleSizeChanged(object sender, EventArgs e)
{
if (this.Width > 0 && AspectRatio > 0)
{
var desiredHeightRequest = this.Width * AspectRatio;
if ((int)desiredHeightRequest != (int)HeightRequest)
{
this.HeightRequest = (int)desiredHeightRequest;
InvalidateMeasure();
}
}
}
}
For MainPageViewModel, we add AspectRatio and ImageUrl property for custom control and count AspectRatio.
public class MainPageViewModel
{
public string ImageUrl { get; set; }
public double AspectRatio { get; set; }
public MainPageViewModel()
{
ImageUrl = "https://cdn-5e5150f5f911c807c41ebdc8.closte.com/wp-content/uploads/IoT-development-kit-article-banner-scaled-900x400.jpg";
AspectRatio = CountAspectRatio(ImageUrl);
}
private double CountAspectRatio(string imageUrl)
{
var service = new GetImageSizeService();
Size imageSize = service.GetImageSize(imageUrl);
return imageSize.Height / imageSize.Width;
}
}
From above code in MainPageViewModel, we count AspectRatio by call platform code. If you are not familiar with it, i recommend this tutorial first: How To Write Platform-Specific Code in .NET MAUI.
To inject platform code in Maui (in Xamarin could use DependencyService):
First, in Project folder, create a new partial class, let's call it GetImageSizeService:
public partial class GetImageSizeService
{
public partial Size GetImageSize(string file);
}
Then creat another partial class in Platforms/iOS folder, called it GetImageSizeService also. Pay attention to the namespace should be the same as above file.
public partial class GetImageSizeService
{
public partial Size GetImageSize(string file)
{
NSData data = NSData.FromUrl(NSUrl.FromString(file));
UIImage image = UIImage.LoadFromData(data);
return new Size((double)image.Size.Width, (double)image.Size.Height);
}
}
Then in MainPageViewModel, we just call this service and count the AspectRatio.
=========================== First post=============
The link you add did inspire me. And if i understand your question correctly, you could try the following code which worked for me:
Create AspectImage custom control which set aspect ratio for width and height
public class AspectImage : Image
{
public double AspectRatio { get; set; }
public AspectImage()
{
SizeChanged += HandleSizeChanged;
}
private void HandleSizeChanged(object sender, EventArgs e)
{
if (this.Width > 0 && AspectRatio > 0)
{
var desiredHeightRequest = this.Width * AspectRatio;
if ((int)desiredHeightRequest != (int)HeightRequest)
{
this.HeightRequest = (int)desiredHeightRequest;
InvalidateMeasure();
}
}
}
}
For xaml, consume the AspectImage. Here the aspect ratio seems to be 4/9 Approximately equal to 0.44
<Grid>
<ScrollView>
<VerticalStackLayout>
<a:AspectImage Source="https://cdn-5e5150f5f911c807c41ebdc8.closte.com/wp-content/uploads/IoT-development-kit-article-banner-scaled-900x400.jpg"
AspectRatio="0.44" Aspect="AspectFit" BackgroundColor="Blue">
<a:AspectImage.Margin>
<OnIdiom Phone="10" Tablet="20" Desktop="20"/>
</a:AspectImage.Margin>
</a:AspectImage>
</VerticalStackLayout>
</ScrollView>
</Grid>
Hope it works for you.
I am trying to load a customized slider control in a listview (with accordeon behaviour). When the View loads all the listview elements are collapsed so the slider control visibility is false. I observed that the overriden Draw method within the ios renderer is not called while the control is not visible so I end up having the native control within my listview.
I have reproduced the issue in a separate project:
I have the IOS custom renderer:
public class CustomGradientSliderRenderer : SliderRenderer
{
public CGColor StartColor { get; set; }
public CGColor CenterColor { get; set; }
public CGColor EndColor { get; set; }
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
if (Control == null)
{
var customSlider = e.NewElement as CustomGradientSlider;
StartColor = customSlider.StartColor.ToCGColor();
CenterColor = customSlider.CenterColor.ToCGColor();
EndColor = customSlider.EndColor.ToCGColor();
var slider = new SlideriOS
{
Continuous = true,
Height = (nfloat)customSlider.HeightRequest
};
SetNativeControl(slider);
}
base.OnElementChanged(e);
}
public override void Draw(CGRect rect)
{
base.Draw(rect);
if (Control != null)
{
Control.SetMinTrackImage(CreateGradientImage(rect.Size), UIControlState.Normal);
}
}
void OnControlValueChanged(object sender, EventArgs eventArgs)
{
((IElementController)Element).SetValueFromRenderer(Slider.ValueProperty, Control.Value);
}
public UIImage CreateGradientImage(CGSize rect)
{
var gradientLayer = new CAGradientLayer()
{
StartPoint = new CGPoint(0, 0.5),
EndPoint = new CGPoint(1, 0.5),
Colors = new CGColor[] { StartColor, CenterColor, EndColor },
Frame = new CGRect(0, 0, rect.Width, rect.Height),
CornerRadius = 5.0f
};
UIGraphics.BeginImageContext(gradientLayer.Frame.Size);
gradientLayer.RenderInContext(UIGraphics.GetCurrentContext());
var image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return image.CreateResizableImage(UIEdgeInsets.Zero);
}
}
public class SlideriOS : UISlider
{
public nfloat Height { get; set; }
public override CGRect TrackRectForBounds(CGRect forBounds)
{
var rect = base.TrackRectForBounds(forBounds);
return new CGRect(rect.X, rect.Y, rect.Width, Height);
}
}
The View with codebehind:
Main.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="GradientSlider.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:GradientSlider">
<ContentPage.Content>
<Grid>
<StackLayout x:Name="SliderContainer">
<local:CustomGradientSlider
x:Name="mySlider"
CenterColor="#feeb2f"
CornerRadius="16"
EndColor="#ba0f00"
HeightRequest="20"
HorizontalOptions="FillAndExpand"
Maximum="10"
Minimum="0"
StartColor="#6bab29"
VerticalOptions="CenterAndExpand"
MaximumTrackColor="Transparent"
ThumbColor="green"
/>
<Label x:Name="lblText" Text="txt"
VerticalOptions="Center" HorizontalOptions="Center"/>
</StackLayout>
<Button Text="Magic" Clicked="Button_Tapped" WidthRequest="100" HeightRequest="50" VerticalOptions="Center" HorizontalOptions="Center"/>
</Grid>
</ContentPage.Content>
</ContentPage>
Main.xaml.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace GradientSlider
{
public partial class MainPage : ContentPage, INotifyPropertyChanged
{
public MainPage()
{
InitializeComponent();
SliderContainer.IsVisible = false;
}
void Button_Tapped(object sender,ClickedEventArgs a)
{
SliderContainer.IsVisible = !SliderContainer.IsVisible;
}
}
}
So in the scenario above you can see that when I load the main.xaml the control is invisible (SliderContainer.IsVisible = false;) in this case I get a native slider control and not my custom one. If I change in the constructor SliderContainer.IsVisible = true; then I get my custom control.
After an investigation I realised that if the control is not visible when the view loads the public override void Draw(CGRect rect) is not called. I could not find any solution to trigger the Draw method while the control is invisible.
Anybody has an idea how to load a custom renderer correctly while the control is not visible ?
Thank you!
Assuming the renderer is overriding OnElementPropertyChanged:
protected override void OnElementChanged(ElementChangedEventArgs<MyFormsSlider> e)
{
if (e.NewElement != null)
{
if (Control == null)
{
// Instantiate the native control and assign it to the Control property with
// the SetNativeControl method
SetNativeControl(new MyNativeControl(...
...
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
//assuming MyFormsSlider derives from View / VisualElement; the latter has IsVisibleProperty
if (e.PropertyName == MyFormsSlider.IsVisibleProperty.PropertyName)
{
//Control is the control set with SetNativeControl
Control. ...
}
...
}
I have this button:
<Button x:Name="btnNext" BorderWidth="2" BorderColor="#96AF5B" BorderRadius="4"
WidthRequest="110" HeightRequest="25" Padding="0" VerticalOptions="Center" HorizontalOptions="Center"
BackgroundColor="#FFFCFF" FontSize="Default"
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent,
Property=Width,Factor=0.5, Constant=-55}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView,
ElementName=videoPlayer,Property=Height,Factor=0.85, Constant=12.5}" FontFamily="verdana"
Clicked="Next_Clicked"/>
in Android, it shows a little square at the top left side of the button when tapped, this also happens when using a frame instead of setting the button's border properties.
here's a gif:
https://i.stack.imgur.com/FGv5j.gif
Actually, this bug has been around for a while now if I am not wrong we started facing this issue around somewhere in mid of March and it has been there ever since.
If you check Bugzilla there are a ton of bugs that have been logged for all the issues that people are facing because of this:
https://bugzilla.xamarin.com/show_bug.cgi?id=58140
https://bugzilla.xamarin.com/show_bug.cgi?id=42351
https://bugzilla.xamarin.com/show_bug.cgi?id=60248
https://bugzilla.xamarin.com/show_bug.cgi?id=60392
So I went out and devised a workaround which seems to be working fine for us using Label and Stack layout with some customized changes:
public class CustomButton: Label
{
public static readonly BindableProperty CommandProperty =
BindableProperty.Create("Command", typeof(ICommand), typeof(CustomButton), null);
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create("CommandParameter", typeof(object), typeof(CustomButton), null);
public event EventHandler ItemTapped = ( e, a ) => { };
public CustomButton()
{
Initialize();
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
private ICommand TransitionCommand
{
get
{
return new Command(async () =>
{
AnchorX=0.48;
AnchorY=0.48;
await this.ScaleTo(0.8, 50, Easing.Linear);
await Task.Delay(100);
await this.ScaleTo(1, 50, Easing.Linear);
Command?.Execute(CommandParameter);
ItemTapped(this, EventArgs.Empty);
});
}
}
public void Initialize()
{
GestureRecognizers.Add(new TapGestureRecognizer
{
Command=TransitionCommand
});
}
}
I have also added a little animation so it gives the feel for a button.
Then use this Label as follows:
<StackLayout BackgroundColor="Black" Padding="1"> // Here padding will be the border size you want and background color will be the color for it
<nameSpace:CustomButton XAlign="Center" BackgroundColor="Blue" /> //Height and Width request is mandatory here
</StackLayout>
The only problem with this solution is that you cannot add border-radius.
I need to create a triangle at the corner of a label/frame like the pic below with a number/small text in it.But just a way to draw the corner would be a great start.
How Can you do you do that ?
Any sample anywhere. Many thanks
Instead Using Plugin for just Triangle you can just use BoxView and rotate it with 135 and give negative margin so half portion will only get visible.
I achieved this using NControl https://github.com/chrfalch/NControl
public class DiagonalControl : NControlView
{
public static readonly BindableProperty CornerRadiusBindableProperty =
BindableProperty.Create(nameof(CornerRadius), typeof(int), typeof(DiagonalControl), 8);
private Xamarin.Forms.Color _backgroundColor;
public DiagonalControl()
{
base.BackgroundColor = Xamarin.Forms.Color.Transparent;
}
public new Xamarin.Forms.Color BackgroundColor
{
get
{
return _backgroundColor;
}
set
{
_backgroundColor = value;
Invalidate();
}
}
public int CornerRadius
{
get
{
return (int)GetValue(CornerRadiusBindableProperty);
}
set
{
SetValue(CornerRadiusBindableProperty, value);
}
}
public override void Draw(ICanvas canvas, Rect rect)
{
base.Draw(canvas, rect);
canvas.FillPath(new PathOp[] {
new MoveTo (0,0),
new LineTo (rect.Width, rect.Height),
new LineTo (rect.Width, 0),
new ClosePath ()
}, new NGraphics.Color((Xamarin.Forms.Color.White).R, (Xamarin.Forms.Color.White).G, (Xamarin.Forms.Color.White).B));
}
}
Then in the XAML use it like
<customviews:DiagonalControl
x:FieldModifier="Public"
HeightRequest="50"
HorizontalOptions="End"
VerticalOptions="Start"
WidthRequest="50" />
Draw path directly in xaml from Xamarin.Forms 4.7.0
(bump into the same request, and have an update for others)
<Path
HorizontalOptions="End"
VerticalOptions="Start"
Data="M 0,0 L 36,0 36,36Z"
Fill="#70a33e"
Stroke="Gray" />
And more details:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/shapes/
https://devblogs.microsoft.com/xamarin/xamarin-forms-shapes-and-paths/
I have a Grid with Buttons and will change the background color if the user swipe over them.
I created a custom Button class:
public class TouchscreenTestButton : UIButton
{
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
this.BackgroundColor = UIColor.Green;
}
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
base.TouchesMoved(touches, evt);
UITouch touch = touches.AnyObject as UITouch;
if (touch != null)
{
this.BackgroundColor = UIColor.Green;
SetNeedsDisplay();
}
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
base.TouchesEnded(touches, evt);
UITouch touch = touches.AnyObject as UITouch;
if (touch != null)
{
this.BackgroundColor = UIColor.Green;
}
}
}
Also a Button Renderer for replace the normal UIButtons of iOS to my TouchscreenTestButton.
public class TouchscreenTestButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
int columnIndex = Grid.GetColumn(e.NewElement);
int rowIndex = Grid.GetRow(e.NewElement);
TouchscreenTestButton button = new TouchscreenTestButton();
button.TouchDown +=(sender, evt) => Control.BackgroundColor=UIColor.Green;
base.SetNativeControl(button);
base.OnElementChanged(e);
}
}
That doesnt work, only the first button I touch will get a green backgroundcolor. After that I create a Renderer for the Grid:
public class GridRenderer : ViewRenderer
{
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
base.TouchesMoved(touches, evt);
var touch = touches.AnyObject as UITouch;
for (int i = 0; i < touch.View.Subviews.Length; i++)
{
var test = touch.LocationInView(this);
if (PointInside(touch.LocationInView(this), evt))
{
this.Subviews[i].TouchesBegan(touches, evt);
this.Subviews[i].BackgroundColor = UIColor.Green;
SetNeedsDisplay();
}
}
}
}
But also this doesnt work. I see that the event is fired and the backgroundcolor of the subview will be changed but the view doesnt update.
Can please somebody help? Thanks a lot!
I have test with the following code on my side , it works.
[assembly: ExportRenderer(typeof(Grid), typeof(GridRenderer))]
namespace Slide.iOS
{
class GridRenderer :ViewRenderer
{
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
var touch = touches.AnyObject as UITouch;
for (int i = 0; i < touch.View.Subviews.Length; i++)
{
var test = touch.LocationInView(this);
var buttonView = touch.View.Subviews[i];
CGRect frame = buttonView.Frame;
if (frame.Contains(test))
{
//pay attention
var button = buttonView.Subviews[0];
button.BackgroundColor = UIColor.Green;
}
}
}
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
base.TouchesMoved(touches, evt);
var touch = touches.AnyObject as UITouch;
for (int i = 0; i < touch.View.Subviews.Length; i++)
{
var test = touch.LocationInView(this);
var buttonView = touch.View.Subviews[i];
CGRect frame = buttonView.Frame;
if (frame.Contains(test)) {
//pay attention
var button = buttonView.Subviews[0];
button.BackgroundColor = UIColor.Green;
}
}
}
}
}
Result :
Details:
1.Disable the button in Portable, since the click event will be conflict with the touch event of Grid.
<Button BackgroundColor="Red" Grid.Row="0" Grid.Column="0" IsEnabled="False"/>
2.Determine whether the point you moved to is in the frame of that subview(button).
if (frame.Contains(test))
3. Look at the buttonView, pay attention it was not the button!! I set the backgroundcolor on it at the beginning but it didn't work. When we layout control in a Grid, it will auto generate subviews according to its columns and rows,and then put the controls on the subviews.
e.g
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
The Grid above will have 4 subviews ,and four corresponding buttons on that subviews.