A RadioButton with zoom animation

The effect is shown in figure

How do I use it?

Step 1.Add it in your root build.gradle at the end of repositories:

allprojects {
	repositories {
		...
		maven { url 'https://jitpack.io'}}}Copy the code

Step 2. Add the dependency

dependencies {
         implementation 'com. Making. Qingyc: FixedAnimatedRadioButton: 0.1'
}
Copy the code

1. Dealing with the problem of the lower version of RadioButton

Use of RadioButton in layout


    <RadioGroup
        android:id="@+id/rg_main"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@drawable/bg_main_bottom"
        android:gravity="bottom"
        android:orientation="horizontal"
        android:paddingTop="4dp"
        android:paddingBottom="4dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/main_viewpager">

        <com.qingyc.fixedanimatedradiobutton.FixedAnimatedRadioButton
            android:id="@+id/rb_capricorn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:checked="true"
            android:drawableTop="@drawable/main_rb_01"
            android:gravity="center"
            android:text="@string/capricorn"
            android:textColor="@color/radio_btn_text_color"
            android:textSize="12sp"/>


        <com.qingyc.fixedanimatedradiobutton.FixedAnimatedRadioButton
            android:id="@+id/rb_compatibility"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:checked="false"
            android:drawableTop="@drawable/main_rb_02"
            android:gravity="center"
            android:text="@string/compatibility"
            android:textColor="@color/radio_btn_text_color"
            android:textSize="12sp"/>


        <com.qingyc.fixedanimatedradiobutton.FixedAnimatedRadioButton
            android:id="@+id/rb_personality"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:checked="false"
            android:drawableTop="@drawable/main_rb_03"
            android:gravity="center"
            android:text="@string/personality"
            android:textColor="@color/radio_btn_text_color"
            android:textSize="12sp"/>

        <com.qingyc.fixedanimatedradiobutton.FixedAnimatedRadioButton
            android:id="@+id/rb_discover"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:button="@null"
            android:checked="false"
            android:drawableTop="@drawable/main_rb_04"
            android:gravity="center"
            android:text="@string/discover"
            android:textColor="@color/radio_btn_text_color"
            android:textSize="12sp"/>


    </RadioGroup>
Copy the code

RadioButton issues on earlier versions (Android 4.4)

You can see that on the lower emulator a default button icon is displayed to the left of radioButton. Android: Button =”@null” is invalid in XML

It works fine on older emulators and phones

The Settings for RadioButton’s Button icon are implemented in the CompoundButton class

Android Api 28 source code

    public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.CompoundButton, defStyleAttr, defStyleRes);

        final Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);
        if(d ! = null) {setButtonDrawable(d);
        }

        if (a.hasValue(R.styleable.CompoundButton_buttonTintMode)) {
            mButtonTintMode = Drawable.parseTintMode(a.getInt(
                    R.styleable.CompoundButton_buttonTintMode, -1), mButtonTintMode);
            mHasButtonTintMode = true;
        }

        if (a.hasValue(R.styleable.CompoundButton_buttonTint)) {
            mButtonTintList = a.getColorStateList(R.styleable.CompoundButton_buttonTint);
            mHasButtonTint = true;
        }

        final boolean checked = a.getBoolean(
                com.android.internal.R.styleable.CompoundButton_checked, false);
        setChecked(checked);
        mCheckedFromResource = true;

        a.recycle();

        applyButtonTint();
    }

Copy the code

a.getDrawable(com.android.internal.R.styleable.CompoundButton_button); Gets the button drawable set in XML, which is the mButtonDrawable private member inside CompoundButton

You can see that the following two methods end up being called


    /**
     * Sets a drawable as the compound button image given its resource
     * identifier.
     *
     * @param resId the resource identifier of the drawable
     * @attr ref android.R.styleable#CompoundButton_button
     */
    public void setButtonDrawable(@DrawableRes int resId) {
        final Drawable d;
        if(resId ! = 0) { d = getContext().getDrawable(resId); }else {
            d = null;
        }
        setButtonDrawable(d);
    }

    /**
     * Sets a drawable as the compound button image.
     *
     * @param drawable the drawable to set
     * @attr ref android.R.styleable#CompoundButton_button
     */
    public void setButtonDrawable(@Nullable Drawable drawable) {
        if(mButtonDrawable ! = drawable) {if(mButtonDrawable ! = null) { mButtonDrawable.setCallback(null); unscheduleDrawable(mButtonDrawable); } mButtonDrawable = drawableif(drawable ! = null) { drawable.setCallback(this); drawable.setLayoutDirection(getLayoutDirection());if (drawable.isStateful()) {
                    drawable.setState(getDrawableState());
                }
                drawable.setVisible(getVisibility() == VISIBLE, false);
                setMinHeight(drawable.getIntrinsicHeight()); applyButtonTint(); }}}Copy the code

