Android universal indicatorIndicatorView

Question: Need an indicator that supports images or color values
Solution: The following effect
 <com.core.ex.widget.IndicatorView
            android:id="@ + id/indicator_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|center_horizontal"
            android:layout_marginBottom="20dp"
            app:indicator_item_height="5dp"
            app:indicator_item_space="10dp"
            app:indicator_item_width="40dp"
            app:selected_color="@color/cl_red"
            app:unSelected_color="@color/cl_32a0fa" />

 <com.core.ex.widget.IndicatorView
            android:id="@ + id/indicator_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginBottom="20dp"
            app:indicator_item_height="30dp"
            android:orientation="vertical"
            app:indicator_item_space="10dp"
            app:indicator_item_width="5dp"
            app:selected_color="@color/cl_red"
            app:unSelected_color="@color/cl_32a0fa" />

<com.core.ex.widget.IndicatorView
    android:id="@ + id/indicator_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout_marginBottom="20dp"
    android:orientation="horizontal"
    app:indicator_item_height="40dp"
    app:indicator_item_space="10dp"
    app:indicator_item_width="40dp"
    app:selected_image="@drawable/icon_main_home"
    app:unSelected_image="@drawable/icon_main_list" />

package com.core.ex.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;

import com.core.ex.LogHelps;
import com.core.ex.R;
import com.core.ex.util.BitmapUtils;

/**
 * @Author chentao 0000668668
 * @Time 2023/5/8
 * @Description indicator display
 *Origin display: width and height can be equal
 * <p>-------------------------------------------------- ----------------------------
 * Circle--the bottom is displayed horizontally and centered:
 * android:layout_gravity="bottom|center_horizontal"
 * android:layout_marginBottom="20dp"
 * app:indicator_item_height="5dp"
 * app:indicator_item_space="5dp"
 * app:indicator_item_width="5dp"
 * app:selected_color="@color/cl_red"
 * app:unSelected_color="@color/cl_32a0fa"
 * android:orientation="horizontal"
 * <p>-------------------------------------------------- ----------------------------
 * The bottom of the rounded rectangle is displayed horizontally and centered:
 * android:layout_gravity="bottom|center_horizontal"
 * android:layout_marginBottom="20dp"
 * app:indicator_item_height="5dp"
 * app:indicator_item_space="10dp"
 * app:indicator_item_width="40dp"
 * app:selected_color="@color/cl_red"
 * app:unSelected_color="@color/cl_32a0fa"
 * android:orientation="horizontal"
 * <p>-------------------------------------------------- ----------------------------
 * Circular vertical right center display:
 * android:layout_gravity="right|center_vertical"
 * android:layout_marginRight="20dp"
 * app:indicator_item_height="5dp"
 * app:indicator_item_space="5dp"
 * app:indicator_item_width="5dp"
 * app:selected_color="@color/cl_red"
 * app:unSelected_color="@color/cl_32a0fa"
 * android:orientation="vertical"
 * <p>-------------------------------------------------- ----------------------------
 * The rounded rectangle is displayed vertically and centered on the right side:
 * android:layout_gravity="right|center_vertical"
 * android:layout_marginBottom="20dp"
 * app:indicator_item_height="5dp"
 * app:indicator_item_space="10dp"
 * app:indicator_item_width="40dp"
 * app:selected_color="@color/cl_red"
 * app:unSelected_color="@color/cl_32a0fa"
 * android:orientation="vertical"
 * <p>---------------Show pictures as above--------------------------------- ----------------------------------
 * app:selected_color="@color/cl_red"
 * app:unSelected_color="@color/cl_32a0fa"
 * Replaced with:
 * app:selected_image="@drawable/icon_main_home"
 * app:unSelected_image="@drawable/icon_main_list"
 */
public class IndicatorView extends View {
    private Paint mPaint;

    private int mOrientation = LinearLayout.HORIZONTAL;
    private int currentIndex, totalIndex = 1;
    private int centreX, startX;

    private int mItemSpace = 10; // Spacing between items
    private int mItemWidth = 40; // itme width
    private int mItemHeight = 5; // item height

    private int mItemSelectedColor, mItemUnselectedColor;

    private Bitmap mItemSelectedBitmap, mItemUnselectedBitmap;

    public IndicatorView(Context context) {
        this(context, null);
    }

