class RadioGroupAll : ConstraintLayout {
constructor(context: Context) : super(context) {
mContext = context
init()
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
mContext = context
init()
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
mContext = context
init()
}
@TargetApi(21)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
mContext = context
init()
}
private val LOG_TAG = RadioGroup::class.java.simpleName
// holds the checked id; the selection is empty by default
private var mCheckedId = -1
// tracks children radio buttons checked state
private var mChildOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
// when true, mOnCheckedChangeListener discards events
private var mProtectFromCheckedChange = false
private var mOnCheckedChangeListener: OnCheckedChangeListener? = null
private var mPassThroughListener: PassThroughHierarchyChangeListener? = null
// Indicates whether the child was set from resources or dynamically, so it can be used
// to sanitize autofill requests.
private var mInitialCheckedId = View.NO_ID
private var mContext: Context? = null
private fun init() {
mChildOnCheckedChangeListener = CheckedStateTracker()
mPassThroughListener = PassThroughHierarchyChangeListener()
super.setOnHierarchyChangeListener(mPassThroughListener)
}
/**
* {@inheritDoc}
*/
override fun setOnHierarchyChangeListener(listener: OnHierarchyChangeListener?) {
// the user listener is delegated to our pass-through listener
mPassThroughListener!!.mOnHierarchyChangeListener = listener
}
/**
* {@inheritDoc}
*/
override fun onFinishInflate() {
super.onFinishInflate()
// checks the appropriate radio button as requested in the XML file
if (mCheckedId != -1) {
mProtectFromCheckedChange = true
setCheckedStateForView(mCheckedId, true)
mProtectFromCheckedChange = false
setCheckedId(mCheckedId)
}
}
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
if (child is RadioButton) {
val button = child
if (button.isChecked) {
mProtectFromCheckedChange = true
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false)
}
mProtectFromCheckedChange = false
setCheckedId(button.id)
}
}
super.addView(child, index, params)
}
/**
*
* Sets the selection to the radio button whose identifier is passed in
* parameter. Using -1 as the selection identifier clears the selection;
* such an operation is equivalent to invoking [.clearCheck].
*
* @param id the unique id of the radio button to select in this group
*
* @see .getCheckedRadioButtonId
* @see .clearCheck
*/
fun check(@IdRes id: Int) {
// don't even bother
if (id != -1 && id == mCheckedId) {
return
}
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false)
}
if (id != -1) {
setCheckedStateForView(id, true)
}
setCheckedId(id)
}
@SuppressLint("NewApi")
private fun setCheckedId(@IdRes id: Int) {
val changed = id != mCheckedId
mCheckedId = id
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener!!.onCheckedChanged(this, mCheckedId)
}
if (changed) {
val afm: AutofillManager? = mContext?.getSystemService(AutofillManager::class.java)
afm?.notifyValueChanged(this)
}
}
private fun setCheckedStateForView(viewId: Int, checked: Boolean) {
val checkedView = findViewById<View>(viewId)
if (checkedView != null && checkedView is RadioButton) {
checkedView.isChecked = checked
}
}
/**
*
* Returns the identifier of the selected radio button in this group.
* Upon empty selection, the returned value is -1.
*
* @return the unique id of the selected radio button in this group
*
* @see .check
* @see .clearCheck
* @attr ref android.R.styleable#RadioGroup_checkedButton
*/
@IdRes
fun getCheckedRadioButtonId(): Int {
return mCheckedId
}
/**
*
* Clears the selection. When the selection is cleared, no radio button
* in this group is selected and [.getCheckedRadioButtonId] returns
* null.
*
* @see .check
* @see .getCheckedRadioButtonId
*/
fun clearCheck() {
check(-1)
}
/**
*
* Register a callback to be invoked when the checked radio button
* changes in this group.
*
* @param listener the callback to call on checked state change
*/
fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) {
mOnCheckedChangeListener = listener
}
override fun getAccessibilityClassName(): CharSequence? {
return RadioGroup::class.java.name
}
/**
*
* Interface definition for a callback to be invoked when the checked
* radio button changed in this group.
*/
interface OnCheckedChangeListener {
/**
*
* Called when the checked radio button has changed. When the
* selection is cleared, checkedId is -1.
*
* @param group the group in which the checked radio button has changed
* @param checkedId the unique identifier of the newly checked radio button
*/
fun onCheckedChanged(group: RadioGroupAll?, @IdRes checkedId: Int)
}
private inner class CheckedStateTracker : CompoundButton.OnCheckedChangeListener {
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
// prevents from infinite recursion
if (mProtectFromCheckedChange) {
return
}
mProtectFromCheckedChange = true
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false)
}
mProtectFromCheckedChange = false
val id = buttonView.id
setCheckedId(id)
}
}
/**
*
* A pass-through listener acts upon the events and dispatches them
* to another listener. This allows the table layout to set its own internal
* hierarchy change listener without preventing the user to setup his.
*/
private inner class PassThroughHierarchyChangeListener : OnHierarchyChangeListener {
var mOnHierarchyChangeListener: OnHierarchyChangeListener? = null
/**
* {@inheritDoc}
*/
override fun onChildViewAdded(parent: View, child: View) {
if (parent is RadioGroupAll && child is RadioButton) {
var id = child.getId()
// generates an id if it's missing
if (id == View.NO_ID) {
id = View.generateViewId()
child.setId(id)
}
child.setOnCheckedChangeListener(mChildOnCheckedChangeListener)
}
mOnHierarchyChangeListener?.onChildViewAdded(parent, child)
}
/**
* {@inheritDoc}
*/
override fun onChildViewRemoved(parent: View, child: View) {
if (parent is RadioGroupAll && child is RadioButton) {
child.setOnCheckedChangeListener(null)
}
mOnHierarchyChangeListener?.onChildViewRemoved(parent, child)
}
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onProvideAutofillStructure(structure: ViewStructure, flags: Int) {
super.onProvideAutofillStructure(structure, flags)
structure.setDataIsSensitive(mCheckedId != mInitialCheckedId)
}
@RequiresApi(Build.VERSION_CODES.O)
override fun autofill(value: AutofillValue) {
if (!isEnabled) return
if (!value.isList) {
Log.w(LOG_TAG, "$value could not be autofilled into $this")
return
}
val index = value.listValue
val child = getChildAt(index)
if (child == null) {
Log.w(View.VIEW_LOG_TAG, "RadioGroup.autoFill(): no child with index $index")
return
}
check(child.id)
}
override fun getAutofillType(): Int {
return if (isEnabled) View.AUTOFILL_TYPE_LIST else View.AUTOFILL_TYPE_NONE
}
@RequiresApi(Build.VERSION_CODES.O)
override fun getAutofillValue(): AutofillValue? {
if (!isEnabled) return null
val count = childCount
for (i in 0 until count) {
val child = getChildAt(i)
if (child.id == mCheckedId) {
return AutofillValue.forList(i)
}
}
return null
}
}