Xamarin forms button with no border issue - xaml

I try to render a list of clickable items in a view. I would like to add a button with an image and a white border (the first one). I discovered that the buttons in my StackLayout/ViewCell can't render a border.
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:XLabs.Forms.Controls;assembly=XLabs.Forms"
x:Class="*.PlacesPage"
Title="TEST">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness" iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<ContentPage.Content>
<Grid>
<ListView x:Name="lvPlaces" ItemsSource="{Binding Places}" SeparatorColor="Gray" SeparatorVisibility="Default" RowHeight="120" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Button HorizontalOptions="Center" VerticalOptions="Center" BorderWidth="3" BorderColor="White" Text="IMG"></Button>
<Button Text="{Binding Name}" BorderWidth="0" FontSize="20" BackgroundColor="Transparent" Clicked="OnButtonClickedPlace"></Button>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage.Content>

I'm using Xamarin.Forms 2.3, and I was also trying to create a button, with no border radius, a background color set to white, and a border color & width, and none of the above answers worked for me.
Actually I still had to set the BorderRadius to 1 (my width was 2), AND add another workaround that I just cannot understand :
In my Android project, I added a Custom renderer, for Button, with nothing in it. Absolutely nothing.
So the behavior of Xamarin forms, is different on Android when you use the Default renderer, and when you use a custom renderer that inherits from the default renderer, and yet with no line of code in it.
My Renderer:
[assembly: ExportRenderer(typeof(Xamarin.Forms.Button), typeof(GenericButtonRenderer))]
namespace Express.CustomRenderers
{
public class GenericButtonRenderer : Xamarin.Forms.Platform.Android.ButtonRenderer
{
}
}

There seems to be a issue in Xamarin.Forms where Button borders don't show on Android when the ButtonRadius is 0. (It doesn't appear that this issue is fixed as of Xamarin.Forms v2.0.0.6482.) It's not ideal since it will slightly change the look of the button, but you can work around it by setting BorderRadius = 1 for just Android, or all platforms, giving a slightly perceptible rounding to the corners.
Workaround (code)
// HACK: fixes a bug where border doesn't work without a radius.
yourButton.BorderRadius = Device.OnPlatform<int>(iOS: 0, Android: 1, WinPhone: 0);
Workaround (XAML)
<Button
x:Name="YourButton"
Text="Some Button Text"
TextColor="Black"
Clicked="OnClickedDiscover"
BackgroundColor="Aqua"
BorderColor="Red"
BorderWidth="1">
<Button.BorderRadius>
<!-- HACK: fixes a bug where border doesn't work without a radius. -->
<OnPlatform x:TypeArguments="x:Int32">
<OnPlatform.Android>1</OnPlatform.Android>
</OnPlatform>
</Button.BorderRadius>
</Button>

Are you using Android? If yes, then:
On Android this property will not have an effect unless
VisualElement.BackgroundColor is set to a non-default color.

In Xamarin.Forms 2.5.0, the patch has been reverted :
"Revert "Fix border on android buttons (#941)" (#1192)"
You have to use a custom renderer for now...
This bug has been fixed in the last version of Xamarin Forms 2.4.0 :
> 36031 - "Button border not drawn on Android without a BorderRadius" (PR)

in Android project go to MainActivity and change it to be inhereted
from
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
to
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
now you won't have issue to use the Border
<Button Text="test" TextColor="White"
BackgroundColor="#FFA733" BorderRadius="15"
BorderColor="White" BorderWidth="2" HorizontalOptions="FillAndExpand" />

Got the some problem. I did two things to solve it:
I don't set background color to the buttons for Android (only for iOS)
<Setter Property="BackgroundColor">
<OnPlatform x:TypeArguments="x:String">
<OnPlatform.iOS>Transparent</OnPlatform.iOS>
</OnPlatform>
</Setter>
Manually setting a background drawable to the buttons
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke android:width="2px" android:color="#ffffff" />
</shape>

I found this solution, don't know why is working but it works.
In PCL
namespace xxx.Renderers
{
public class WhiteButton : Button
{
public WhiteButton()
{
}
}
}
Then you have to make the render in android and DO NOTHING
[assembly: ExportRenderer(typeof(WhiteButton), typeof(WhiteButtonRenderer))]
namespace xxxx.Droid.Renderers
{
public class WhiteButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var newElement = e.NewElement as WhiteButton;
if (newElement != null)
{
}
}
}
}
}
Then you only have to call instantiate the button and do the border that you want
var myButton = new WhiteButton()
{
BackgroundColor = Color.Transparent,
TextColor = Color.White,
Text = "Desconectarse",
BorderRadius = 45/2,//rounded border Heightbutton/2
BorderWidth = 2,
BorderColor = Color.White
};
If nobody knows why is working please explain me , I have tried the same but with no render only using the class Button normally and if I do this I don't get the expected result.

