Fix for Button Layout Bug on Android

If you've run into the same problems with layout on buttons that I have where when you either add other controls, or even just do things like set text on labels, and you're finding that the Button text alignment gets corrupted you're going to want to read this...:

This bug appears to occur pretty much whenever you have a number of nested layout controls, and in response to a button click you add controls elsewhere in the page, or even just set content (e.g. set text on a label).

It has been referenced here:

http://forums.xamarin.com/discussion/20501/is-layout-badly-broken-or-am-i-just-not-getting-it

and here:

http://forums.xamarin.com/discussion/18947/changed-button-text-alignment-after-button-clicked

The workaround is to create a Button subclass, and a corresponding ButtonRenderer to force update of the native control's text after the click event has been propagated.

Button class:

using System;
using Xamarin.Forms;
using System.Windows.Input;

namespace TestLayoutProblem
{
    public class MyButton : Button
    {
        // we have to hide the existing event since we cannot raise the event
        // (personal opinion - too much of the framework is internal. makes it very difficult to workaround bugs)
        public new event EventHandler Clicked;

        public void SendClicked()
        {
            ICommand command = this.Command;

            if (command != null)
                command.Execute(this.CommandParameter);

            EventHandler eventHandler = this.Clicked;

            if (eventHandler != null)
                eventHandler(this, EventArgs.Empty);
        }
    }
}

Android ButtonRenderer class:

using System;
using Xamarin.Forms.Platform.Android;
using Android.Content.Res;
using Android.Graphics;
using Android.Widget;
using Android.Graphics.Drawables;
using System.ComponentModel;
using Android.Runtime;
using Android.Views;
using Android.Util;
using TestLayoutProblem;

[assembly: Xamarin.Forms.ExportRenderer (typeof (MyButton), typeof (TestLayoutProblem.Android.MyButtonRenderer))]

namespace TestScrollViewProblem.Android
{
    public class MyButtonRenderer : ButtonRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
        {
            base.OnElementChanged(e);

            if (this.Control != null)
            {
                Button button = this.Control;
                button.SetOnClickListener((IOnClickListener) MyButtonRenderer.ButtonClickListener.Instance.Value);
            }
        }

        private void ForceUpdateText()
        {
            this.Control.Text = this.Element.Text;
        }

        private void HandleClick (object sender, EventArgs e)
        {
            this.ForceUpdateText();   
        }

        private class ButtonClickListener : Java.Lang.Object, IOnClickListener, IJavaObject, IDisposable
        {
            public static readonly Lazy<MyButtonRenderer.ButtonClickListener> Instance = 
                new Lazy<MyButtonRenderer.ButtonClickListener>(() => new MyButtonRenderer.ButtonClickListener());

            public void OnClick(View v)
            {
                MyButtonRenderer buttonRenderer = v.Tag as MyButtonRenderer;

                if (buttonRenderer != null)
                {
                    // have to cast to MyButton to access the new SendClicked method
                    // which replaces that of the base class Button
                    MyButton myButton = (MyButton)buttonRenderer.Element;

                    myButton.SendClicked();
                    buttonRenderer.ForceUpdateText();
                }
            }
        }
    }
}

Hope this helps someone, because it sure was a lot of work to track down.

Phil

«1

