1919
2020import android .content .Context ;
2121import android .graphics .Canvas ;
22- import android .graphics .Color ;
2322import android .graphics .Paint ;
23+ import android .graphics .PorterDuff ;
24+ import android .graphics .PorterDuffXfermode ;
25+ import android .graphics .drawable .Drawable ;
2426import android .util .AttributeSet ;
25- import android .util .Log ;
2627
2728import androidx .annotation .NonNull ;
2829import androidx .annotation .Nullable ;
3334import java .util .List ;
3435
3536/**
36- * A {@link FocusAwareSeekBar} that draws thin white vertical tick marks at chapter boundaries.
37+ * A {@link FocusAwareSeekBar} that renders narrow transparent gaps at chapter boundaries,
38+ * giving the seekbar a segmented "chopped" appearance.
3739 * Call {@link #setChapters(List, long)} whenever a new stream loads.
3840 */
3941public final class ChaptersSeekBar extends FocusAwareSeekBar {
4042
41- private static final String TAG = "ChaptersSeekBar" ;
43+ private static final float GAP_WIDTH_DP = 2f ;
4244
43- private static final int TICK_ALPHA = 180 ; // ~70% opacity
44- private static final float TICK_WIDTH_DP = 2f ;
45- private static final float TICK_HEIGHT_FRACTION = 0.6f ; // fraction of view height
46-
47- private final Paint tickPaint = new Paint (Paint .ANTI_ALIAS_FLAG );
45+ private final Paint gapPaint = new Paint ();
4846
4947 @ NonNull private List <StreamSegment > chapters = Collections .emptyList ();
5048 private long durationSeconds = 0 ;
@@ -68,14 +66,12 @@ public ChaptersSeekBar(@NonNull final Context context,
6866 }
6967
7068 private void init () {
71- tickPaint .setColor (Color .WHITE );
72- tickPaint .setAlpha (TICK_ALPHA );
73- tickPaint .setStyle (Paint .Style .FILL );
74- Log .d (TAG , "init: ChaptersSeekBar created" );
69+ gapPaint .setStyle (Paint .Style .FILL );
70+ gapPaint .setXfermode (new PorterDuffXfermode (PorterDuff .Mode .CLEAR ));
7571 }
7672
7773 /**
78- * Stores chapter data for rendering tick marks .
74+ * Stores chapter data for rendering segment gaps .
7975 *
8076 * @param newChapters list of {@link StreamSegment}s; may be empty but never null
8177 * @param newDurationSecs total duration in seconds; used to compute fractional positions
@@ -84,64 +80,46 @@ public void setChapters(@NonNull final List<StreamSegment> newChapters,
8480 final long newDurationSecs ) {
8581 chapters = newChapters ;
8682 durationSeconds = newDurationSecs ;
87- Log .d (TAG , "setChapters: count=" + newChapters .size ()
88- + " durationSeconds=" + newDurationSecs );
89- for (final StreamSegment seg : newChapters ) {
90- Log .d (TAG , " chapter: startSec=" + seg .getStartTimeSeconds ()
91- + " title=" + seg .getTitle ());
92- }
9383 invalidate ();
9484 }
9585
9686 @ Override
9787 protected void onDraw (@ NonNull final Canvas canvas ) {
98- super .onDraw (canvas );
99-
10088 if (chapters .isEmpty () || durationSeconds <= 0 ) {
101- Log .d (TAG , "onDraw: skipped — chapters=" + chapters .size ()
102- + " durationSeconds=" + durationSeconds );
89+ super .onDraw (canvas );
10390 return ;
10491 }
10592
106- final float density = getResources ().getDisplayMetrics ().density ;
107- final float tickWidthPx = TICK_WIDTH_DP * density ;
108-
109- // Track bounds: AbsSeekBar pads the track by getPaddingLeft/getPaddingRight
110- final int paddingLeft = getPaddingLeft ();
111- final int paddingRight = getPaddingRight ();
112- final float trackWidth = getWidth () - paddingLeft - paddingRight ;
113-
114- Log .d (TAG , "onDraw: w=" + getWidth () + " h=" + getHeight ()
115- + " paddingL=" + paddingLeft + " paddingR=" + paddingRight
116- + " trackWidth=" + trackWidth + " chapters=" + chapters .size ()
117- + " durationSeconds=" + durationSeconds );
93+ // Draw the seekbar into an offscreen layer so CLEAR mode can punch transparent gaps
94+ final int sc = canvas .saveLayer (null , null );
95+ super .onDraw (canvas );
11896
119- if (trackWidth <= 0 ) {
120- Log .d (TAG , "onDraw: trackWidth<=0, skipping" );
121- return ;
97+ final float density = getResources ().getDisplayMetrics ().density ;
98+ final float gapHalfWidth = (GAP_WIDTH_DP * density ) / 2f ;
99+ final int paddingLeft = getPaddingLeft ();
100+ final float trackWidth = getWidth () - paddingLeft - getPaddingRight ();
101+
102+ if (trackWidth > 0 ) {
103+ for (final StreamSegment seg : chapters ) {
104+ final int startSec = seg .getStartTimeSeconds ();
105+ // Skip the very first position and anything at or past the end
106+ if (startSec <= 0 || startSec >= durationSeconds ) {
107+ continue ;
108+ }
109+ final float x = paddingLeft + (startSec / (float ) durationSeconds ) * trackWidth ;
110+ canvas .drawRect (x - gapHalfWidth , 0 , x + gapHalfWidth , getHeight (), gapPaint );
111+ }
122112 }
123113
124- // Center ticks vertically, scaling height as a fraction of the view
125- final float tickHeight = getHeight () * TICK_HEIGHT_FRACTION ;
126- final float tickTop = (getHeight () - tickHeight ) / 2f ;
127- final float tickBottom = tickTop + tickHeight ;
128-
129- for (final StreamSegment seg : chapters ) {
130- final int startSec = seg .getStartTimeSeconds ();
131- // Skip the very beginning and anything at or past the end
132- if (startSec <= 0 || startSec >= durationSeconds ) {
133- Log .d (TAG , " skipping seg startSec=" + startSec );
134- continue ;
135- }
136- final float x = paddingLeft + (startSec / (float ) durationSeconds ) * trackWidth ;
137- Log .d (TAG , " drawing tick at x=" + x + " for startSec=" + startSec
138- + " title=" + seg .getTitle ());
139- canvas .drawRect (
140- x - tickWidthPx / 2f ,
141- tickTop ,
142- x + tickWidthPx / 2f ,
143- tickBottom ,
144- tickPaint );
114+ canvas .restoreToCount (sc );
115+
116+ // Redraw the thumb on top so it visually overlaps the gaps
117+ final Drawable thumb = getThumb ();
118+ if (thumb != null ) {
119+ final int thumbSave = canvas .save ();
120+ canvas .translate (getPaddingLeft () - getThumbOffset (), getPaddingTop ());
121+ thumb .draw (canvas );
122+ canvas .restoreToCount (thumbSave );
145123 }
146124 }
147125}
0 commit comments