Why seekbar tick marker shown in front of thumb?

It seems the problem is the AppCompatSeekBar widget calls super, which draws the thumb, and then draws the ticks over it.

Here is a Kotlin class that fixes the issue by redrawing the thumb over the canvas (which, at this point has the thumb and then the ticks drawn over it:

class SeekBar @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = android.R.attr.seekBarStyle
) : AppCompatSeekBar(context, attrs, defStyleAttr) {
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        drawThumb(canvas)
    }

    private fun drawThumb(canvas: Canvas) {
        if (thumb != null) {
            val saveCount = canvas.save()
            canvas.translate((paddingLeft - thumbOffset).toFloat(), paddingTop.toFloat())
            thumb.draw(canvas)
            canvas.restoreToCount(saveCount)
        }
    }
}

Note: this solution might leave some artifacts because the thumb is drawn twice. A workaround might be to define a secondary thumb and use that while hiding the original thumb.


This is a bug of AppCompatSeekBar. I resolved this problem with a custom Class that extends AppCompatSeekBar:

public class CustomSeekBar extends AppCompatSeekBar {

    private Drawable mTickMark;

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

    public CustomSeekBar(Context context, AttributeSet attrs) {
        this(context, attrs, android.support.v7.appcompat.R.attr.seekBarStyle);
    }

    public CustomSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        applyAttributes(attrs, defStyleAttr);
    }

    private void applyAttributes(AttributeSet rawAttrs, int defStyleAttr)
    {
        TypedArray attrs = getContext().obtainStyledAttributes(rawAttrs, R.styleable.CustomSeekBar, defStyleAttr, 0);
        try {
            mTickMark = attrs.getDrawable(R.styleable.CustomSeekBar_tickMarkFixed);
        } finally {
            attrs.recycle();
        }
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawTickMarks(canvas);
    }

    @Override
    public int getThumbOffset() {
        return super.getThumbOffset();
    }

    void drawTickMarks(Canvas canvas) {
        if (mTickMark != null) {
            final int count = getMax();
            if (count > 1) {
                final int w = mTickMark.getIntrinsicWidth();
                final int h = mTickMark.getIntrinsicHeight();
                final int halfThumbW = getThumb().getIntrinsicWidth() / 2;
                final int halfW = w >= 0 ? w / 2 : 1;
                final int halfH = h >= 0 ? h / 2 : 1;
                mTickMark.setBounds(-halfW, -halfH, halfW, halfH);
                final float spacing = (getWidth() - getPaddingLeft() - getPaddingRight() + getThumbOffset() * 2 - halfThumbW * 2) / (float) count;
                final int saveCount = canvas.save();
                canvas.translate(getPaddingLeft() - getThumbOffset() + halfThumbW, getHeight() / 2);
                for (int i = 0; i <= count; i++) {
                    if(i!=getProgress())
                        mTickMark.draw(canvas);
                    canvas.translate(spacing, 0);
                }
                canvas.restoreToCount(saveCount);
            }
        }
    }
}

with the attrs.xml:

<resources>
    <declare-styleable name="CustomSeekBar">
        <attr name="tickMarkFixed" format="reference"/>
    </declare-styleable>
</resources>

and in layout you must use tickMarkFixed instead of tickMark.