in Android project create ButtonRenderer and paste code
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (Control != null)
{
var roundableShape = new GradientDrawable();
roundableShape.SetShape(ShapeType.Rectangle);
roundableShape.SetStroke((int) Element.BorderWidth,Element.BorderColor.ToAndroid());
roundableShape.SetColor(Element.BackgroundColor.ToAndroid());
roundableShape.SetCornerRadius(Element.BorderRadius * Resources.DisplayMetrics.Density);
Control.Background = roundableShape;
Control.TransformationMethod = null;
Control.Elevation = 0;
}
base.OnElementPropertyChanged(sender, e);
}

Related

Xamarin.Forms: xaml. Add "click" animation to button

so I have this button:
<ImageButton
Aspect="AspectFit"
Grid.Column="1"
x:Name="btn_mymatches_mainmenu"
Source="btn_emptydummy.png" BackgroundColor="#00000000"/>
When the user clicks on it, there is no animation visible as if I just were to take the standard "Button". How can I add this little animation to also play on my image button? On Android, this is like a little grey "wave". Any animation would be awesome...
Thank you!
here is a starting point.
<ImageButton
Aspect="AspectFit"
Grid.Column="1"
x:Name="btn_mymatches_mainmenu"
Source="btn_emptydummy.png"
BackgroundColor="#00000000"
Clicked="btn_mymatches_mainmenu_Clicked" />
Then in your code behind :
public async void btn_mymatches_mainmenu_Clicked(object sender, EventArgs e)
{
await btn_mymatches_mainmenu.ScaleTo(0.75, 100);
await btn_mymatches_mainmenu.ScaleTo(1, 100);
}
There is also Fade animations, and a lot of other. Just take a look at the Xamarin Forms documentation.
If you want, you can create a class which inherit from ImageButton and perform the animation inside so all your controls will have the animation without any code.
You can create an ImageButton Renderer by setting a custom background to achieve that .
[assembly: ExportRenderer(typeof(Xamarin.Forms.ImageButton), typeof(CustomImageButtonRenderer))]
namespace AppImageXml.Droid
{
public class CustomImageButtonRenderer : ImageButtonRenderer
{
public CustomImageButtonRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ImageButton> e)
{
base.OnElementChanged(e);
SetBackgroundResource(Resource.Drawable.ripple_bg);
// Set a background to be like a Ripple animation
}
}
}
ripple_bg.xml :
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#android:color/darker_gray">
<!--Ripple color-->
<item>
<shape android:shape="rectangle">
<!-- background color-->
<solid android:color="#android:color/white"/>
</shape>
</item>
</ripple>
Then the ImageButton used in Xaml can show the Ripple animation :
<ImageButton Aspect="AspectFit"
Grid.Row="1"
Grid.Column="0"
x:Name="btn_mymatches_mainmenu"
Source="heart.png"
BackgroundColor="#00000000" />
The effect :

WebView GestureRecognition not working in Xamarin forms