Posts

  • LauraGrossoLauraGrosso ITMember, University

    Thanks for this.
    This button text alignment bug looks really bad despite the workaround.

  • AlexeiGarbuzenkoAlexeiGarbuzenko UAMember ✭✭

    Let me put my 2ć here. I used to have another issue with Android button text alignment and I was already using own renderer with BitmapDrawable as a background. After some tests I realized that button size is first calculated from drawable bounds and by some reason that BitmapDrawable got incorrect DPI resulting in 1/2 bounds size on preparation stage. I solved this by calling drawable.SetTargetDensity(Resources.DisplayMetrics)

  • PhilipParkerPhilipParker USMember

    Alexei, do you have a copy of your renderer you can post? Maybe we can combine the fixes?

  • FredyWengerFredyWenger CHInsider ✭✭✭✭✭

    Hi Phil
    I have a similar problem.
    I "try to try" Xamarin.Forms (90-day-trial -> various base-problems).

    I have created a small test-app for all platforms.
    On a page I add various buttons to a page.
    The text of buttons is centered by showing the page (what's correct)
    After a click on a button, the button-text is showed left (instead centered).
    This is true for every button on the page (that looks very nice...)
    The problem occurs only on android (not on iOS and also not on WP).

    As it can't be that every .Forms-developer has to change the android-code to correct this bug:

    - Do you have a idea, until when this bug will be fixed?

    P.S.
    There are further bugs, i think:

    • in WP, the title of the page is not showed (in iOS and Android O.K.)

    • in WP, it's not possible to close the virtual keyboard by pressing Enter

    • In iOS IPad the Text of "Master-Page" is showed to the "Back-Button" in iPhone not (Text "Back")

  • CraigDunnCraigDunn USXamarin Team Xamurai

    @FredyWenger‌ when you created your new test app did you also update the Xamarin.Forms NuGet to the latest version in all your projects? Many issues that were written about earlier on the forums (this thread was first posted a month ago) get fixed in newer releases. The current released version is 1.2.2.6243.

  • DH80DH80 USMember ✭✭

    I am still seeing the button shift in 1.2.2.6243 on Android

  • FredyWengerFredyWenger CHInsider ✭✭✭✭✭

    I have updatet a few days ago.
    I have called the nuget-manager now once again.
    It have showed me some updates for MS (http) and

    • one update to Xamarin.Forms (version 1.2.2.6243) (as you wrote)
    • one update to Xamarin Support Library v4 (version 20.0.0.3)

    I think to remember me, that I have installed this updates already a few days ago.

    Problem: When I click the button "Update" (in nuget-manager) to the Xamarin-Updates:

    • the form to projects appears (but the checkboxes to the projects are greyed out)
      Then nothing happens...
      If I close the nuget-manager and reopen in, the Xamarin-Updates still are showed...
      I was able to install I the MS-updates without problems (after update a green tick is displayed in nuget-manager and the updates are not showed anymore).

    What to do, to solve the problem..?

    Thanks

  • FredyWengerFredyWenger CHInsider ✭✭✭✭✭

    I also have some warnings in the Project:
    To the iOS-Project:
    Warnung 3 Es wurden Konflikte zwischen verschiedenen Versionen derselben abhängigen Assembly gefunden. Legen Sie die "AutoGenerateBindingRedirects"-Eigenschaft in der Projektdatei auf "True" fest. MatrixGuide.iOS

    Translated to English:
    There are some conflicts to the same Assembly found.
    Set the "AutoGenerateBindingRedirects" in your Project-file to True.

    I don't find a property "AutoGenerateBindingRedirects" in my iOS-Project-propertys

    ... and various additional warnings to the iOS-project like:
    Warnung 5 Application references a .NET FX assembly C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll. It will not be uploaded to the build server. MatrixGuide.iOS

    Note:
    I also have the problem "Connection to Xamarin.iOS Build Host failed. Double click here to attempt to reconnect/select a server." at the moment (once again :-(().

    This means that I actually don't have a connection to the MAC BH (maybe this is the reason for the warnings to the iOS-Project..?)

    How can I solve this conflicts?
    Maybe the Problem with the nuget-Manager is caused by this Problem?

    Thanks

  • FredyWengerFredyWenger CHInsider ✭✭✭✭✭

    In the meantime I have I have searched the Internet for information's to true

    According to MS, This entry should be in the project-file.
    I then have open the project with editor - there was no entry to AutoGenerateBindingRedirects.
    I then have added the entry (with editor according to MS):


    Debug
    iPhoneSimulator
    8.0.30703
    2.0
    {FE8738DC-7E10-43F4-9090-4E6D98CCA5C8}
    {6BC8ED88-2882-458C-8E55-DFD12B67127B};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
    Exe
    MatrixGuide.iOS
    Resources
    FormsTemplateiOS
    8f5ce02c
    true

    No change... :-(

  • MichaelStrongMichaelStrong AUUniversity

    @CraigDunn‌ I am using 1.2.2.6243 and still seeing the button labels shifting left after a click. Before the click they are centered correctly.

  • JohnTamJohnTam GBMember, University ✭✭

    I have a similar issue. I have the most up to date packages (Xamarin.Forms on 1.2.2.6243) and the text inside my buttons on Android shift a few pixels to the right and down when I click on the button. This doesn't happen on iOS.

  • MatthewCowanMatthewCowan USMember, University

    Thanks Philip for the tip. I get a slight flicker still using the OnClick event handler. Using a touch listener instead without all the custom click handling seems to be working for me. Seems like there's a little bit of a disconnect in the binding between the Forms Button and the Android Widget Button Text properties.

    In the OnElementChanged:

    Control.SetOnTouchListener(TouchListener.Instance.Value);
    

    Then

    private class TouchListener : Java.Lang.Object, IOnTouchListener
    {
        public static readonly Lazy<TouchListener> Instance =
            new Lazy<TouchListener>(() => new TouchListener());
    
        public bool OnTouch(Android.Views.View v, MotionEvent e)
        {
            var buttonRenderer = v.Tag as ButtonRenderer;
            if (buttonRenderer != null && e.Action == MotionEventActions.Down)
            {
                buttonRenderer.Control.Text = buttonRenderer.Element.Text;
            }
            return false;
        }
    }
    
  • MinaSamyMinaSamy EGMember

    Thanks Matthew, you saved us from this buggy framework

  • chris_riesgochris_riesgo USUniversity ✭✭✭

    This bug had been mentioned in the 1.2.3-pre4 post

  • FalkoSchindlerFalkoSchindler USMember ✭✭
    edited October 2014

    Here is a much more light-weight workaround: http://stackoverflow.com/a/26089432/3419103 :)

    Ok, After hours of dealing with this silly bug, I resolved it by implementing a custom rendering and overriding ChildDrawableStateChanged

    public override void ChildDrawableStateChanged(Android.Views.View child) 
    { 
        base.ChildDrawableStateChanged(child); 
       Control.Text = Control.Text; 
    }
    
  • JeffHannahJeffHannah USMember ✭✭

    Hello,

    Pretty annoying bug. I've tried the various solutions offered here around the net, built a few custom renders for it and still had problems or just didn't like the excess overhead.

    I can't reproduce it anymore after:

    I created a function that accepts a Button (with optional height/width etc.) as a parameter and returns a Frame (containing the Button). Leave the Button's HorizontalOptions & VerticalOptions = Fill, and adjust the Frame to the size/LayoutOptions you want.

    The majority of the buttons in my app have the same basic look, so I have that function do all of the formatting too.

    Hope this helps.

  • plamplam USMember
    edited November 2014

    I'm currently trying to implement the light-weight workaround, but it doesn't seem to be working. I believe I may not be implementing it properly. Currently I have a RenderButton class in my PCL that is blank: public class RenderButton : Button {}

    I have a button renderer in my android project that is the solution above:

    [assembly: Xamarin.Forms.ExportRenderer(typeof(RenderButton), typeof(TEButtonRenderer))]
    namespace TestProject.Droid.CustomRenderers
    {
        public class TEButtonRenderer: ButtonRenderer
        {
            public override void ChildDrawableStateChanged(Android.Views.View child)
            {
                base.ChildDrawableStateChanged(child);
                Control.Text = Control.Text;
            }
        }
    }
    

    What may I be missing?

  • FredyWengerFredyWenger CHInsider ✭✭✭✭✭

    @plam:
    For your information...
    It seems, as this bug is fixed in the newest pre-1 release (therefore, there are various new bugs).

  • JeffHannahJeffHannah USMember ✭✭

    @plam:

    According to stackoverflow.com/questions/26056519/xamarin-forms-wrong-button-text-alignment-after-click-android/26089432#26089432, even that work-around has a problem depending on your LayoutOptions and Height etc.

    Even the work-around one I tried by wrapping the button in a Frame caused an nasty error if you created that Frame in another function and passed it back. The error didn't occur until focus was set to a control on a modal form, remove that frame and it worked fine again.

    I ended up wrapping the button in a ContentView. Not the way I'd like it to happen, but works just fine for now...

    Jeff

  • I have also come across this bug. But I noticed that if you rotate the device to change the orientation and rotate it back, the text will realign itself and any further actions on the buttons in view will not change the alignment of the text. However, if you navigate away from the page, the issue returns until you rotate the device again.

  • xeoxeo NZMember

    I am on Xamarin Forms 1.3 and I encountered this bug when using a Grid Layout. Very annoying.

  • Michael.1122Michael.1122 USUniversity

    Unfortunately, @FalkoSchindler‌ 's lightweight solution doesn't work in all scenarios. If you push two new views and then pop back two views the buttons will have left-aligned text as the ChildDrawableStateChanged is not called for every button.

    The solution that worked for me was to move FalkoSchindler's solution into DrawChild instead, which does seem to be called every time.

        protected override bool DrawChild (Android.Graphics.Canvas canvas, View child, long drawingTime)
        {
            Control.Text = Control.Text;
            return base.DrawChild(canvas, child, drawingTime);
        }
    
  • DavidCatteuwDavidCatteuw USMember ✭✭
    edited January 2015

    Thank you for your suggestion, @Michael.1122.

    However, the DrawChild override gets called too often when I implement the solution you propose, wrecking my app's performance. I don't think this is a feasible solution.

    It's a real shame this bug still isn't fixed :neutral_face:

  • @Michael.1122
    I can confirm that this works. However what @DavidCatteuw‌ is referring to, I have not tested, so he might be right that this is heavy on the device.
    However I prefer this solution over a button where text jumps everywhere when you press it, and other buttons.

  • JohanHelsingJohanHelsing NOMember
    edited January 2015

    @DavidCatteuw If you have performance problems with the fix @Michael.1122 provided. You can try this horrible workaround:

    [assembly: ExportRenderer (typeof (Button), typeof (BuggyButtonRenderer))]
    
    namespace YourNameSpace
    {
        public class BuggyButtonRenderer : ButtonRenderer
        {
            private int timesWorkedAround = 0;
            protected override bool DrawChild(Android.Graphics.Canvas canvas, Android.Views.View child, long drawingTime)
            {
                if (timesWorkedAround < 2) {
                    timesWorkedAround++;
                    Control.Text = Control.Text;
                }
                return base.DrawChild (canvas, child, drawingTime);
            }
        }
    }
    

    You might have to change the limit of the counter. Just one didn't do it for me, while two worked fine.

  • rmarinhormarinho PTMember, Insider, Beta Xamurai

    Hi guys, this should be fixed in 1.3.1 or 1.3.2. Can you please confirm you are using the latest package in all projects, and if the issue still happens please open a new bug on bugzilla so we can track it.

    Thanks

  • DanielPDanielP USMember ✭✭

    This bug still exists in 1.3.1 and 1.3.2

    https://bugzilla.xamarin.com/show_bug.cgi?id=26696

  • ManojAgarwalManojAgarwal USMember ✭✭

    Same issue still exists in 1.3.3.6323

  • This is definitely not fixed. I've got this on 1.3.3

  • FredyWengerFredyWenger CHInsider ✭✭✭✭✭

    I can confirm the "evergreen-bug" in 1.3.4 pre-2.

  • Seriously??!! !@$%

    So, I have not tried this recently, but is this seriously still not fixed?

    I posted about this issue 6 months ago! How on earth can a highly visible (to the end user!) bug like this go unresolved over 6 months ?

    Pathetic.

  • PhilipParker.1098PhilipParker.1098 USMember
    edited February 2015

    .

  • pratik90pratik90 INMember ✭✭

    ditto....

  • XamarinSnobXamarinSnob USUniversity ✭✭

    @DavidCatteuw - thanks, your solution worked for me.

    Really annoying that this hasn't been addressed yet.

  • SamFayez.8489SamFayez.8489 USMember
    edited March 2015

    @PhilipParker.1098 has a point here. I am experiencing the same issues after the latest update. Xamarin, how can you let this apparent bug remain for so long?

    I managed to temporarily fix the issue with the suggestion for setting the Control.Text on the ButtonRenderer with a maximum iteration count for it inside DrawChild as @JohanHelsing had mentioned, my iteration count was 4.
    This is needed because as @DavidCatteuw had suggested, the performance hit is pretty bad on this workaround for the following reason: Setting the native Button widget's Text property (Control.Text) calls the DrawChild() Method, and so an infinite loop occurs, so we need to break out of this process early, as @DavidCatteuw shows us with an iterationCount of 2.

  • SamFayez.8489SamFayez.8489 USMember
    edited March 2015

    Edit: There is one thing I found I had to revise:

    private int _iterationCount = 1; protected override bool DrawChild(Android.Graphics.Canvas canvas, Android.Views.View child, long drawingTime) { if (_iterationCount <= 10) { ++_iterationCount; Control.Text = Control.Text; } else { _iterationCount = 1; } return base.DrawChild(canvas, child, drawingTime); }

    I had to add an else statement to reset the iteration counter when additional rendering is done on the page that affects Child Drawing for the buttons. When content in the layout is changed as a result of user interaction, the text alignment will sometimes shift back to the left and needs to be centered again. So when the initial draw iterations are done and the text is centered, the counter is put back to the original number, so that when the DrawChild() method is called again, the iteration loop will go through again and re-center the text after a number of calls.
    Depending on the complexity of your layout, you may need to increase the iterationCount value. I originally set mine to 4, which was not enough when the button changed in height, as well as a few other properties were changed on the button in code, so 10 ended up working very well for me.

  • BrianRepettiBrianRepetti USUniversity ✭✭✭

    and another.... https://bugzilla.xamarin.com/show_bug.cgi?id=27281

    why is this such a trivial fix?

  • KeithDKeithD USMember

    Have this on 1.4 when I do a PushModalAsync when a button is clicked and then when I PopAsync after doing what I want to do in the model the alignment on the buttons go left instead of center.

  • ngocthangngocthang USMember

    Hi guys,
    Is there any solution for this issue?
    Thanks,

  • EmilSvrdEmilSvrd USMember

    Putting the following code in the code behind for the ContentPage seems to work for me:

    protected override void OnAppearing()
    {
        nameOfButton.IsVisible = false;
        nameOfButton.IsVisible = true;
    }
    
Sign In or Register to comment.