I'm Andrew Hoefling, and I work for FileOnQ as a Lead Software Engineer building mobile technologies for Government, Financial and First Responders using Xamarin. 

 

The Coordinate System in Xamarin.Forms and Android


This month is Xamarin UI July and this article is the July 8th 2019 Featured Blog where we will learn about the Cartesian Coordinate System and how it applies to Computer Graphics, Xamarin.Forms and the different platforms that our mobile app run on. 

In this article you will learn:

  • Basics of Coordinate Systems and how they apply to Computer Graphics. More importantly how they apply to Xamarin.Forms and mobile applications
  • How to move or animate objects on the screen in the expected directions (Up, Down, Right, Left)
  • How to move, animate or render objects on the screen relative to the placement of other objects
  • A deeper understanding of Coordinate Systems and why they are so important
  • Focus will be first on Xamarin.Forms and then Android

What is Xamarin UI July?

During the month of July 2019, Xamarin Community members from around the globe are putting together blogs about building User Interfaces (UI) in Xamarin and Xamarin.Forms. These range from building your own clone of a popular mobile app to detailed techniques for solving complex UI problems. 

See the full list of blogs

My Background

Who am I to talk to you about Coordinate Systems and advanced UI techniques?

My name is Andrew Hoefling and I am a Lead Software Engineer specializing in Xamarin.Forms and ASP.NET technologies. I originally attended Rochester Institute of Technology and have a degree in Game Design and Development. While attending school the topics covered here were crucial in solving many projects. The topics I learned in school about coordinate systems and Computer Graphics directly apply to advanced UI techniques that we use in our mobile applications.

Cartesian Coordinate System

The Cartesian Coordinate System is something that many learn in school, if you aren't familiar with what this is, we will go over it.

Given a 2-Dimensional (2-D) plane, you will have a 2-axis system that defines unique positions on the screen. 

  • The X-Axis is the Horizontal Position
  • The Y-Axis is the Vertical Position

Horizontal Axis (X-Axis)

The X-Axis of the graph is the horizontal axis. As the position moves from left to right the number increases. As the position moves from right to left the number decreases.

Vertical Axis (Y-Axis)

The Y-Axis of the graph is the vertical axis. As the position moves from top to bottom the number decreases. As the position moves from bottom to top the number increases.

This is very different in Computer Graphics. Note that this is just a traditional way to draw the graph.

Positions on the Graph

The Cartesian Coordinate System provides a way to uniquely identify positions on a 2-D plane. This is done by providing the X-Axis position and the Y-Axis position using the following notation:

(5, 2)
  • 5 - X-Axis Position
  • 2 - Y-Axis Position

Center Position

In the traditional Cartesian Coordinate System, the center position is the absolute center of the graph is

(0, 0)
  • 0 - X-Axis Position
  • 0 - Y-Axis Position

This is very different in Computer Graphics. Note that this is just a traditional way to draw the graph.

Computer Graphics Coordinate System

In Computer Graphics and more importantly User Interfaces in the mobile applications the coordinate system has some major differences. 

Horizontal Axis (X-Axis)

The flow of the X-Axis remains the same:

  • As the point moves from left -> right the value increases
  • As the point moves from right -> left the value decreases

Vertical Axis (Y-Axis)

The flow of the Y-Axis is flipped from the traditional Cartesian Coordinate System described earlier.

  • As the point moves from top -> bottom the value increases
  • As the point moves from bottom -> top the value decreases

Note that 0 is at the top

Center Position

The center position is different than the Cartesian Coordinate System described above.

  • Position (0, 0) is now at the top-left position of the screen

Xamarin.Forms Usage

In Xamarin.Forms the Coordinate System is used several different ways

  • Relative Object Position
  • Screen Position
  • Platform Position (Android, iOS) 

Not What You Expected

In Xamarin.Forms there are several properties on each Element that is not exactly what you would expect. Each object contains a Bounds object with the X, Y. The default values are 0, 0.

  • Bounds.X is the relative horizontal position of the Element
  • Bounds.Y is the relative vertical position of the Element 

Manipulating these properties will update the relative position. If you follow the rules from the Computer Science Coordinate System described above you will be able to move them on the screen.

But Screen Position?

The screen position of an Element is, where on the screen does the element render. This is not necessarily a technical definition, for this article Screen Position means the position on the Screen Plane. As described in the Computer Scient Coordinate System this means (0, 0) is the top-left position.

I have found it best to maniuplate the screen position using Custom Renderers in Xamarin.Forms.

