【问题标题】:How to convert listener from Java to C#如何将侦听器从 Java 转换为 C#
【发布时间】:2020-03-19 20:09:33
【问题描述】:

我需要将单选选项显示为网格,并发现这个 gist 提供了一个 RadioGridGroup 类,我可以使用它来代替 Android 中提供的 RadioGroup。 (https://gist.github.com/saiaspire/a73135cfee1110a64cb0ab3451b6ca33)

我已将所有内容转换为下面的代码,只是想问一些问题。

(1) 我刚刚删除了所有“final”关键字可以吗?即int result = sNextGeneratedId.Get();

(2) 将parent == RadioGridGroup.this && child instanceof AppCompatRadioButton 转换为parent is RadioGridGroup && child is AppCompatRadioButton 是否准确?

(3) 在扩展/实现侦听器的 CheckedStateTracker 中。它可以访问 RadioGridGroup 字段,例如 Java 示例中的 mProtectFromCheckedChange,但在 C# 示例中,这些变量在那里无法访问。我该如何解决?

(4) 对于侦听器,我也让他们实现了 Java.Lang.Object,因此我不需要实现 Dispose 端 CheckedStateTracker implements CompoundButton.OnCheckedChangeListener 变为 CheckedStateTracker : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener

using System;
using Android.Content;
using Android.Support.V7;
using Android.Support.V7.Widget;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Views.Accessibility;
using Android.Widget;

using Java.Util.Concurrent.Atomic;

/**
 * https://stackoverflow.com/questions/60764344/how-to-convert-listener-from-java-to-c-sharp
 * 
 * <p>This class is used to create a multiple-exclusion scope for a set of radio
 * buttons. Checking one radio button that belongs to a radio group unchecks
 * any previously checked radio button within the same group.</p>
 * <p/>
 * <p>Intially, all of the radio buttons are unchecked. While it is not possible
 * to uncheck a particular radio button, the radio group can be cleared to
 * remove the checked state.</p>
 * <p/>
 * <p>The selection is identified by the unique id of the radio button as defined
 * in the XML layout file.</p>
 * <p/>
 * <p>See
 * {@link android.widget.GridLayout.LayoutParams GridLayout.LayoutParams}
 * for layout attributes.</p>
 *
 * @see AppCompatRadioButton
 */
public class RadioGridGroup: Android.Support.V7.Widget.GridLayout
{
    private static AtomicInteger sNextGeneratedId = new AtomicInteger(1);
    private int mCheckedId = -1;
    private CompoundButton.IOnCheckedChangeListener mChildOnCheckedChangeListener;
    private bool mProtectFromCheckedChange = false;
    private OnCheckedChangeListener mOnCheckedChangeListener;
    private PassThroughHierarchyChangeListener mPassThroughListener;

    public RadioGridGroup(Context context) : base(context)
    {
        init();

    }

    public RadioGridGroup(Context context, IAttributeSet attrs): base(context, attrs)
    {
        init();
    }

    private void init()
    {
        mChildOnCheckedChangeListener = new CheckedStateTracker();
        mPassThroughListener = new PassThroughHierarchyChangeListener();
        base.SetOnHierarchyChangeListener(mPassThroughListener);
    }

    public override void SetOnHierarchyChangeListener(IOnHierarchyChangeListener listener)
    {
        mPassThroughListener.mOnHierarchyChangeListener = listener;
    }

    protected override void OnFinishInflate()
    {
        base.OnFinishInflate();

        if (mCheckedId != -1)
        {
            mProtectFromCheckedChange = true;
            SetCheckedStateForView(mCheckedId, true);
            mProtectFromCheckedChange = false;
            setCheckedId(mCheckedId);
        }
    }

    public override void AddView(View child, int index, ViewGroup.LayoutParams prs)
    {
        if (child is AppCompatRadioButton) {
            AppCompatRadioButton button = (AppCompatRadioButton)child;
            if (button.Checked)
            {
                mProtectFromCheckedChange = true;
                if (mCheckedId != -1)
                {
                    SetCheckedStateForView(mCheckedId, false);
                }
                mProtectFromCheckedChange = false;
                setCheckedId(button.Id);
            }
        }

        base.AddView(child, index, prs);
    }

    public void check(int id)
    {
        if (id != -1 && (id == mCheckedId))
        {
            return;
        }

        if (mCheckedId != -1)
        {
            SetCheckedStateForView(mCheckedId, false);
        }

        if (id != -1)
        {
            SetCheckedStateForView(id, true);
        }

        setCheckedId(id);
    }

    private void setCheckedId(int id)
    {
        mCheckedId = id;
        if (mOnCheckedChangeListener != null)
        {
            mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
        }
    }

    private void SetCheckedStateForView(int viewId, bool chkd)
    {
        View checkedView = FindViewById(viewId);
        if (checkedView != null && checkedView is AppCompatRadioButton) {
            ((AppCompatRadioButton)checkedView).Checked = (chkd);
        }
    }

    public int GetCheckedCheckableImageButtonId()
    {
        return mCheckedId;
    }

    public void clearCheck()
    {
        check(-1);
    }

    public void SetOnCheckedChangeListener(OnCheckedChangeListener listener)
    {
        mOnCheckedChangeListener = listener;
    }

    public override void OnInitializeAccessibilityEvent(AccessibilityEvent ev)
    {
        base.OnInitializeAccessibilityEvent(ev);
        ev.ClassName = (this.GetType().Name);
    }

    public override void OnInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
    {
        base.OnInitializeAccessibilityNodeInfo(info);
        info.ClassName = (this.GetType().Name);
    }

    public interface OnCheckedChangeListener
    {
        void onCheckedChanged(RadioGridGroup group, int checkedId);
    }

    internal class CheckedStateTracker : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
    {
        public void OnCheckedChanged(CompoundButton buttonView, bool isChecked)
        {
            if (mProtectFromCheckedChange)
            {
                return;
            }

            mProtectFromCheckedChange = true;
            if (mCheckedId != -1)
            {
                SetCheckedStateForView(mCheckedId, false);
            }
            mProtectFromCheckedChange = false;

            int id = buttonView.Id;
            SetCheckId(id);
        }
    }

    internal class PassThroughHierarchyChangeListener : Java.Lang.Object
            ViewGroup.IOnHierarchyChangeListener
    {
        internal ViewGroup.IOnHierarchyChangeListener mOnHierarchyChangeListener;


        public void OnChildViewAdded(View parent, View child)
        {
            if (parent is RadioGridGroup && child is AppCompatRadioButton) {
                int id = child.Id;
                // generates an id if it's missing
                if (id == View.NoId)
                {
                    id = View.GenerateViewId();
                    child.Id = (id);
                }
                ((AppCompatRadioButton)child).SetOnCheckedChangeListener(
                        mChildOnCheckedChangeListener);
            }

            if (mOnHierarchyChangeListener != null)
            {
                mOnHierarchyChangeListener.OnChildViewAdded(parent, child);
            }
        }

        public void OnChildViewRemoved(View parent, View child)
        {
            if (parent is RadioGridGroup && child is AppCompatRadioButton) {
                ((AppCompatRadioButton)child).SetOnCheckedChangeListener(null);
            }

            if (mOnHierarchyChangeListener != null)
            {
                mOnHierarchyChangeListener.OnChildViewRemoved(parent, child);
            }
        }
    }

    public static int GenerateViewId()
    {
        for (; ; )
        {
            int result = sNextGeneratedId.Get();

            // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
            int newValue = result + 1;
            if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.

            if (sNextGeneratedId.CompareAndSet(result, newValue))
            {
                return result;
            }
        }
    }
}

更新:

下面是我的代码,其中包括将封闭的父级传递给侦听器,并使用它们来比较父级以及访问父级的字段。我还将只读添加到它没有出错的一个位置。

using System;
using Android.Content;
using Android.Support.V7;
using Android.Support.V7.Widget;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Views.Accessibility;
using Android.Widget;

using Java.Util.Concurrent.Atomic;

/**
 * https://stackoverflow.com/questions/60764344/how-to-convert-listener-from-java-to-c-sharp
 * 
 * <p>This class is used to create a multiple-exclusion scope for a set of radio
 * buttons. Checking one radio button that belongs to a radio group unchecks
 * any previously checked radio button within the same group.</p>
 * <p/>
 * <p>Intially, all of the radio buttons are unchecked. While it is not possible
 * to uncheck a particular radio button, the radio group can be cleared to
 * remove the checked state.</p>
 * <p/>
 * <p>The selection is identified by the unique id of the radio button as defined
 * in the XML layout file.</p>
 * <p/>
 * <p>See
 * {@link android.widget.GridLayout.LayoutParams GridLayout.LayoutParams}
 * for layout attributes.</p>
 *
 * @see AppCompatRadioButton
 */
public class RadioGridGroup : Android.Support.V7.Widget.GridLayout
{
    private static readonly AtomicInteger sNextGeneratedId = new AtomicInteger(1);
    private int mCheckedId = -1;
    private CompoundButton.IOnCheckedChangeListener mChildOnCheckedChangeListener;
    private bool mProtectFromCheckedChange = false;
    private OnCheckedChangeListener mOnCheckedChangeListener;
    private PassThroughHierarchyChangeListener mPassThroughListener;

    public RadioGridGroup(Context context) : base(context)
    {
        Init();
    }

    public RadioGridGroup(Context context, IAttributeSet attrs): base(context, attrs)
    {
        Init();
    }

    private void Init()
    {
        mChildOnCheckedChangeListener = new CheckedStateTracker(this);
        mPassThroughListener = new PassThroughHierarchyChangeListener(this);
        base.SetOnHierarchyChangeListener(mPassThroughListener);
    }

    public override void SetOnHierarchyChangeListener(IOnHierarchyChangeListener listener)
    {
        mPassThroughListener.mOnHierarchyChangeListener = listener;
    }

    protected override void OnFinishInflate()
    {
        base.OnFinishInflate();

        if (mCheckedId != -1)
        {
            mProtectFromCheckedChange = true;
            SetCheckedStateForView(mCheckedId, true);
            mProtectFromCheckedChange = false;
            SetCheckedId(mCheckedId);
        }
    }

    public override void AddView(View child, int index, ViewGroup.LayoutParams prs)
    {
        if (child is AppCompatRadioButton) {
            AppCompatRadioButton button = (AppCompatRadioButton)child;
            if (button.Checked)
            {
                mProtectFromCheckedChange = true;
                if (mCheckedId != -1)
                {
                    SetCheckedStateForView(mCheckedId, false);
                }
                mProtectFromCheckedChange = false;
                SetCheckedId(button.Id);
            }
        }

        base.AddView(child, index, prs);
    }

    public void check(int id)
    {
        if (id != -1 && (id == mCheckedId))
        {
            return;
        }

        if (mCheckedId != -1)
        {
            SetCheckedStateForView(mCheckedId, false);
        }

        if (id != -1)
        {
            SetCheckedStateForView(id, true);
        }

        SetCheckedId(id);
    }

    private void SetCheckedId(int id)
    {
        mCheckedId = id;
        if (mOnCheckedChangeListener != null)
        {
            mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
        }
    }

    private void SetCheckedStateForView(int viewId, bool chkd)
    {
        View checkedView = FindViewById(viewId);
        if (checkedView != null && checkedView is AppCompatRadioButton) {
            ((AppCompatRadioButton)checkedView).Checked = (chkd);
        }
    }

    public int GetCheckedCheckableImageButtonId()
    {
        return mCheckedId;
    }

    public void clearCheck()
    {
        check(-1);
    }

    public void SetOnCheckedChangeListener(OnCheckedChangeListener listener)
    {
        mOnCheckedChangeListener = listener;
    }

    public override void OnInitializeAccessibilityEvent(AccessibilityEvent ev)
    {
        base.OnInitializeAccessibilityEvent(ev);
        ev.ClassName = (this.GetType().Name);
    }

    public override void OnInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
    {
        base.OnInitializeAccessibilityNodeInfo(info);
        info.ClassName = (this.GetType().Name);
    }

    public interface OnCheckedChangeListener
    {
        void onCheckedChanged(RadioGridGroup group, int checkedId);
    }

    private class CheckedStateTracker : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
    {
        readonly RadioGridGroup enclosingClass;

        public CheckedStateTracker(RadioGridGroup enclosing)
        {
            enclosingClass = enclosing;
        }

        public void OnCheckedChanged(CompoundButton buttonView, bool isChecked)
        {
            if (enclosingClass.mProtectFromCheckedChange)
            {
                return;
            }

            enclosingClass.mProtectFromCheckedChange = true;
            if (enclosingClass.mCheckedId != -1)
            {
                enclosingClass.SetCheckedStateForView(enclosingClass.mCheckedId, false);
            }
            enclosingClass.mProtectFromCheckedChange = false;

            int id = buttonView.Id;
            enclosingClass.SetCheckedId(id);
        }
    }

    private class PassThroughHierarchyChangeListener : Java.Lang.Object,
            ViewGroup.IOnHierarchyChangeListener
    {
        internal ViewGroup.IOnHierarchyChangeListener mOnHierarchyChangeListener;
        readonly RadioGridGroup enclosingClass;

        public PassThroughHierarchyChangeListener(RadioGridGroup enclosing)
        {
            enclosingClass = enclosing;
        }

        public void OnChildViewAdded(View parent, View child)
        {
            if (parent == enclosingClass && child is AppCompatRadioButton) {
                int id = child.Id;
                // generates an id if it's missing
                if (id == View.NoId)
                {
                    id = View.GenerateViewId();
                    child.Id = (id);
                }
                ((AppCompatRadioButton)child).SetOnCheckedChangeListener(
                        enclosingClass.mChildOnCheckedChangeListener);
            }

            if (mOnHierarchyChangeListener != null)
            {
                mOnHierarchyChangeListener.OnChildViewAdded(parent, child);
            }
        }

        public void OnChildViewRemoved(View parent, View child)
        {
            if (parent == enclosingClass && child is AppCompatRadioButton) {
                ((AppCompatRadioButton)child).SetOnCheckedChangeListener(null);
            }

            if (mOnHierarchyChangeListener != null)
            {
                mOnHierarchyChangeListener.OnChildViewRemoved(parent, child);
            }
        }
    }

    public static int GenerateViewId()
    {


    for (; ; )
    {
        int result = sNextGeneratedId.Get();

        // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
        int newValue = result + 1;
        if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.

        if (sNextGeneratedId.CompareAndSet(result, newValue))
        {
            return result;
        }
    }
}

}

【问题讨论】:

    标签: c# android


    【解决方案1】:

    回答您的多个问题:

    1. 在这种情况下没问题。你需要先了解what final is used for in Java。在这里,您可以将所有三个字段/变量的 final 替换为 readonly。
    2. 转换不准确。在第一种情况下,您正在对 parent 实例进行实例比较,而在第二种情况下,您正在对 parent 实例进行类型比较。
    3. 根据SO post,在 C# 中,嵌套类不包含封闭类的引用。所以基本上不可能在 C# 中做类似RadioGridGroup.this 的事情,因为即使RadioGridGroup 包含了正在访问RadiGridGroup.this 字段的类,对它的引用在包含的类中不可用。因此,您不能引用RadioGridGroup 类的this 实例的任何私有成员。解决这个问题很简单。只需将RadioGridGroup 视为PassThroughHierarchyChangeListener 中的任何其他类,并将对RadioGridGroupthis 实例的引用传递给PassThroughHierarchyChangeListener 构造函数。所以你的代码会变成类似

      internal class CheckedStateTracker : CompoundButton.IOnCheckedChangeListener {
      
          //...
          readonly RadioGridGroup enclosingClass;
      
          //Constructor of CheckedStateTracker
          //With this, access the members of RadioGridGroup class with enclosingClass scope
          //So mProtectFromCheckedChange becomes enclosingClass.mProtectFromCheckedChange
      
          CheckedStateTracker ( RadioGridGroup enclosing){
              enclosingClass = enclosing;
          }
      }
      

    最后,您将mChildOnCheckedChangeListener 实例化如下

          //Line 51
          mChildOnCheckedChangeListener = new CheckedStateTracker(this);
    

    【讨论】:

    • 谢谢拉詹!为听众实现了您的解决方案,我的代码不再有构建错误。如果我让它运行起来,我会通知你的。
    • 关于您对问题 1 的回答。我之前也尝试将 final 替换为 readonly ,但它告诉我只有代码 private static readonly AtomicInteger sNextGeneratedId = new AtomicInteger(1); 中的第一个 final 才有可能,而不是其他两个
    • 你知道吗,我可以通过 .Check = () =>.. 而不是设置监听器来实现监听器功能吗?
    • @SweetTomato 如果你打算在这里使用 lambda,你可能会,我不确定。我必须设置并运行 IDE,看看它是否适合你.但是,我建议您首先让代码正常工作。只是我的 2 美分。
    • @SweetTomato 请求您留下赞成票并接受我的回答作为您的问题的解决方案,如果您让您的代码运行。 :) 顺便说一句,我刚刚在这里读到,在 C# 中,没有等效于 final 的局部变量 stackoverflow.com/questions/1327544/… ,所以运气不好。
    猜你喜欢
    • 1970-01-01
    • 2016-08-05
    • 2015-04-24
    • 2020-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-26
    • 2020-05-20
    相关资源
    最近更新 更多