I need to achieve this effect on Xamarin.iOS for a Button (so just the blue button with rounded corners in the image), basically just make any corner of a button rounded, but not all of them at once. I prefer not using another nuget package like "Pancake" which I saw as an answer. Is there any platform specific customization on iOS to match the Android one?
I managed to achieve this on ANDROID like this:
public class DroidCustomButtonRenderer : ButtonRenderer
{
public DroidCustomButtonRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Forms.Button> e)
{
base.OnElementChanged(e);
if (Control != null)
{
GradientDrawable gradientDrawable = new GradientDrawable(); gradientDrawable.SetColor(global::Android.Graphics.Color.ParseColor(AppConstants.Constants.CUSTOM_BACK_BUTTON_COLOR_NEW));
float[] radius = new float[8];
radius[0] = 0; //Top Left corner
radius[1] = 0; //Top Left corner
radius[2] = 46f; //Top Right corner
radius[3] = 46f; //Top Right corner
radius[4] = 46f; //Bottom Right corner
radius[5] = 46f; //Bottom Right corner
radius[6] = 0; //Bottom Left corner
radius[7] = 0; //Bottom Left corner
gradientDrawable.SetCornerRadii(radius);
Control.SetBackground(gradientDrawable);
}
}
}
How can I achieve same on iOS please?
public class iOSCustomButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
// ?
}
}
You can use MaskedCorners in your iOS class to control the display of graphics.
Here is my implementation class for your reference:
[assembly: ExportRenderer(typeof(CustomButton),typeof(CurvedCornersButtonRenderer))]
namespace xxx.iOS
{
class CurvedCornersButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
Control.BackgroundColor = UIColor.Blue;
Control.ClipsToBounds = true;
Control.Layer.CornerRadius = 15;
Control.Layer.MaskedCorners = CoreAnimation.CACornerMask.MaxXMinYCorner | CoreAnimation.CACornerMask.MaxXMaxYCorner;
}
}
}
The above answer works, but if someone wants to change all corners, must include CoreAnimation.CACornerMask.MinXMinYCorner|CoreAnimation.CACornerMask.MinXMaxYCorner for "MaskedCorners".
Related
I need to crop the image in elliptical shape, but I do not want to use Ellipse and fill with ImageBrush as mentioned in this link , instead I need the bitmap itself to be in rounded / elliptical instead of rectangular.
Sometimes I would like to crop in rectangular and sometimes in elliptical, so I cannot use Ellipse and fill.
Is there any alternative solution to this? It would also be better if I can clip the Image in elliptical format.
But the Clip in Image accepts only RectangleGeometry.
you can create a custom UserControl and in that use win2d CanvasControl to display image where you can draw image in any shape that you want, using "CreateLayer" funtion to mask the drawing image with shape. for example
at xaml:
<UserControl
x:Class="UWPClassLib.MyImageControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UWPClassLib"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:win2d="using:Microsoft.Graphics.Canvas.UI.Xaml"
mc:Ignorable="d">
<win2d:CanvasControl x:Name="w2dCanvas"
Draw="w2dCanvas_Draw" />
</UserControl>
at code behind :
public enum MaskShape
{
rectangle,
circle
}
public sealed partial class MyImageControl : UserControl
{
public WriteableBitmap Bitmap
{
get { return (WriteableBitmap)GetValue(BitmapProperty); }
set { SetValue(BitmapProperty, value); }
}
public static readonly DependencyProperty BitmapProperty = DependencyProperty.Register(nameof(Bitmap), typeof(WriteableBitmap), typeof(MyImageControl), new PropertyMetadata(null, OnBitmapPropertyChanged));
private static void OnBitmapPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myctrl = (MyImageControl)d;
myctrl.TryCreateResource();
}
public MaskShape Shape
{
get { return (MaskShape)GetValue(ShapeProperty); }
set { SetValue(ShapeProperty, value); }
}
public static readonly DependencyProperty ShapeProperty = DependencyProperty.Register(nameof(Shape), typeof(MaskShape), typeof(MyImageControl), new PropertyMetadata(MaskShape.circle));
private CanvasBitmap Source;
public MyImageControl()
{
this.InitializeComponent();
}
public void Invalidate()
{
w2dCanvas.Invalidate();
}
private void w2dCanvas_Draw(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
using (var session = args.DrawingSession)
{
session.Clear(Colors.Transparent);
if (CheckResourceCreated())
{
using (var mask = GetMaskShape())
using (var layer = session.CreateLayer(1.0f, mask))
{
session.DrawImage(Source);
}
// either you can do that or can use
// session.FillGeometry(mask, imagebrush); //and image brush can be made from source e.g.
// imagebrush = new CanvasImageBrush(w2dCanvas, Source);
}
}
}
private CanvasGeometry GetMaskShape()
{
switch (Shape)
{
default:
case MaskShape.circle:
var center = new System.Numerics.Vector2((float)this.ActualWidth / 2, (float)this.ActualHeight / 2);
var radiusX = (float)this.ActualWidth / 2;
var radiusY = (float)this.ActualHeight / 2;
return CanvasGeometry.CreateEllipse(w2dCanvas, center, radiusX, radiusY);
case MaskShape.rectangle:
return CanvasGeometry.CreateRectangle(w2dCanvas, new Rect(0, 0, this.ActualWidth, this.ActualHeight));
}
}
private bool CheckResourceCreated()
{
if (Source == null)
{
TryCreateResource();
}
return (Source != null);
}
private void TryCreateResource()
{
try
{
if (Bitmap == null)
return;
Source = CanvasBitmap.CreateFromBytes(w2dCanvas, Bitmap.PixelBuffer.ToArray(), Bitmap.PixelWidth, Bitmap.PixelHeight, DirectXPixelFormat.B8G8R8A8UIntNormalized);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
and now you can use it any where to display image in rectangle or circle. you just have to change MaskShape and call invalidate
plese forgive any error since, i wrote it on the go and haven't test it.
its just for an idea
i need to Create a ActionBar with TabbedLayout control in xamarin forms, In xamarin Android i did that Easily but now they want in both platform IOS and Android using Xamarin forms.please share any Example or Give me suggestion for how to make the custom Controls in Xamari Froms.
Below i have attached the Image how i need Action bar with Tabbed layout.
If you are using Xamarin.Forms the Tabbed page, for Android tabbar items will in the top. For iOS, you have to create a renderer to achieve it. However, Showing Tabbar items in the top are against User guidelines of iOS.
Create custom render, override ViewDidLayoutSubviews and add the following lines code.
[assembly: ExportRenderer(typeof(ExtendedTabbedPage), typeof(ExtendedTabbedPageRenderer))]
namespace ExtendedTabbedPage.Pages
{
public class ExtendedTabbedPageRenderer : TabbedRenderer
{
private ExtendedTabbedPage Page => (ExtendedTabbedPage)Element;
public ExtendedTabbedPageRenderer()
{
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var page = (ExtendedTabbedPage)Element;
page.CurrentPageChanged += Page_CurrentPageChanged;
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
Page_CurrentPageChanged();
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
SetTabPostition();
}
void SetTabPostition()
{
if (Element == null)
return;
var element = Element as ExtendedTabbedPage;
this.TabBar.InvalidateIntrinsicContentSize();
nfloat tabSize = 74.0f;
UIInterfaceOrientation orientation = UIApplication.SharedApplication.StatusBarOrientation;
if (UIInterfaceOrientation.LandscapeLeft == orientation || UIInterfaceOrientation.LandscapeRight == orientation)
{
tabSize = 32.0f;
}
CGRect tabFrame = this.TabBar.Frame;
CGRect viewFrame = this.View.Frame;
tabFrame.Height = tabSize;
tabFrame.Y = this.View.Frame.Y;
this.TabBar.Frame = tabFrame;
this.TabBar.ContentMode = UIViewContentMode.Top;
PageController.ContainerArea = new Rectangle(0, tabFrame.Y + tabFrame.Height, viewFrame.Width, viewFrame.Height - tabFrame.Height);
this.TabBar.SetNeedsUpdateConstraints();
}
void Page_CurrentPageChanged()
{
var current = Tabbed.CurrentPage;
//if Tab is more than 5 then more will appear in iOS
if (current == null)
{
CGRect tabFrm = this.TabBar.Frame;
if (this.MoreNavigationController != null)
{
var morenavframe = this.MoreNavigationController.View.Frame;
morenavframe.Y = tabFrm.Y + tabFrm.Height;
this.MoreNavigationController.View.Frame = morenavframe;
foreach (var morecontroller in this.MoreNavigationController.ViewControllers)
{
var morecontframe = morecontroller.View.Frame;
morecontframe.Y = morenavframe.Y + morenavframe.Height;
morecontroller.View.Frame = tabFrm;
}
}
return;
}
var controller = Platform.GetRenderer(current);
if (controller == null)
return;
var frame = controller.ViewController.View.Frame;
CGRect tabFrame = this.TabBar.Frame;
frame.Y = (tabFrame.Y + tabFrame.Height);
controller.ViewController.View.Frame = frame;
this.View.Frame = frame;
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
Page_CurrentPageChanged();
}
}
}
To get a tabbed layout in Xamarin.Forms you'll usually use a TabbedPage. This will give you the tabs you show on Android. On iOS and Windows you'll get the native alternative. This means you'll get the tabs on the bottom of the screen on iOS and on Windows you'll get the tabs on top (similar, but exactly like on Android). See this illustration from the Xamarin docs:
If you want to create your own version you can implement your own version of the MultiPage class.
Now before anyone ignores this as a duplicate please read till the end. What I want to achieve is this
I've been doing some googling and looking at objective c and swift responses on stackoverflow as well. And this response StackOverFlowPost seemed to point me in the right direction. The author even told me to use ClipsToBounds to clip the subview and ensure it's within the parents bounds. Now here's my problem, if I want to show an image on the right side of the entry(Gender field), I can't because I'm clipping the subview.
For clipping, I'm setting the property IsClippedToBounds="True" in the parent stacklayout for all textboxes.
This is the code I'm using to add the bottom border
Control.BorderStyle = UITextBorderStyle.None;
var myBox = new UIView(new CGRect(0, 40, 1000, 1))
{
BackgroundColor = view.BorderColor.ToUIColor(),
};
Control.AddSubview(myBox);
This is the code I'm using to add an image at the beginning or end of an entry
private void SetImage(ExtendedEntry view)
{
if (!string.IsNullOrEmpty(view.ImageWithin))
{
UIImageView icon = new UIImageView
{
Image = UIImage.FromFile(view.ImageWithin),
Frame = new CGRect(0, -12, view.ImageWidth, view.ImageHeight),
ClipsToBounds = true
};
switch (view.ImagePos)
{
case ImagePosition.Left:
Control.LeftView.AddSubview(icon);
Control.LeftViewMode = UITextFieldViewMode.Always;
break;
case ImagePosition.Right:
Control.RightView.AddSubview(icon);
Control.RightViewMode = UITextFieldViewMode.Always;
break;
}
}
}
After analysing and debugging, I figured out that when OnElementChanged function of the Custom Renderer is called, the control is still not drawn so it doesn't have a size. So I subclassed UITextField like this
public class ExtendedUITextField : UITextField
{
public UIColor BorderColor;
public bool HasBottomBorder;
public override void Draw(CGRect rect)
{
base.Draw(rect);
if (HasBottomBorder)
{
BorderStyle = UITextBorderStyle.None;
var myBox = new UIView(new CGRect(0, 40, Frame.Size.Width, 1))
{
BackgroundColor = BorderColor
};
AddSubview(myBox);
}
}
public void InitInhertedProperties(UITextField baseClassInstance)
{
TextColor = baseClassInstance.TextColor;
}
}
And passed the hasbottomborder and bordercolor parameters like this
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
var view = e.NewElement as ExtendedEntry;
if (view != null && Control != null)
{
if (view.HasBottomBorder)
{
var native = new ExtendedUITextField
{
BorderColor = view.BorderColor.ToUIColor(),
HasBottomBorder = view.HasBottomBorder
};
native.InitInhertedProperties(Control);
SetNativeControl(native);
}
}
But after doing this, now no events fire :(
Can someone please point me in the right direction. I've already built this for Android, but iOS seems to be giving me a problem.
I figured out that when OnElementChanged function of the Custom Renderer is called, the control is still not drawn so it doesn't have a size.
In older versions of Xamarin.Forms and iOS 9, obtaining the control's size within OnElementChanged worked....
You do not need the ExtendedUITextField, to obtain the size of the control, override the Frame in your original renderer:
public override CGRect Frame
{
get
{
return base.Frame;
}
set
{
if (value.Width > 0 && value.Height > 0)
{
// Use the frame size now to update any of your subview/layer sizes, etc...
}
base.Frame = value;
}
}
So i wrote this radial Menu controlled by the trackpad on the left-hand wand. It determine which button to magnify by my fingers position on trackpad.
The Weird movement can be seen here.
Here i attacked my code related to this problem, the code for left wand.
SteamVR_TrackedObject obj; //The wand
public GameObject buttonHolder; //All the buttons will be children of this object
public bool buttonEnabled;
void Awake() {
obj = GetComponent<SteamVR_TrackedObject>(); //this will be left hand controller
}
void Update() {
var device = SteamVR_Controller.Input((int)obj.index);
//if touchpad touched
if (device.GetTouch(SteamVR_Controller.ButtonMask.Touchpad))
{
if (buttonEnabled) //if radial menu is open
{
//touchPadAngle: Get the angle between touch coord and X-axis
Vector2 touchedCoord = device.GetAxis(EVRButtonId.k_EButton_Axis0); //what is this line each variable
float touchPadAngle = VectorAngle(new Vector2(1, 0), touchedCoord); //(1, 0) is X-axis
// ------------------- Find closest button ------------------------
//Description: The process will be done by calculating the angle between button_Vector2 and X-axis (button_V2_to_10)
// And then find the button with the closest angler difference with (touchPadAngle).
float minAngle = float.PositiveInfinity;
Transform minButton = transform; //Temperatry assign wand tranform to it.
float pad_N_button_Angle = 0.0f; //Angle between touchPadAngle and buttonAngle.
Vector2 button_V2_to_10;
float button_Angle;
foreach (Transform bt in buttonHolder.transform)
{
button_V2_to_10 = new Vector2(transform.position.x, transform.position.z) - new Vector2(bt.position.x, bt.position.z);
button_Angle = VectorAngle(new Vector2(1, 0), button_V2_to_10);
pad_N_button_Angle = Mathf.Abs(button_Angle - touchPadAngle);
//Both buttonAngle and touchPadAngle range from -180 to 180, avoid Abs(170 - (-170)) = 340
pad_N_button_Angle = (pad_N_button_Angle > 180) ? Mathf.Abs(pad_N_button_Angle - 360) : pad_N_button_Angle;
if (pad_N_button_Angle < minAngle)
{
minButton = bt;
minAngle = pad_N_button_Angle;
}
}
//Magnify the closest button
foreach (Transform bt in buttonHolder.transform)
{
GameObject btGO = bt.gameObject;
if (!btGO.GetComponentInChildren<ButtomHandler>().onHover && bt == minButton) {
//Magnify
}
else if (bt != minButton && btGO.GetComponentInChildren<ButtomHandler>().onHover)
{
//minify
}
}
}
else {
activateButtonMenu();
}
}
//dis-hover all button if leave touch pad
if (device.GetTouchUp(SteamVR_Controller.ButtonMask.Touchpad)) {
//Hover the closest button
foreach (Transform bt in buttonHolder.transform)
{
GameObject btGO = bt.gameObject;
if (btGO.GetComponentInChildren<ButtomHandler>().onHover)
{
//minify
}
}
}
I'm quite stucked here, Any help would really be appreciated
"the closest angler difference with (touchPadAngle)"
shouldn't you consider more than one axis for a radial dial?
As the center node of a gluon view I have a scrollpane which contains several textfields in a vbox. When one of these textfields becomes the focusowner and the keyboard shows up, the textfield doesn't get repositioned according to the layout of the keyboard, so it is left covered by the keyboard.
I tried putting
android:windowSoftInputMode="adjustResize"
in the AndroidManifest, but without any success.
As a workaround I translate the y-coordinates of the covered textfield to the visible area. When you press the android back button to hide the keyboard, the textfields position will be reset to its original state. The issue I'm getting here is that I don't get an event for the android back button, no matter where I add the listener:
view.addEventFilter(MobileEvent.BACK_BUTTON_PRESSED, evt -> eventFilter);
MobileApplication.getInstance().getGlassPane().addEventFilter(MobileEvent.BACK_BUTTON_PRESSED, evt -> eventFilter);
Is there any possibility to handle the positioning of a node under the keyboard, or to get a reference to the keyboard itself?
Only layers get the MobileEvent.BACK_BUTTON_PRESSED event. One solution is to go native and use the Android API.
This is the solution I could come up with so far:
public class PositionAdjuster {
public static void main(String[] args) { launch(args); }
private static final float SCALE = FXActivity.getInstance().getResources().getDisplayMetrics().density;
private Node nodeToAdjust;
private ObservableValue<Node> focusOwner;
private ViewGroup viewGroup;
private Rect currentBounds;
private DoubleProperty height;
private OnGlobalLayoutListener layoutListener;
public PositionAdjuster(Node nodeToAdjust, ObservableValue<Node> focusOwner) {
this.nodeToAdjust = nodeToAdjust;
this.focusOwner = focusOwner;
initLayoutListener();
}
private void initLayoutListener() {
double screenHeight = MobileApplication.getInstance().getScreenHeight();
height = new SimpleDoubleProperty(screenHeight);
height.addListener((ov, n, n1) -> onHeightChanged(n, n1));
layoutListener = () -> height.set(getCurrentHeigt());
viewGroup = FXActivity.getViewGroup();
viewGroup.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
currentBounds = new Rect();
}
private float getCurrentHeigt() {
viewGroup.getRootView().getWindowVisibleDisplayFrame(currentBounds);
return currentBounds.height() / SCALE;
}
private void onHeightChanged(Number oldValue, Number newValue) {
double heightDelta = newValue.doubleValue() - oldValue.doubleValue();
if (heightDelta < 0) {
double maxY = getBoundsInScene(nodeToAdjust)).getMaxY();
double currentMaxY = heightDelta + maxY;
double result = currentMaxY- getBoundsInScene(focuseOwner.getValue()).getMaxY();
if (result < 0) {
nodeToAdjust.setTranslateY(result);
}
} else if (heightDelta > 0) {
nodeToAdjust.setTranslateY(0);
}
}
private Bounds getBoundsInScene(Node node) {
return node.localToScene(node.getBoundsInLocal());
}
public void removeListener() {
viewGroup.getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener);
}
}
EDIT:
I think this is a more straightforward approach. The previous version was dependent on the maxY of noteToAdjust to be equal to the height of the screen, not taking into account e.g. the presence of a bottomBar. Now the maxY position of the focusedNode is validated against the visible screen height, and the difference is used to reposition its parent.
public AndroidPositionAdjuster(Node parent, ObservableValue<Node> focusOwner) {
this.parent = parent;
this.focusOwner = focusOwner;
initLayoutListener();
}
private void onHeightChanged(Number oldHeight, Number newHeight) {
double heightDelta = newHeight.doubleValue() - oldHeight.doubleValue();
if (heightDelta < 0) {
double maxY = newHeight.doubleValue();
double focusedNodeY = getBoundsInScene(focusOwner.getValue()).getMaxY();
double result = maxY - focusedNodeY;
if (result < 0) {
parent.setTranslateY(result);
}
} else if (heightDelta > 0) {
parent.setTranslateY(0);
}
}