The custom renderers provides native access to bounding boxes to perform actions on Elements with granular position access.

Moving Elements

Moving Elements around in Xamarin.Forms is easy using the basic animation APIs provided in Xamarin.Forms. While the (X, Y) position is all relative, the coordinate system follows the same directions. 

Given we have a Button on the top-left portion of the screen and we want to move it to the bottom right porition of the screen.

 The shared code for Xamarin.Forms would use the Translate API

myButton.TranslateTo(5, 5);

Xamarin Animations

Earlier this year, I wrote an article all about Xamarin.Forms Translation animations. It goes into more details describing the usage of this API.

Translation Animations in Xamarin.Forms

Advanced Positioning

The advanced positioning techniques in Xamarin require direct API access and custom renderers. Once the custom renderer can access the native APIs it will be able to describe the exact position on the screen and perform additional business rules.

Our solution will focus on the Android Platform but the techniques can be applied to iOS and UWP

Consider the following scenario:

An app requires you to access the exact center point of an object. This exact center point needs to be used to position a Floating Action Button.

Mockup

Consider that there is a red button somewhere on the screen, in the mockup it is in the bottom-right position. When the user taps on the button a Green Circle appears over the exact center.

Disclaimer

Yes, this exact scenario can be accomplished in 100% shared code. However, the principles applied can solve much more complex scenarios. Keeping the problem scope simple helps for better understanding of this advanced technique.

Shared Code

Create a shared control that will be used for rendering the Red Control and the Floating Green Circle. We will call it the CenterControl for this demo.

CenterControl.cs

public class CenterControl : Button
{
}

MainPage.xaml

<?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:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:c="clr-namespace:App1"
             mc:Ignorable="d"
             x:Class="App1.MainPage">

    <StackLayout>
        <c:CenterControl Text="Test"
                         VerticalOptions="EndAndExpand"
                         HorizontalOptions="EndAndExpand" />
    </StackLayout>

</ContentPage>

Android Implementation

In the Android Project create your Custom Renderer for the CenterControl, which we will call CenterControlRenderer. Since we are using a Xamarin.Forms Buttonwe can simplify the Custom Renderer code by inheriting from Button.

Create the Custom Renderer Stub

[assembly: ExportRenderer(typeof(CenterControl), typeof(CenterControlRenderer))]
namespace App1.Droid
{
    public class CenterControlRenderer : ButtonRenderer
    {
        public CenterControlRenderer(Context context) : base(context)
        {
        }
    }
}

Create Event Handler Stub

To properly create the event handler, configure the CenterControlRenderer to implement the android interface IOnClickListener.

public class CenterControlRenderer : ButtonRenderer, IOnClickListener
{
    Android.Widget.Button button;
    public CenterControlRenderer(Context context) : base(context)
    {
    }


    protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
    {
        base.OnElementChanged(e);

        if (e.OldElement == null)
        {
            button.SetOnClickListener(this);
        }
    }

    protected override Android.Widget.Button CreateNativeControl()
    {
        button = base.CreateNativeControl();
        return button;
    }

    public void OnClick(Android.Views.View v)
    {
        // TODO - Calculate center position
    }
}

Calculate The Center

Calculating the center position of a View in Android is not the most intutitive thing if you don't understand the coordinate system. But since we do, this will be simple. Remember: The coordinate system starts with (0, 0) at the top left, this includes any Rect that is inside the coordinate system.

This means if we get the location of a View that position will be the top left portion of the View

Using this code snippet we can access the top-left position of our control. The result of this method stores the X,Y position into the 2 element array where

  • location[0] is the X Position
  • location[1] is the Y Position

public void OnClick(Android.Views.View centerControl)
{
    int[] location = new int[2];
    centerControl.GetLocationOnScreen(location);
}

To get the exact center of our control you will need to apply the height and width to the top-left position retreived. Remember from eariler that as we flow towards the bottom-right both the X-Axis and Y-Axis will be positive in value. 

public void OnClick(Android.Views.View centerControl)
{
    int[] location = new int[2];
    centerControl.GetLocationOnScreen(location);

    var centerX = location[0] + (centerControl.Width / 2);
    var centerY = location[1] + (centerControl.Width / 2);
}

When dealing in absolute screen positions such as an Android Dialog the code needs to account for the height of the status bar. Let's create a new method to calculate the offset and then we can apply it to our centerY.

private int OffsetY()
{
    var id = Resources.GetIdentifier("status_bar_height", "dimen", "android");
    return Resources.GetDimensionPixelSize(id);
}

