Create triangle shape in a corner xamarin forms - xamarin

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/

Related

.NET MAUI Image wrong size

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.

Position View below RelativeLayout

Context of the problem:
I do have a StackLayout with a lot of entries. When the user taps on an entry I do want to show below the tapped entry an info box. This info box should visually be above the next entry (kind of like a tooltip). The entry can have a dynamic height.
What is my approach:
Using a RelativeLayout it should be possible to position views outside the bounds of the RelativeLayout which represents the entry.
Something like this:
<StackLayout>
<BoxView BackgroundColor="Green" HeightRequest="150" ></BoxView>
<RelativeLayout BackgroundColor="Yellow" x:Name="container">
<Label Text="This is the entry"></Label>
<BoxView BackgroundColor="Aqua"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView, ElementName=container, Property=Y, Factor=1, Constant=100}"></BoxView>
</RelativeLayout>
<BoxView BackgroundColor="Green" HeightRequest="150" ></BoxView>
</StackLayout>
In this sample code the green BoxView's are kind of the entries before and after the one I do want to show. This is the result:
This makes actually sense, as I've linked to the Y-Property of the container and added 100 using "Constant".
And this is what I do want to archive:
I want to have a StackLayout with multiple entries. Whenever I click on one of this entries (yellow) right below an info should appear (blue).
How do I have to specify the YConstraint on the BoxView (which should illustrate the info window) to archive my goal? Or am I on a wrong path and another solution fits better?
I write a demo about your needs, here is running GIF.
First of all, I create content view.
<ContentView.Content>
<RelativeLayout x:Name="container" BackgroundColor="Yellow">
<Entry Text="This is the entry" x:Name="MyEntry" Focused="MyEntry_Focused" Unfocused="MyEntry_Unfocused">
</Entry>
</RelativeLayout>
</ContentView.Content>
Here is background code about content view.
public partial class FloatEntry : ContentView
{
BoxView boxView;
public FloatEntry()
{
InitializeComponent();
boxView = new BoxView();
boxView.BackgroundColor = Color.Red;
boxView.WidthRequest = 200;
}
private void MyEntry_Focused(object sender, FocusEventArgs e)
{
container.Children.Add(boxView,Constraint.RelativeToView(MyEntry, (Parent, sibling) =>
{
return sibling.X + 100;
}), Constraint.RelativeToView(MyEntry, (parent, sibling) =>
{
return sibling.Y + 50;
}));
container.RaiseChild(boxView);
}
private void MyEntry_Unfocused(object sender, FocusEventArgs e)
{
container.Children.Remove(boxView);
}
}
}
But If you used this way to achieve it, you want to BoxView to cover the below Entry. You have to put the content view to a RelativeLayout as well.
<RelativeLayout x:Name="myRl">
<myentry:FloatEntry x:Name="myfloat" HorizontalOptions="StartAndExpand" HeightRequest="50" >
<myentry:FloatEntry.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
</myentry:FloatEntry.GestureRecognizers>
</myentry:FloatEntry>
<myentry:FloatEntry HorizontalOptions="StartAndExpand" HeightRequest="50"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView, ElementName=myfloat, Property=Y, Factor=1, Constant=50}"
>
</myentry:FloatEntry>
</RelativeLayout>
Here is layout background code.
public partial class Page1 : ContentPage
{
public Page1()
{
InitializeComponent();
}
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
// I need to use following method to move the Boxview cover the blew Entry
myRl.RaiseChild(myfloat);
}
}
A more generic approach would be to write your own control which could be named as InfoBoxPopup (bascially a ContentPage) which you open manually once the Entry gets Focused and Close it on Unfocus.
Just be sure that you have on top of every page a grid panel defined.
In the InfoBox.xaml you define your custom style (panel, label, margins, IsInputTransparent?, etc. to show the custom text or other stuff)
public partial class InfoBoxPopup : ContentView
{
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(InfoBoxPopup));
public InfoBoxPopup()
{
InitializeComponent();
}
public string? Text
{
get => (string?)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public void Show()
{
var rootGrid = GetCurrentPageGrid();
var rowsCount = rootGrid.RowDefinitions.Count;
if (rowsCount > 1)
{
Grid.SetRowSpan(this, rowsCount);
}
rootGrid.Children.Add(this);
}
public void Close()
{
var rootGrid = (Grid)Parent;
rootGrid.Children.Remove(this);
}
private static Grid GetCurrentPageGrid()
{
var shellView = (ShellView)Application.Current.MainPage;
var contentPage = (ContentPage)shellView.CurrentPage;
if (contentPage.Content is Grid grid) { return grid; }
var actualPanel = contentPage.Content;
for (int i = 0; i < 10; i++)
{
var children = actualPanel.LogicalChildren;
var childGrid = children.OfType<Grid>().FirstOrDefault();
if (childGrid != null) { return childGrid; }
actualPanel = children.OfType<View>().FirstOrDefault();
}
throw new ArgumentException("No Grid panel could identified to place the info box!");
}
}

Get position of a image inside view cell listview in Xamarin forms

I have a listview with view cell.Inside the listview I have a image( using absolute layout with ratio) which i use for some animation.To do animation I need the x and y coordinates. I am extending the image as
class MyImage : Image
{
public void AnimateImage(double value)
{
this.LayoutTo(new Rectangle(this.X, this.Y - (value), 20, value),
}
}
I need to get the x and y coordinates during the time of loading(not by using any events).Through this code am not getting the correct x,y coordinates. Value am getting with the help of bindable property.
What do you mean by Through this code am not getting the correct x,y coordinates.? What is wrong?
I use you code and it works on my side.
I create a ListView with image in ViewCell and is a layout by absolute layout:
<ListView x:Name="listView" RowHeight="200">
<ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>mono</x:String>
<x:String>monodroid</x:String>
<x:String>monotouch</x:String>
<x:String>monorail</x:String>
</x:Array>
</ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<AbsoluteLayout AbsoluteLayout.LayoutBounds="0,0,500,100" AbsoluteLayout.LayoutFlags="WidthProportional" Padding="5,0,0,0">
<local:MyImage Source="Images"
AbsoluteLayout.LayoutBounds=".5,1,.1,.5" AbsoluteLayout.LayoutFlags="All" />
</AbsoluteLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And in the code behind, i use you code and the animation works well:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}
public class MyImage : Image{
public MyImage() {
NSTimer.CreateScheduledTimer(3, true, (obj) =>
{
AnimateImage(30);
});
}
public void AnimateImage(double value)
{
Console.WriteLine(this.X);
Console.WriteLine(this.Y);
this.LayoutTo(new Rectangle(this.X, this.Y - (value), 20, value), 500);
}
}
The Y of image reduce 50 every 3 seconds and the height of image change to 50.
Am I doing something different with you?
Here is a gif:
Update:
Add a little delay before call the animation as I mentioned in my comment:
public MyImage() {
Task.Delay(100).ContinueWith(t => AnimateImage(30));
}

Xamarin.Forms issue with button with border?

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.

Xamarin.Form iOS custom height ProgressBar issue

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.

Resources