Search code examples
androidandroid-layoutradio-buttonandroid-custom-view

How to use a custom layout as RadioButton label


I made a custom layout that I want to implement for a RadioButton. The code for the android class is here :

public class MyRadioButton extends LinearLayout implements View.OnClickListener{
    private ImageView iv;
    private TextView tv;
    private RadioButton rb;

    private View view;

    public MyRadioButton(Context context) {
        super(context);
        view = View.inflate(context, R.layout.my_radio_button, this);
        setOrientation(HORIZONTAL);

        rb = (RadioButton) view.findViewById(R.id.radioButton1);
        tv = (TextView) view.findViewById(R.id.textView1);
        iv = (ImageView) view.findViewById(R.id.imageView1);

        view.setOnClickListener(this);
        rb.setOnCheckedChangeListener(null);
    }

    public void setImageBitmap(Bitmap bitmap) {
        iv.setImageBitmap(bitmap);
    }

    public View getView() {
        return view;
    }

    @Override
    public void onClick(View v) {

        boolean nextState = !rb.isChecked();

        LinearLayout lGroup = (LinearLayout)view.getParent();
        if(lGroup != null){
            int child = lGroup.getChildCount();
            for(int i=0; i<child; i++){
                //uncheck all
                ((RadioButton)lGroup.getChildAt(i).findViewById(R.id.radioButton1)).setChecked(false);
            }
        }

        rb.setChecked(nextState);
    }

    public void setImage(Bitmap b){
        iv.setImageBitmap(b);
    }

    public void setText(String text){
        tv.setText(text);
    }

    public void setChecked(boolean isChecked){
        rb.setChecked(isChecked);
    }
}

And the code for layout is here

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:orientation="horizontal" >

    <RadioButton
        android:id="@+id/radioButton1"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="top"
        android:text="" />

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Medium Text"
            android:textAppearance="?android:attr/textAppearanceMedium" />

        <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/wow_visa_prepaid" />

    </LinearLayout>
</LinearLayout>

At this moment, I can't figure out how to change the inheritance from LinearLayout to RadioButton and to keep the same layout.

This should like but with rounded corners this is almost perfect


Solution

  • There are two ways to do the job:

    1. When we hear about a custom view, it drives us to override onDraw method then drawing what we want into the view's Canvas.

    2. In this case, there is a simpler approach, using drawableLeft. Here I've extended AppCompatRadioButton and set the considered layout as the drawableLeft.


    MyRadioButton.java

    package com.aminography.radiobutton;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.drawable.BitmapDrawable;
    import android.util.AttributeSet;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.widget.ImageView;
    import android.widget.TextView;
    
    import com.bumptech.glide.Glide;
    import com.bumptech.glide.load.DataSource;
    import com.bumptech.glide.load.MultiTransformation;
    import com.bumptech.glide.load.engine.GlideException;
    import com.bumptech.glide.load.resource.bitmap.CenterCrop;
    import com.bumptech.glide.request.RequestListener;
    import com.bumptech.glide.request.RequestOptions;
    import com.bumptech.glide.request.target.Target;
    
    import jp.wasabeef.glide.transformations.MaskTransformation;
    
    // TODO: If you are using androidx
    import androidx.annotation.Nullable;
    import androidx.appcompat.widget.AppCompatRadioButton;
    
    // TODO: If you are using appcompat
    //import android.support.annotation.Nullable;
    //import android.support.v7.widget.AppCompatRadioButton;
    
    public class MyRadioButton extends AppCompatRadioButton {
    
        private View view;
        private TextView textView;
        private ImageView imageView;
    
        public MyRadioButton(Context context) {
            super(context);
            init(context);
        }
    
        public MyRadioButton(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }
    
        public MyRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context);
        }
    
       private RequestListener<Bitmap> requestListener = new RequestListener<Bitmap>() {
            @Override
            public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
                return false;
            }
    
            @Override
            public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
                imageView.setImageBitmap(resource);
                redrawLayout();
                return false;
            }
        };
    
        public void setImageResource(int resId) {
            Glide.with(getContext())
                    .asBitmap()
                    .load(resId)
                    .apply(RequestOptions.bitmapTransform(
                            new MultiTransformation<>(
                                    new CenterCrop(),
                                    new RoundedCornersTransformation(dp2px(getContext(), 24), 0, RoundedCornersTransformation.CornerType.ALL))
                            )
                    )
                    .listener(requestListener)
                    .submit();
        }
    
        public void setImageBitmap(Bitmap bitmap) {
            Glide.with(getContext())
                    .asBitmap()
                    .load(bitmap)
                    .apply(RequestOptions.bitmapTransform(
                            new MultiTransformation<>(
                                    new CenterCrop(),
                                    new RoundedCornersTransformation(dp2px(getContext(), 24), 0, RoundedCornersTransformation.CornerType.ALL))
                            )
                    )
                    .listener(requestListener)
                    .submit();
        }
    
        // setText is a final method in ancestor, so we must take another name.
        public void setTextWith(int resId) {
            textView.setText(resId);
            redrawLayout();
        }
    
        public void setTextWith(CharSequence text) {
            textView.setText(text);
            redrawLayout();
        }
    
        private void init(Context context) {
            view = LayoutInflater.from(context).inflate(R.layout.my_radio_button_content, null);
            textView = view.findViewById(R.id.textView);
            imageView = view.findViewById(R.id.imageView);
            redrawLayout();
        }
    
        private void redrawLayout() {
            view.setDrawingCacheEnabled(true);
            view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
            view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
    
            view.buildDrawingCache(true);
            Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
            setCompoundDrawablesWithIntrinsicBounds(new BitmapDrawable(getResources(), bitmap), null, null, null);
            view.setDrawingCacheEnabled(false);
        }
    
        private int dp2px(Context context, int dp) {
            return (int) (dp * context.getResources().getDisplayMetrics().density);
        }
    
    }
    

    my_radio_button_content.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="vertical">
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Medium Text"
            android:textAppearance="?android:attr/textAppearanceMedium" />
    
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="96dp"
            android:layout_height="64dp"
            android:src="@drawable/img_visa" />
    
    </LinearLayout>
    


    Visual Result:

    enter image description here


    Note:

    1. If you're using appcompat in the project, do comment the androidx import at the top of class and uncomment appcompat one.

    2. You can change the position of the custom layout simply by changing android:paddingLeft for your RadioButton:

    <com.aminography.radiobutton.MyRadioButton
        android:id="@+id/radioButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="8dp" />
    



    Edited:

    I've rewritten the code to address the requirement of rounded corners image using Glide and Glide-Transformations.

    build.gradle

    dependencies {
        implementation 'com.github.bumptech.glide:glide:4.9.0'
        implementation 'jp.wasabeef:glide-transformations:3.3.0'
    }