I have a webview When this webview is tapped I need to make visible of a button the problem Is Gesture recognition is not working
my Xaml
<customRenderer:CustomWebView Uri="{Binding SelectedJournal.Uri}" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" x:Name="CustomWebView" AbsoluteLayout.LayoutBounds="0,50,1,1" AbsoluteLayout.LayoutFlags="SizeProportional" >
<customRenderer:CustomWebView.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_OnTapped2" NumberOfTapsRequired="1" ></TapGestureRecognizer>
</customRenderer:CustomWebView.GestureRecognizers>
</customRenderer:CustomWebView>
<customRenderer:NavigationImageButton ItemTapped="FullScreenOnTapped" Source="full.jpg" AbsoluteLayout.LayoutBounds="0.5,1,-1,-1" AbsoluteLayout.LayoutFlags="PositionProportional" HeightRequest="60" WidthRequest="60" x:Name="FullScreenBtn" IsVisible="False" >
In the code behind i called like this
private void TapGestureRecognizer_OnTapped2(object sender, EventArgs e)
{
FullScreenBtn.IsVisible = true;
}
This should work but this is not working
also my custom rendering web view class
public class CustomWebView : WebView
{
public static readonly BindableProperty UriProperty = BindableProperty.Create<CustomWebView, string>(p => p.Uri, default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
}
How to Achive this
Instead of using the gesture recognizer on your webview, you can use the 'Focused' event of your view to display your button. You can do something like this:
var wb = new WebView
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
Source = "https://stackoverflow.com/questions/56320611/webview-gesturerecognition-not-working-in-xamarin-forms",
};
wb.Focused += (s, e) =>
{
//Handle your logic here!
wb.Unfocus();
};
Here, Unfocus() is used if you wish to implement your logic everytime the webview is tapped.
A WebView is used in my application to display formatted text. Tapping the text should open an editor to modify the text. The suggested Focused event works on Android but not iOS (as of iOS 15.0 simulator at least). I found the solution in the comments for https://bugzilla.xamarin.com/42/42596/bug.html#c4. Adding an empty label to the xaml as a transparent overlay of the WebView registers the tap event and enables the gesture recogniser.
<Grid HeightRequest="132">
<WebView Grid.Column="0" Grid.Row="0"
Source="{Binding FormattedDescription, Converter={tr:StringToWebViewSourceConverter}}"
HeightRequest="132"/>
<Label Grid.Column="0" Grid.Row="0" Text=""
HeightRequest="132" HorizontalOptions="Fill">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding EditDescriptionCommand}"/>
</Label.GestureRecognizers>
</Label>
</Grid>

Disable ScrollView in Xamarin Forms without disabling buttons

