Reputation: 569
I need to display radio options as a grid and found this gist offering a RadioGridGroup class I can use instead of the RadioGroup provided in Android. (https://gist.github.com/saiaspire/a73135cfee1110a64cb0ab3451b6ca33)
I have converted everything into the code below and just wanted to ask some questions.
(1) Is it okay that I just deleted all the "final" keywords? ie int result = sNextGeneratedId.Get();
(2) Is converting parent == RadioGridGroup.this && child instanceof AppCompatRadioButton
to parent is RadioGridGroup && child is AppCompatRadioButton
an accurate conversion?
(3) In the CheckedStateTracker which extends/implements a Listener.. It has access to the RadioGridGroup fields like mProtectFromCheckedChange in the Java example, but in the C# example, those variables are inaccessable there. How do I solve this?
(4) For the listeners I had them implement Java.Lang.Object as well so I don't need to implement the Dispose side CheckedStateTracker implements CompoundButton.OnCheckedChangeListener
became 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;
}
}
}
}
UPDATE:
Below is my code which includes passing the enclosing parent to the listeners, and using those to compare the parent as well as to access the parent's fields. I've also added readonly to just the one location where it didn't error out to do so.
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;
}
}
}
}
Upvotes: 1
Views: 187
Reputation: 1679
To answer your multiple questions:
parent
instance whereas in the second case you are doing the type comparison of the parent
instance.As per this SO post , in C#, the nested class do not hold reference of the enclosing class. So it is basically not possible to do something like RadioGridGroup.this
in C#, because even though RadioGridGroup
encloses the class where RadiGridGroup.this
field is being accessed, the reference to that is not available in the enclosed class. Hence, you cannot refer to any private member of the this
instance of the RadioGridGroup
class. The solution to this is simple. Just treat RadioGridGroup
as any other class inside PassThroughHierarchyChangeListener
and pass a reference to the this
instance of the RadioGridGroup
to PassThroughHierarchyChangeListener
constructor. So your code would change to something like
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;
}
}
Finally, you instantiate your mChildOnCheckedChangeListener
as the following
//Line 51
mChildOnCheckedChangeListener = new CheckedStateTracker(this);
Upvotes: 1