    public IndicatorView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomViewAttr, defStyleAttr, 0);
        mOrientation = typedArray.getInt(R.styleable.CustomViewAttr_android_orientation, mOrientation);

        mItemHeight = (int) typedArray.getDimension(R.styleable.CustomViewAttr_indicator_item_height, mItemHeight);
        mItemWidth = (int) typedArray.getDimension(R.styleable.CustomViewAttr_indicator_item_width, mItemWidth);
        mItemSpace = (int) typedArray.getDimension(R.styleable.CustomViewAttr_indicator_item_space, mItemSpace);

        BitmapDrawable drawable = (BitmapDrawable) typedArray.getDrawable(R.styleable.CustomViewAttr_selected_image);
        if (drawable != null) {
            //Set the image width and height to the item width and height to prevent the image from being too large and not fully displayed.
            mItemSelectedBitmap = drawable.getBitmap();
            mItemSelectedBitmap = BitmapUtils.alterBitmapSize(mItemSelectedBitmap, mItemHeight, mItemWidth);
        }
        drawable = (BitmapDrawable) typedArray.getDrawable(R.styleable.CustomViewAttr_unSelected_image);
        if (drawable != null) {
            //Set the image width and height to the item width and height to prevent the image from being too large and not fully displayed.
            mItemUnselectedBitmap = drawable.getBitmap();
            mItemUnselectedBitmap = BitmapUtils.alterBitmapSize(mItemUnselectedBitmap, mItemHeight, mItemWidth);
        }
        LogHelps.i("mItemSelectedBitmap:" + mItemSelectedBitmap, "mItemUnselectedBitmap:" + mItemUnselectedBitmap);

        mItemSelectedColor = typedArray.getColor(R.styleable.CustomViewAttr_selected_color, Color.LTGRAY);
        mItemUnselectedColor = typedArray.getColor(R.styleable.CustomViewAttr_unSelected_color, Color.WHITE);
        typedArray.recycle();

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }

    private int measureHeight(int measureSpec) {
        int result;
        int desired = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (mOrientation) {
            case LinearLayout.HORIZONTAL:
                desired = mItemHeight + getPaddingBottom() + getPaddingTop();
                break;
            case LinearLayout.VERTICAL:
                desired = (totalIndex - 1) * mItemSpace + (totalIndex) * mItemHeight + getPaddingBottom() + getPaddingTop();
                break;
        }
        if (specMode == MeasureSpec.EXACTLY) {
            result = Math.max(desired, specSize);
        } else {
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(desired, specSize);
            } else result = desired;
        }
        return result;
    }

    private int measureWidth(int measureSpec) {
        int result;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        int desired = 0;
        switch (mOrientation) {
            case LinearLayout.HORIZONTAL:
                desired = (totalIndex - 1) * mItemSpace + (totalIndex) * mItemWidth + getPaddingLeft() + getPaddingRight();
                break;
            case LinearLayout.VERTICAL:
                desired = mItemWidth + getPaddingLeft() + getPaddingRight();
                break;
        }
        if (specMode == MeasureSpec.EXACTLY) {
            result = Math.max(desired, specSize);
        } else {
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(desired, specSize);
            } else result = desired;
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        switch (mOrientation) {
            case LinearLayout.HORIZONTAL:
                centreX = getWidth() / 2;
                startX = centreX - ((totalIndex - 1) * mItemSpace + totalIndex * mItemWidth) / 2;
                for (int i = 0; i < totalIndex; i + + ) {
                    if (mItemSelectedBitmap != null & amp; & amp; mItemUnselectedBitmap != null) {
                        // Prioritize picture drawing
                        canvas.drawBitmap((i == currentIndex) ? mItemSelectedBitmap : mItemUnselectedBitmap, startX, 0, mPaint);
                    } else {
                        mPaint.setColor((i == currentIndex) ? mItemSelectedColor : mItemUnselectedColor);
                        canvas.drawRoundRect(new RectF(startX, 0, startX + mItemWidth, mItemHeight), mItemHeight, mItemHeight, mPaint);
                    }
                    startX + = (mItemSpace + mItemWidth);
                }
                break;
            case LinearLayout.VERTICAL:
                centreX = getHeight() / 2;
                startX = centreX - ((totalIndex - 1) * mItemSpace + totalIndex * mItemHeight) / 2;
                for (int i = 0; i < totalIndex; i + + ) {
                    if (mItemSelectedBitmap != null & amp; & amp; mItemUnselectedBitmap != null) {
                        // Prioritize picture drawing
                        canvas.drawBitmap((i == currentIndex) ? mItemSelectedBitmap : mItemUnselectedBitmap, 0, startX, mPaint);
                    } else {
                        mPaint.setColor((i == currentIndex) ? mItemSelectedColor : mItemUnselectedColor);
                        canvas.drawRoundRect(new RectF(0, startX, mItemWidth, startX + mItemHeight), mItemWidth, mItemWidth, mPaint);
                    }
                    startX + = (mItemSpace + mItemHeight);
                }
                break;
        }
    }

    public void setCurrentIndex(int currentIndex) {
        if (this.currentIndex == currentIndex) {
            return;
        }
        this.currentIndex = currentIndex;
        requestLayout();
    }

    public int getCurrentIndex() {
        return currentIndex;
    }

    public void setTotalIndex(int totalIndex) {
        if (this.totalIndex == totalIndex) {
            return;
        }

        int oldTotalIndex = this.totalIndex;
        if (totalIndex < 1) return;
        if (totalIndex < oldTotalIndex) {
            if (currentIndex == totalIndex) currentIndex = totalIndex - 1;
        }
        this.totalIndex = totalIndex;

        LogHelps.iLow("totalIndex=" + totalIndex);
        post(() -> requestLayout());
    }

    public int getTotalIndex() {
        return totalIndex;
    }
}
<attr name="selected_image" format="reference" /> <!-- Selected image -->
<attr name="unSelected_image" format="reference" /> <!-- Unselected image -->
<attr name="selected_color" format="color" /> <!-- Font, etc. selected color -->
<attr name="unSelected_color" format="color" /> <!-- Unselected colors such as fonts -->
<attr name="indicator_item_height" format="dimension" /> <!-- Indicator item height -->
<attr name="indicator_item_width" format="dimension" /> <!-- Indicator item width-->
<attr name="indicator_item_space" format="dimension" /> <!-- The spacing between indicator items-->
<attr name="android:orientation" />