Now update the OnClick behavior to apply the offest to the vertical center

public void OnClick(Android.Views.View centerControl)
{
    int[] location = new int[2];
    centerControl.GetLocationInWindow(location);

    var centerX = location[0] + (centerControl.Width / 2);
    var centerY = location[1] + (centerControl.Height / 2) - OffsetY();
}

Create the Green Circle

Creating the Green Circle as shown in the mockup is not the simpliest thing as we do not just want to add it to the layout container or customize it. We just want to render the Green Circle ontop of the button at the exact center. The easiest way to do this is using the Android Dialog

Here is a snippet of code we are using to create the native Dialog and render a circle

private void CreateCircleDialog(int centerX, int centerY)
{
    var dialog = new Dialog(Context);
    dialog.Window.SetBackgroundDrawable(new ColorDrawable(Android.Graphics.Color.Transparent));
    dialog.Window.DecorView.SetBackgroundColor(Android.Graphics.Color.Transparent);

    var decorView = (ViewGroup)dialog.Window.DecorView;
    var childView = (FrameLayout)decorView.GetChildAt(0);

    var layoutParams = (FrameLayout.LayoutParams)childView.LayoutParameters;
    layoutParams.Width = 50;
    layoutParams.Height = 50;
    childView.LayoutParameters = layoutParams;

    var imageView = new ImageView(Context);
    imageView.LayoutParameters = new LayoutParams(50, 50);

    var circle = new ShapeDrawable(new OvalShape());
    circle.SetIntrinsicHeight(50);
    circle.SetIntrinsicWidth(50);
    circle.Paint.Color = Android.Graphics.Color.Green;
    circle.Bounds = new Rect(0, 0, 50, 50);

    imageView.SetBackground(circle);
    dialog.SetContentView(imageView);

    dialog.Show();
}

Once you have this code working and you invoke it from the click handler you will get something that looks like this screenshot.

The circle is rendering in the center of the screen, we want it to render exactly over the button that invoked the request earlier. This can be solved by passing the X, Y coordinates on the screen where we want it to render. 

Adjust the method signature

private void CreateCircleDialog(int centerX, int centerY)

Now in the original Dialog Code from earlier add the following statements:

Step 1

Configure the dialog's coordinate system to start in the top-left

dialog.Window.SetGravity(GravityFlags.Top | GravityFlags.Left);

Step 2

Set the X, Y coordinates of the circle based on the calculation from earlier

dialog.Window.Attributes.X = centerX;
dialog.Window.Attributes.Y = centerY;

When you run the app it should render the Green Circle over the button, but it doesn't look quite right.

If you look closely at the screen shot you may notice the Green Circle's top-left point is perfectly centered. This is because we are positioning the Green Circle by the top-left point and not the center of the circle. Remember our coordinate system starts at the top-left, any variations from that point need to be manually calculated.

To calculate the center point of the Green Circle you can just apply the 1/2 the width and height to the centerX and centerY

Step 1

Invoke the measure API to get a MeasuredWidth and MeasuredHeight

dialog.Window.DecoreView.Measure((int)MeasureSpecMode.Unspecified, (int)MeasureSpecMode.Unspecified);

Step 2

Calculate the center of circle and apply that to the centerX and centerY

dialog.Window.Attributes.X = centerX - (dialog.Window.DecorView.MeasuredWidth / 2);
dialog.Window.Attributes.Y = centerY - (dialog.Window.DecoreView.MeasuredHeight / 2);

 Now when you run the app the circle will be perfectly centered over the button on both the Horizonatl and Vertical Axis

Final Custom Renderer

The source of the Android Custom Renderer

My Coordinate System Process

When I am writing advanced UI code and need to really customize where things are positioned on the screen I follow the same process regardless of the platform. Most UI systems will start the coordinate system in the top-left but it may be different, so be advised of what the platform default is.

  1. Determine Coordinate System
  2. Calculate position of where you want to place control
  3. Calculate center of control
  4. Apply transformation to control

There is sometimes a lot of iterations, and you may need to create a sandbox project to really understand what is going on, something I do often

Conclusion

This is meant to be an introduction to Coordinate Systems and how they apply to Xamarin.Forms and Android development. You should be able to understand how items are positioned on the screen, how to perform basic maniuplations and the concepts of more advanced manipulations.

Be sure to follow the rest of the great community bloggers this month for Xamarin UI July

-Happy Coding


Share

Tags

Xamarin UI JulyXamarin.FormsAndroidXamarinRenderer.NETC#Coordinate SystemAnimationsTransformmanipulation