I have the following XAML. I want to target phones with a scrollview, and want scrolling disabled on a tablet.
<ScrollView InputTransparent="False" Orientation="Both" >
<ScrollView.IsEnabled>
<OnIdiom x:TypeArguments="x:Boolean">
<OnIdiom.Phone>True</OnIdiom.Phone>
<OnIdiom.Tablet>True</OnIdiom.Tablet>
</OnIdiom>
</ScrollView.IsEnabled>
<StackLayout VerticalOptions="FillAndExpand" BackgroundColor="White" >
<StackLayout.HorizontalOptions>
<OnIdiom x:TypeArguments="LayoutOptions">
<OnIdiom.Tablet>FillAndExpand</OnIdiom.Tablet>
<OnIdiom.Phone>Start</OnIdiom.Phone>
</OnIdiom>
</StackLayout.HorizontalOptions>
<Grid BackgroundColor="White" HeightRequest="65" MinimumHeightRequest="65">
<Grid.HorizontalOptions>
<OnIdiom x:TypeArguments="LayoutOptions">
<OnIdiom.Tablet>CenterAndExpand</OnIdiom.Tablet>
<OnIdiom.Phone>Start</OnIdiom.Phone>
</OnIdiom>
</Grid.HorizontalOptions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<WebView x:Name="webViewBtn1" HeightRequest="65" Grid.Column="0" Grid.ColumnSpan="1" VerticalOptions="FillAndExpand" BackgroundColor="White">
<WebView.HorizontalOptions>
<OnIdiom x:TypeArguments="LayoutOptions">
<OnIdiom.Tablet>CenterAndExpand</OnIdiom.Tablet>
<OnIdiom.Phone>Start</OnIdiom.Phone>
</OnIdiom>
</WebView.HorizontalOptions>
<WebView.WidthRequest>
<OnIdiom x:TypeArguments="x:Double">
<OnIdiom.Tablet>770</OnIdiom.Tablet>
<OnIdiom.Phone>300</OnIdiom.Phone>
</OnIdiom>
</WebView.WidthRequest>
</WebView>
<Button Grid.Column="0" Grid.ColumnSpan="1" x:Name="btn1" Clicked="btn1_Clicked" BackgroundColor="Transparent" TextColor="Transparent" BorderColor="White" />
</Grid>
</StackLayout>
</ScrollView>
the buttons no longer allow the user to click on them if I set ScrollView.IsEnabled the following way:
<OnIdiom.Tablet>False</OnIdiom.Tablet>
My assumption that using InputTransparent was not correct. Is there a way to make the buttons clickable inside a scroll view that has scrolling disabled?
I essentially am looking for something like Orientation=None, but that is not an option.
You need to write a CustomRenderer for disabling the scroll.
On iOS UIScrollView has a ScrollEnabled property
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
// IsScrollEnabled just a custom property
// handled it in OnPropertyChanged too
ScrollEnabled = Element.IsScrollEnabled;
}
Android it is a bit tricky, there is not direct property. We intercept the touch event and return without handling it.
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
if (Element.IsScrollEnabled)
{
return base.OnInterceptTouchEvent(ev);
}
else
{
return false;
}
}
public override bool OnTouchEvent(MotionEvent ev)
{
if (Element.IsScrollEnabled)
{
return base.OnTouchEvent(ev);
}
else
{
return false;
}
}
I ended up using this approach to disable vertical scrolling on an iPad, which is my target device. Not perfect for android 7 inch tablets, but oh well:
<ScrollView.Orientation>
<OnPlatform x:TypeArguments="ScrollOrientation">
<On Platform="iOS">
<OnIdiom x:TypeArguments="ScrollOrientation">
<OnIdiom.Phone>Both</OnIdiom.Phone>
<OnIdiom.Tablet>Horizontal</OnIdiom.Tablet>
</OnIdiom>
</On>
<On Platform="Android">
<OnIdiom x:TypeArguments="ScrollOrientation">
<OnIdiom.Phone>Both</OnIdiom.Phone>
<OnIdiom.Tablet>Both</OnIdiom.Tablet>
</OnIdiom>
</On>
<On Platform="UWP">Both</On>
</OnPlatform>
</ScrollView.Orientation>
In the latest version of Xamarin Forms, you can set the Orientation to Neither.
scrollV.Orientation = ScrollOrientation.Neither;
Cool and compact way to disable scrolling in Xamarin Forms without affecting it's children through ScrollEnabled extension method:
public static class ScrollViewEx
{
/// <summary>
/// Disables scrollview by modifying Scrolled Event and attaching itself to ScrollView's binding context
/// Scrolled event sends it back to the original x,y
/// </summary>
public class DisabledScrollClass : IDisposable
{
private double ScrollX;
private double ScrollY;
private object OldContext;
private ScrollView Parent;
public DisabledScrollClass(ScrollView parent)
{
Parent = parent;
ScrollX = parent.ScrollX;
ScrollY = parent.ScrollY;
OldContext = parent.BindingContext;
parent.Scrolled += Scrolled;
parent.BindingContext = this;
}
private void Scrolled(object sender, ScrolledEventArgs e)
{
(sender as ScrollView)?.ScrollToAsync(ScrollX, ScrollY, false);
}
public void Dispose()
{
Parent.Scrolled -= Scrolled;
Parent.BindingContext = OldContext;
}
}
public static ScrollView ScrollEnabled(this ScrollView scroll, bool isEnabled)
{
DisabledScrollClass binding = scroll.BindingContext as DisabledScrollClass;
if (isEnabled && binding != null)
binding.Dispose();
if (!isEnabled && binding == null)
_ = new DisabledScrollClass(scroll);
return scroll;
}
}

Refresh UI after Dynamicly adding entry field to stacklayout on stipper value change