So you can see that whatever RadioButton is set up in XML or code ends up calling setButtonDrawable(@nullable Drawable Drawable)

Compare the Android API 19 source code

Click to view


    public void setButtonDrawable(Drawable d) {
        if(d ! =null) {
            if(mButtonDrawable ! =null) {
                mButtonDrawable.setCallback(null);
                unscheduleDrawable(mButtonDrawable);
            }
            d.setCallback(this);
            d.setVisible(getVisibility() == VISIBLE, false);
            mButtonDrawable = d;
            setMinHeight(mButtonDrawable.getIntrinsicHeight());
        }

        refreshDrawableState();
    }
Copy the code

You can see that mButtonDrawable = drawable is not called at all when drawable is null and mButtonDrawable is null

Problem processing

  override fun setButtonDrawable(buttonDrawable: Drawable?) {// QTIP: 2019-04-28 fix for lower versions (android4.4) setting button to null to display default buttonif (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
                 try {
                     val clazz = CompoundButton::class.java
                     val field = clazz.getDeclaredField("mButtonDrawable")
                     field.isAccessible = true
                     field.set(this, null)
                 } catch (e: Exception) {
                     e.printStackTrace()
                 }
             } else {
                 super.setButtonDrawable(buttonDrawable)
             }
  }
Copy the code

Click RadioButton to zoom animation

Call Time When the check status changes


    override fun setChecked(checked: Boolean) {
        super.setChecked(checked)
        if (width == 0 || height == 0) {
            return
        }
        if (checked) {
            val animator = ValueAnimator.ofFloat(minScaleRate, maxScaleRate)
            animator.addUpdateListener(this)
            animator.duration = 300
            animator.start()
        } else {
            if(animatedScaleRate ! = 1f) { val animator = ValueAnimator.ofFloat(animatedScaleRate, 1f) animator.addUpdateListener(this) animator.duration = 0 animator.start() } } }Copy the code

RadioButton selected animation implementation

override fun onAnimationUpdate(animation: ValueAnimator?) { animatedScaleRate = animation? .animatedValue as Float try { //1. Save the default icon locationif(mDefaultDrawableBounds == null) { mDefaultDrawableBounds = arrayOfNulls(4) compoundDrawables.forEachIndexed { index, drawable -> drawable? .let { val rect = Rect(drawable.bounds) mDefaultDrawableBounds? .set(index, rect) } } } var leftDrawable: Drawable? = null var rightDrawable: Drawable? = null var topDrawable: Drawable? = null var bottomDrawable: Drawable? = null / / 2. Get the Settings icon in the radioButton drawable compoundDrawables. ForEachIndexed {index, drawable - > drawable? .let { mDefaultDrawableBounds? .get(index)? .let { mDefaultDrawableBounds -> val copyBounds = Rect(mDefaultDrawableBounds) //3. Dynamic scaling RadioButton icon copyBounds. Let {copyBounds. Left = mDefaultDrawableBounds. Left copyBounds. Right = mDefaultDrawableBounds.right - (mDefaultDrawableBounds.width() * (1 - animatedScaleRate)).toInt() copyBounds.top = mDefaultDrawableBounds.top + (mDefaultDrawableBounds.height() * (1 - animatedScaleRate)).toInt() / 2 copyBounds.bottom =  mDefaultDrawableBounds.bottom - (mDefaultDrawableBounds.height() * (1 - animatedScaleRate)).toInt() / 2 when (index) { 0 -> { leftDrawable = drawable leftDrawable? .bounds = copyBounds } 1 -> { topDrawable = drawable topDrawable? .bounds = copyBounds } 2 -> { rightDrawable = drawable rightDrawable? .bounds = copyBounds } 3 -> { bottomDrawable = drawable bottomDrawable? .bounds = copyBounds } } } } } } //4. Update icon size and positionsetCompoundDrawables(leftDrawable, topDrawable, rightDrawable, bottomDrawable)

        } catch (e: Exception) {
        }
    }
Copy the code

Making the source code