Search code examples
c#androidxamarinmvvmcross

MvvmCross Binding Touch Event Prevents Focus on MvxItemTemplate


I have an Android App that has two RecyclerViews embedded in a HorizontalScrollView. The top RecyclerView has categories and when you select a category, the bottom RecyclerView lists the items in that category. In order to detect the selected category, I am trying to use the Touch event. When an EditText gets the touch event, that row is selected. This part works great. The problem is that I also need to be able to edit some fields in the Category list. If I bind the Touch event on an EditText, the EditText does not get focus when you click on it. I tried using the FocusChanged event instead, but sometimes, when horizontally scrolling, the focus on the top RecyclerView gets set to the EditText for the first Category thus causing the selected Category to change. Is there a way to bind the Touch event in MvvmCross and still let it cause the bound control to get focus?

This is the layout for my Item Template:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="2dp"
    android:paddingBottom="2dp"
    local:MvxBind="This View">
    <TextView
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        style="@style/TableEditText"
        local:MvxBind="Text Name; LongClick Name_LongClick; Touch Touch"
        android:id="@+id/txtName" />
    <EditText
        android:layout_width="110dp"
        android:layout_height="wrap_content"
        style="@style/TableEditText"
        android:inputType="numberDecimal|numberSigned"
        android:selectAllOnFocus="true"
        local:MvxBind="Text Cubes, Converter=FloatToStringConverter; Touch Touch"
        android:id="@+id/txtCubes" />
    <EditText
        android:layout_width="110dp"
        android:layout_height="wrap_content"
        style="@style/TableEditText"
        android:inputType="numberDecimal|numberSigned"
        android:selectAllOnFocus="true"
        local:MvxBind="Text Weight, Converter=IntToStringConverter; Touch Touch"
        android:id="@+id/txtWeight" />
    <EditText
        android:layout_width="500dp"
        android:layout_height="wrap_content"
        style="@style/TableEditText"
        android:inputType="textCapSentences"
        local:MvxBind="Text Note; Touch Touch"
        android:id="@+id/txtNote" />
    <ImageButton
        android:src="@drawable/ic_action_delete"
        android:scaleType="fitCenter"
        android:padding="3dp"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:layout_gravity="center"
        local:MvxBind="Click DeleteClicked"
        android:id="@+id/btnDeleteCategory" />
</LinearLayout>

This is my hook for the touch event:

    public MvxCommand Touch
    {
        get
        {
            return new MvxCommand(() =>
            {
                SurveyView act = (Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity as SurveyView) ?? null;
                if (act != null)
                {
                    act.SetCurrCategory(this);
                }
            });
        }
    }

This is my custom binding for the Touch Event:

public class MvxViewTouchBinding
    : MvxAndroidTargetBinding
{
    private readonly View _view;
    private IMvxCommand _command;

    public MvxViewTouchBinding(View view) : base(view)
    {
        _view = view;
        _view.Touch += ViewOnTouch;
    }

    private void ViewOnTouch(object sender, View.TouchEventArgs eventArgs)
    {
        if (_command != null)
        {
            _command.Execute();
        }
    }

    public override void SetValue(object value)
    {
        _command = (IMvxCommand)value;
    }

    protected override void Dispose(bool isDisposing)
    {
        if (isDisposing)
        {
            _view.Touch -= ViewOnTouch;
        }
        base.Dispose(isDisposing);
    }

    protected override void SetValueImpl(object target, object value)
    {
    }

    public override Type TargetType
    {
        get { return typeof(IMvxCommand); }
    }

    public override MvxBindingMode DefaultMode
    {
        get { return MvxBindingMode.OneWay; }
    }
}

This is in the Setup.cs

        registry.RegisterCustomBindingFactory<View>("Touch",
                                                  view => new MvxViewTouchBinding(view));

Solution

  • I had to change the ViewOnTouch handler to:

        private void ViewOnTouch(object sender, View.TouchEventArgs eventArgs)
        {
            eventArgs.Handled = false;
    
            if (_command != null)
            {
                _command.Execute();
            }
        }
    

    Apparently Handled defaults to true and this causes the event chain to terminate after this one is called. Setting it to false tells the system that the event hasn't been handled properly yet so it calls the next handler in line. I'm guessing that a subsequent handler sets the focus. Thanks @Cheesebaron