I have a stepper and i want to add new entry field when the value increment or delete entry on value decrement , i manged to do that but the UI won't refresh directly after the value of the stepper change (i need to click on other UI element first) then the fields will appear , and after clicking in other UI element the stepper will work properly (adding and deleting functionality) which i don't know why it's happening ?!! .
EDIT :
It's look like the problem is with having scroll view .. if i remove it the new fields will added or deleted directly .. but still don't know why .
The xaml page
<ScrollView>
<StackLayout x:Name="StackLayout">
<Label Text="عدد العناصر المراد اضافتها (اقصى عدد في المرة الواحدة 30)"></Label>
<Stepper Maximum="30"
Minimum="2"
Increment="1"
ValueChanged="Stepper_OnValueChanged"
Value="2"></Stepper>
<StackLayout x:Name="EntryStackLayout">
<Entry></Entry>
<Entry ></Entry>
</StackLayout>
</StackLayout>
</ScrollView>
Code Behind
public partial class AddNewListOfItemsPage : ContentPage
{
public AddNewListOfItemsPage ()
{
InitializeComponent();
}
private void Stepper_OnValueChanged(object sender, ValueChangedEventArgs e)
{
if (e.NewValue > e.OldValue)
{
EntryStackLayout.Children.Add(new Entry());
}
else
{
var childCount = EntryStackLayout.Children.Count;
EntryStackLayout.Children.RemoveAt(childCount - 1);
}
}
}
Ok i found that adding a stackLayout before the scrollView fix the problem
<StackLayout>
<ScrollView>
<StackLayout x:Name="StackLayout">
<Label Text="عدد العناصر المراد اضافتها (اقصى عدد في المرة الواحدة 30)"></Label>
<Stepper Maximum="30"
Minimum="2"
Increment="1"
ValueChanged="Stepper_OnValueChanged"
Value="2"></Stepper>
<StackLayout x:Name="EntryStackLayout">
<Entry></Entry>
<Entry ></Entry>
</StackLayout>
</StackLayout>
</ScrollView>
</StackLayout>
I had a similar problem. Size of elements deep inside ScrollView was not refreshed after layout change (on Android < 4.3).
Specifying height of ScrollView helped (specifying either one of the following for ScrollView in XAML helped):
VerticalOptions="FillAndExpand"
HeightRequest="100"

How to call IOS Custom render every time when I click a StackLayout(Having Gesture Recognizer) which is inside a ListView

I am trying to build a marquee for ios so I have written a custom Label render and used animation to achieve marquee and I am calling custom Label inside a stackLayout(stackLayout is inside a ViewCell of List). this StackLayout has a gesture Recogniser.
On click I want Marquee to get called every time so I can start and stop marquee on the gesture click.Renderer is not being called every time we click the view?
It only gets called when the ViewModel constructor is called. How do I make the renderer get called everytime I click on the gesture of Stack Layout. Below is the code I have tried.
Xaml Page:
<DataTemplate>
<ViewCell>
<StackLayout x:Name="MarqueeLabelGestureView" HorizontalOptions="FillAndExpand">
<controls:MarqueeLabelLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference ResourceListView}, Path=BindingContext.OnMarqueeClicked}" CommandParameter="{Binding .}" />
</StackLayout.GestureRecognizers>
</StackLayout>
</ViewCell>
</DataTemplate>
Custom Render Code:
namespace VCS.FieldManager.UI.iOS.CustomRenderer
{
public class CustomLabelRender:LabelRenderer
{
public CustomLabelRender()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null)
{
UILabel marqueelabel = Control;
marqueelabel.Frame = new CGRect(40, 40, marqueelabel.Frame.X + 200, 40);
MessagingCenter.Send("", "OpenMarqueeLabel");
if (!GridEntryViewModel.isMarqueeClicked)
{
UIView.Animate(6, 0, UIViewAnimationOptions.CurveLinear| UIViewAnimationOptions.Repeat,
() =>
{
UIView.SetAnimationRepeatCount(1);
marqueelabel.Transform = CoreGraphics.CGAffineTransform.MakeTranslation(-100,0);
}, null);
// marqueelabel.Lines = 1200;
}
}
}
}
}