Kotlin实现RadioFlexboxGroup组件

时间:2023-01-18 05:30:12

如果使用过RadioGroup组件都是在,这个有很大的局限性,基本上设计师稿子一出来你就知道要自定义才可以实现的这些效果。
如果使用FlexBoxLayout就知道,这个东西还是挺好用了,如果还没使用过,不妨参考http://www.oschina.net/news/73442/google-flexbox-layout
这里我结合RadioGroup实现的单选效果加上FlexBoxLayout,简直太好用了,而且不需要写任何多余的代码,方便又好用,好了,废话不多说,先上效果图
Kotlin实现RadioFlexboxGroup组件
当然我这里只列了基本效果,当然如果想要其他效果,可以充分发挥FlexBoxLayout布局优势
下边是布局文件xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.example.myproject.component.RadioFlexboxGroup
android:id="@+id/rg_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:justifyContent="space_around"
app:flexWrap="wrap">

<RadioButton
android:id="@+id/radio_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="不限" />

<RadioButton
android:id="@+id/radio_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="精选项目" />

<RadioButton
android:id="@+id/radio_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="转让项目" />

</com.example.myproject.component.RadioFlexboxGroup>

<com.example.myproject.component.RadioFlexboxGroup
android:id="@+id/rg
_2"
android:layout_width="
match_parent"
android:layout_height="
wrap_content"
android:layout_marginLeft="
16dp"
android:layout_marginTop="
16dp"
app:flexDirection="
column"
app:justifyContent="
space_around"
app:layout_constraintLeft_toRightOf="
parent"
app:layout_constraintTop_toBottomOf="
@+id/rg_1">

<RadioButton
android:id="
@+id/radio_11"
android:layout_width="
wrap_content"
android:layout_height="
wrap_content"
android:textColor="
@color/radio_text_coloraccent_select"
android:text="
不限" />

<RadioButton
android:id="
@+id/radio_12"
android:layout_width="
wrap_content"
android:layout_height="
wrap_content"
android:textColor="
@color/radio_text_coloraccent_select"
android:text="
精选项目" />

<RadioButton
android:id="
@+id/radio_13"
android:layout_width="
wrap_content"
android:layout_height="
wrap_content"
android:textColor="
@color/radio_text_coloraccent_select"
android:checked="
true"
android:text="
转让项目" />


</com.example.myproject.component.RadioFlexboxGroup>


<com.example.myproject.component.RadioFlexboxGroup
android:id="
@+id/rg_3"
android:layout_width="
match_parent"
android:layout_height="
wrap_content"
android:layout_marginLeft="
32dp"
android:layout_marginRight="
32dp"
android:layout_marginTop="
16dp"
app:flexDirection="
row_reverse"
app:justifyContent="
space_between"
app:layout_constraintLeft_toRightOf="
parent"
app:layout_constraintRight_toRightOf="
parent"
app:layout_constraintTop_toBottomOf="
@+id/rg_2">

<RadioButton
android:id="
@+id/radio_22"
android:layout_width="
wrap_content"
android:layout_height="
wrap_content"
android:background="
@drawable/radio_select"
android:button="
@null"
android:paddingBottom="
4dp"
android:paddingLeft="
16dp"
android:paddingRight="
16dp"
android:paddingTop="
4dp"
android:textColor="
@color/radio_text_select"
android:text="
\t\t不限\t\t" />

<RadioButton
android:id="
@+id/radio_23"
android:layout_width="
wrap_content"
android:layout_height="
wrap_content"
android:background="
@drawable/radio_select"
android:button="
@null"
android:checked="
true"
android:paddingBottom="
4dp"
android:paddingLeft="
16dp"
android:paddingRight="
16dp"
android:paddingTop="
4dp"
android:textColor="
@color/radio_text_select"
android:text="
精选项目" />

<RadioButton
android:id="
@+id/radio_24"
android:layout_width="
wrap_content"
android:layout_height="
wrap_content"
android:background="
@drawable/radio_select"
android:button="
@null"
android:paddingBottom="
4dp"
android:paddingLeft="
16dp"
android:paddingRight="
16dp"
android:paddingTop="
4dp"
android:textColor="
@color/radio_text_select"
android:text="
转让项目" />


</com.example.myproject.component.RadioFlexboxGroup>


</android.support.constraint.ConstraintLayout>

Activity界面其实啥都没有:

class RadioFlexBoxGroupActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_radio_group)
}
}

最后就是组件的代码:

class RadioFlexboxGroup : FlexboxLayout {
/**
*
* 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
* *
* @attr ref android.R.styleable#RadioGroup_checkedButton
* *
* @see .check
* @see .clearCheck
*/
var mCheckedId = -1
var checkedRadioButtonId = -1
@IdRes
get() = mCheckedId
private set
/
/ 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

constructor(context: Context?) : super(context)

constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)

constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

init {
init()
}

private fun init() {
mChildOnCheckedChangeListener = CheckedStateTracker()
mPassThroughListener = PassThroughHierarchyChangeListener()
super.setOnHierarchyChangeListener(mPassThroughListener)
}

/
**
* {@inheritDoc}
*/
override fun setOnHierarchyChangeListener(listener: ViewGroup.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 .getMCheckedId
* @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)
}

private fun setCheckedId(@IdRes id: Int) {
mCheckedId = id
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener?.onCheckedChanged(this, mCheckedId)
}
}

private fun setCheckedStateForView(viewId: Int, checked: Boolean) {
val checkedView = findViewById<RadioButton>(viewId)
if (checkedView != null) {
checkedView.isChecked = checked
}
}

/
**
*
* Clears the selection. When the selection is cleared, no radio button
* in this group is selected and [.getMCheckedId] returns
* null.

* @see .check
* @see .getMCheckedId
*/
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
}

/
**
* {@inheritDoc}
*/
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return RadioFlexboxGroup.LayoutParams(context, attrs)
}

/
**
* {@inheritDoc}
*/
override fun checkLayoutParams(p: ViewGroup.LayoutParams?): Boolean {
return p is LayoutParams
}

override fun generateDefaultLayoutParams(): LayoutParams {
return LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}

override fun getAccessibilityClassName(): CharSequence {
return RadioFlexboxGroup::class.java.name
}

/
**
*
* This set of layout parameters defaults the width and the height of
* the children to [.WRAP_CONTENT] when they are not specified in the
* XML file. Otherwise, this class ussed the value read from the XML file.
*
*
*
* See
* for a list of all child view attributes that this class supports.
*/
class LayoutParams : FlexboxLayout.LayoutParams {
/
**
* {@inheritDoc}
*/
constructor(c: Context?, attrs: AttributeSet?) : super(c, attrs)

/
**
* {@inheritDoc}
*/
constructor(w: Int, h: Int) : super(w, h)

/
**
* {@inheritDoc}
*/
constructor(p: ViewGroup.LayoutParams?) : super(p)

/
**
* {@inheritDoc}
*/
constructor(source: ViewGroup.MarginLayoutParams?) : super(source)

/
**
*
* Fixes the child's width to
* [ViewGroup.LayoutParams.WRAP_CONTENT] and the child'
s
* height to [ViewGroup.LayoutParams.WRAP_CONTENT]
* when not specified in the XML file.

* @param a the styled attributes set
* *
* @param widthAttr the width attribute to fetch
* *
* @param heightAttr the height attribute to fetch
*/
override fun setBaseAttributes(a: TypedArray,
widthAttr: Int, heightAttr: Int) {

if (a.hasValue(widthAttr)) {
width = a.getLayoutDimension(widthAttr, "layout_width")
} else {
width = ViewGroup.LayoutParams.WRAP_CONTENT
}

if (a.hasValue(heightAttr)) {
height = a.getLayoutDimension(heightAttr, "layout_height")
} else {
height = ViewGroup.LayoutParams.WRAP_CONTENT
}
}
}

/
**
*
* 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: RadioFlexboxGroup, @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 : ViewGroup.OnHierarchyChangeListener {
var mOnHierarchyChangeListener: ViewGroup.OnHierarchyChangeListener? = null

/
**
* {@inheritDoc}
*/
override fun onChildViewAdded(parent: View, child: View) {
if (parent === this@RadioFlexboxGroup && child is RadioButton) {
var id = child.getId()
/
/ generates an id if it's missing
if (id == View.NO_ID) {
id = generateViewId()
child.setId(id)
}
child.setOnCheckedChangeListener(
mChildOnCheckedChangeListener)
}

mOnHierarchyChangeListener?.onChildViewAdded(parent, child)
}

/
**
* {@inheritDoc}
*/
override fun onChildViewRemoved(parent: View, child: View) {
if (parent === this@RadioFlexboxGroup && child is RadioButton) {
child.setOnCheckedChangeListener(null)
}

mOnHierarchyChangeListener?.onChildViewRemoved(parent, child)
}
}

companion object {

private val sNextGeneratedId = AtomicInteger(1)

fun generateViewId(): Int {
while (true) {
val result = sNextGeneratedId.get()
/
/ aapt-generated IDs have the high byte nonzero; clamp to the range under that.
var newValue = result + 1
if (newValue > 0x00FFFFFF) newValue = 1 /
/ Roll over to 1, not 0.
if (sNextGeneratedId.compareAndSet(result, newValue)) {
return result
}
}
}
}
}

其实我这里是继承FlexBoxLayout实现的效果,所有拥有FLexBoxLayout的所有属性效果,整体还是很简单的
github地址:https://github.com/ttarfall/RadioFlexboxGroup