If you like what I do and want to support me, you can
A customizable Android knob View.
The shape of the knob is a circle and consists of fill, track, progress, thumb, steps and substeps. The diameter of the circle is defined using knobSize
, while the angle of rotation is specified using progressStartAngle
(in degrees, with the angle 0 corresponding to 3 o'clock) and progressSweepAngle
(in degrees, representing the rotation arc starting from the start angle).
The arc on the circle defined by the size, start angle and sweep angle is the reference, conceptual, imaginary track (see debug_drawTrack
), in reference to which all other visual elements are positioned. This means that the visual track, progress, thumb, steps and substeps are positioned (centered) on this reference track according to their anchor points (see thumbAnchorX
, thumbAnchorY
, stepAnchorX
, stepAnchorY
, substepAnchorX
, substepAnchorY
), but can be offset (see trackStrokeOffset
, progressStrokeOffset
, thumbOffset
, stepOffset
, substepOffset
, touchOffset
) to the outside of the circle (using positive values), or to the inside of the circle (using negative values).
By default, the whole view reacts to touches, but that can be restricted using touchThreshold
and touchOffset
(see debug_drawTouchArea
)
Simply add the dependency to your module build.gradle
file
dependencies {
// ...
implementation 'com.codevblocks.android:knob:<version>'
// ...
}
Current version available on Maven
Include the com.codevblocks.android.knob.Knob
view in your XML layout file. Here is a complete example:
<com.codevblocks.android.knob.Knob
android:id="@+id/knob"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:knobSize="250dp"
app:fillColor="@android:color/transparent"
app:fillStartAngle="0"
app:fillSweepAngle="360"
app:trackStrokeColor="#18FFFFFF"
app:trackStrokeWidth="28dp"
app:trackStrokeCap="butt"
app:trackStrokeOffset="-14dp"
app:progressMode="continuous"
app:maxProgress="100"
app:progress="0"
app:progressStrokeColor="#FFEBBA64"
app:progressStrokeWidth="20dp"
app:progressStrokeCap="butt"
app:progressStrokeOffset="-14dp"
app:progressStartAngle="135"
app:progressSweepAngle="270"
app:thumbDrawable="@drawable/drawable_knob_thumb"
app:thumbAnchorX="50%"
app:thumbAnchorY="0%"
app:thumbOffset="-4dp"
app:thumbRotation="true"
app:stepCount="5"
app:stepDrawable="@drawable/drawable_knob_step"
app:stepAnchorX="50%"
app:stepAnchorY="0%"
app:stepOffset="-4dp"
app:substepCount="145"
app:substepDrawable="@drawable/drawable_knob_substep"
app:substepAnchorX="50%"
app:substepAnchorY="0%"
app:substepOffset="-4dp"
app:touchThreshold="48dp"
app:touchOffset="-16dp"
app:debug="false"
app:debug_drawBounds="false"
app:debug_drawTrack="false"
app:debug_drawTouchRadius="false"
app:debug_drawTouchArea="false"
android:background="@drawable/drawable_knob_background" />
drawable_knob_background.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="center">
<shape android:shape="oval">
<size android:width="250dp" android:height="250dp" />
<gradient android:startColor="#434756" android:endColor="#252936" android:type="linear" android:angle="270" />
</shape>
</item>
</layer-list>
drawable_knob_thumb.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<size android:width="4dp" android:height="36dp" />
<solid android:color="#F0F0F0" />
</shape>
drawable_knob_step.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<size android:width="1dp" android:height="32dp" />
<solid android:color="#A0A0A0" />
</shape>
drawable_knob_substep.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<size android:width="1dp" android:height="20dp" />
<gradient android:startColor="#A0A0A0" android:endColor="#00FFFFFF" android:angle="270"/>
</shape>
The diameter of the knob circle
app:knobSize="250dp"
The fill color of the knob arc or circle
app:fillColor="@android:color/transparent"
app:fillColor="#00000000"
Starting angle (in degrees) of the fill arc
app:fillStartAngle="0"
Sweep angle (in degrees) of the fill arc, measured clockwise. 360 is equivalent to a complete fill circle
app:fillSweepAngle="360"
The color of the track which is rendered as a stroked arc
app:trackStrokeColor="#18FFFFFF"
app:trackStrokeColor="@android:color/holo_red_dark"
The width of the track arc stroke. The stroke will be centered on the imaginary reference track (see debug_drawTrack
)
app:trackStrokeWidth="28dp"
Equivalent to StrokeCap applied to the track stroke
app:trackStrokeCap="butt|square|round"
The offset of the visual track stroke to the reference track (see debug_drawTrack
). Keep in mind this is affected by the stroke width because the offset takes into account the centerline of the stroke width.
app:trackStrokeOffset="-14dp"
The behaviour of the progress. continuous
will allow for a smooth progress along the track. step
will restrict progress in between steps. substep
will restrict progress in between substeps
app:progressMode="continuous|step|substep"
The maximum progress value
app:maxProgress="100"
The current value of the knob progress
app:progress="0"
The color of the progress which is rendered as a stroked arc
app:progressStrokeColor="#FFEBBA64"
The width of the progress arc stroke. The stroke will be centered on the imaginary reference track (see debug_drawTrack
)
app:progressStrokeWidth="20dp"
Equivalent to StrokeCap applied to the progress stroke
app:progressStrokeCap="butt|square|round"
The offset of the progress stroke to the reference track (see debug_drawTrack
). Keep in mind this is affected by the stroke width because the offset takes into account the centerline of the stroke width.
app:progressStrokeOffset="-14dp"
The start angle (in degrees) of the knob rotation (progress equals to 0)
app:progressStartAngle="135"
The knob rotation angle (in degrees) starting from progressStartAngle
.
app:progressSweepAngle="270"
Optional drawable for the thumb indicating the current progress. The position of the drawable relative to the reference track (see debug_drawTrack
) depends on thumbAnchorX
, thumbAnchorY
and thumbOffset
.
app:thumbDrawable="@drawable/drawable_knob_thumb"
app:thumbDrawable="@null"
The offset of the thumb drawable anchor point relative to the reference track (see debug_drawTrack
).
app:thumbOffset="-4dp"
The horizontal anchor point (X coordinate, left to right) within the thumb drawable relative to the reference track
app:thumbAnchorX="50%"
The vertical anchor point (Y coordinate, top to bottom) within the thumb drawable relative to the reference track
app:thumbAnchorY="0%"
Flag which determines if the thumb drawable will be rotated to be perpendicular to the reference track. Useful to obtain more realistic visual efects like shadows on circular thumb drawables. In this case, the drawable should not be rotated since the shadow should not vary with the progress.
app:thumbRotation="true|false"
The number of main progress steps. Ex: on a clock knob, there would be 12 steps. (see progressMode
)
app:stepCount="5"
Optional drawable for a step along the progress arc. The position of the drawable relative to the reference track (see debug_drawTrack
) depends on stepAnchorX
, stepAnchorY
and stepOffset
.
app:stepDrawable="@drawable/drawable_knob_step"
app:stepDrawable="@null"
The offset of the step drawable anchor point relative to the reference track (see debug_drawTrack
).
app:stepOffset="-4dp"
The horizontal anchor point (X coordinate, left to right) within the step drawable relative to the reference track
app:stepAnchorX="50%"
The vertical anchor point (Y coordinate, top to bottom) within the step drawable relative to the reference track
app:stepAnchorY="0%"
The number of secondary progress steps. Ex: on a clock knob, there would be 60 substeps. (see progressMode
))
app:substepCount="145"
Optional drawable for a substep along the progress arc. The position of the drawable relative to the reference track (see debug_drawTrack
) depends on substepAnchorX
, substepAnchorY
and substepOffset
.
app:substepDrawable="@drawable/drawable_knob_substep"
app:substepDrawable="@null"
The offset of the substep drawable anchor point relative to the reference track (see debug_drawTrack
).
app:substepOffset="-4dp"
The horizontal anchor point (X coordinate, left to right) within the substep drawable relative to the reference track
app:substepAnchorX="50%"
The vertical anchor point (Y coordinate, top to bottom) within the step drawable relative to the reference track
app:substepAnchorY="0%"
If specified with a value greater than 0, defines a touch treshold relative to the knob circle where touches are interpreted. Touches outside of this treshold will be ignored (see debug_drawTouchArea
).
app:touchThreshold="0dp"
app:touchThreshold="48dp"
Offset of the touch threshold centerline relative to the reference knob circle (see debug_drawTouchArea
). A positive value moves the threshold towards the outside of the knob circle, a negative value moves it towards the inner of the knob circle.
app:touchOffset="-16dp"
Enables debugging mode. This must be set to true
for any of the other debug flags to be taken into account.
Renders the bounds of the drawable area
Renders the reference track with a red stroke
Renders a red radius from the center of the knob circle to the touch point
Renders the touch area with a semitransparent green tint if touchThreshold
is greater than 0.
<com.codevblocks.android.knob.Knob
android:id="@+id/knob"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:knobSize="250dp"
app:fillColor="@android:color/transparent"
app:fillStartAngle="0"
app:fillSweepAngle="360"
app:trackStrokeColor="@android:color/transparent"
app:trackStrokeWidth="1dp"
app:trackStrokeCap="round"
app:trackStrokeOffset="0dp"
app:progressMode="step"
app:maxProgress="100"
app:progress="0"
app:progressStrokeColor="#21C0E0"
app:progressStrokeWidth="10dp"
app:progressStrokeCap="round"
app:progressStrokeOffset="16dp"
app:progressStartAngle="270"
app:progressSweepAngle="360"
app:thumbDrawable="@drawable/drawable_knob_thumb"
app:thumbOffset="-20dp"
app:thumbAnchorX="50%"
app:thumbAnchorY="0%"
app:thumbRotation="true"
app:stepCount="18"
app:stepDrawable="@drawable/drawable_knob_step"
app:stepOffset="16dp"
app:stepAnchorX="50%"
app:stepAnchorY="50%"
app:touchThreshold="64dp"
app:touchOffset="-16dp"
app:debug="false"
app:debug_drawBounds="false"
app:debug_drawTrack="false"
app:debug_drawTouchRadius="false"
app:debug_drawTouchArea="false"
android:background="@drawable/drawable_knob_background"
android:elevation="5dp" />
drawable_knob_background.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="center">
<shape android:shape="oval">
<size android:width="250dp" android:height="250dp" />
<gradient android:startColor="@android:color/white" android:endColor="#D0D0D0" android:type="linear" android:angle="225" />
</shape>
</item>
<item android:gravity="center">
<shape android:shape="oval">
<size android:width="230dp" android:height="230dp" />
<gradient android:startColor="@android:color/white" android:endColor="#D0D0D0" android:type="linear" android:angle="45" />
</shape>
</item>
</layer-list>
drawable_knob_step.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<size android:width="10dp" android:height="10dp" />
<solid android:color="#21C0E0" />
</shape>
<com.codevblocks.android.knob.Knob
android:id="@+id/knob"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:knobSize="250dp"
app:trackStrokeColor="#23979F"
app:trackStrokeWidth="8dp"
app:trackStrokeCap="round"
app:trackStrokeOffset="14dp"
app:progressMode="continuous"
app:maxProgress="100"
app:progressStrokeColor="#34FFFF"
app:progressStrokeWidth="8dp"
app:progressStrokeCap="round"
app:progressStrokeOffset="14dp"
app:progressStartAngle="135"
app:progressSweepAngle="270"
app:thumbDrawable="@drawable/drawable_knob_thumb"
app:thumbOffset="-16dp"
app:thumbAnchorX="50%"
app:thumbAnchorY="0%"
app:thumbRotation="false"
app:stepCount="5"
app:stepDrawable="@drawable/drawable_knob_step"
app:stepOffset="22dp"
app:stepAnchorX="50%"
app:stepAnchorY="100%"
app:touchThreshold="64dp"
app:touchOffset="-16dp"
android:background="@drawable/drawable_knob_background"
android:elevation="5dp" />
drawable_knob_background.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="center">
<shape android:shape="oval">
<size android:width="250dp" android:height="250dp" />
<gradient android:startColor="#434756" android:endColor="#252936" android:type="linear" android:angle="270" />
</shape>
</item>
</layer-list>
drawable_knob_thumb.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<size android:width="44dp" android:height="44dp" />
<gradient android:startColor="#434756" android:endColor="#292e3c" android:type="linear" android:angle="90" />
</shape>
drawable_knob_step.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<size android:width="2dp" android:height="24dp" />
<solid android:color="#34FFFF" />
</shape>
<com.codevblocks.android.knob.Knob
android:id="@+id/knob"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:knobSize="250dp"
app:trackStrokeColor="#9a234f"
app:trackStrokeWidth="12dp"
app:trackStrokeCap="round"
app:trackStrokeOffset="24dp"
app:progressMode="continuous"
app:maxProgress="100"
app:progressStrokeColor="#f31558"
app:progressStrokeWidth="8dp"
app:progressStrokeCap="round"
app:progressStrokeOffset="24dp"
app:progressStartAngle="180"
app:progressSweepAngle="180"
app:thumbDrawable="@drawable/drawable_knob_thumb"
app:thumbOffset="-25dp"
app:thumbRotation="false"
app:touchThreshold="64dp"
app:touchOffset="-16dp"
android:background="@drawable/drawable_knob_background"
android:elevation="5dp" />
drawable_knob_background.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="center">
<shape android:shape="oval">
<size android:width="250dp" android:height="250dp" />
<gradient android:startColor="@android:color/white" android:endColor="#D0D0D0" android:type="linear" android:angle="270" />
</shape>
</item>
<item android:gravity="center">
<shape android:shape="oval">
<size android:width="150dp" android:height="150dp" />
<gradient android:startColor="#EBEBEB" android:endColor="#E0E0E0" android:type="linear" android:angle="90" />
</shape>
</item>
<item android:gravity="center">
<shape android:shape="oval">
<size android:width="148dp" android:height="148dp" />
<gradient android:startColor="@android:color/white" android:endColor="#D0D0D0" android:type="radial" android:gradientRadius="146dp" />
</shape>
</item>
</layer-list>
drawable_knob_thumb.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="center">
<shape android:shape="oval">
<size android:width="18dp" android:height="18dp" />
<gradient android:startColor="#920d35" android:endColor="#f31558" android:type="linear" android:angle="270" />
</shape>
</item>
<item android:gravity="center">
<shape android:shape="oval">
<size android:width="14dp" android:height="14dp" />
<gradient android:startColor="#db134f" android:endColor="#f31558" android:type="radial" android:gradientRadius="7dp" />
</shape>
</item>
</layer-list>
<com.codevblocks.android.knob.Knob
android:id="@+id/knob"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:knobSize="250dp"
app:trackStrokeColor="#FDE7DF"
app:trackStrokeWidth="4dp"
app:trackStrokeCap="round"
app:progressMode="continuous"
app:maxProgress="100"
app:progressStrokeColor="#F3865F"
app:progressStrokeWidth="4dp"
app:progressStrokeCap="round"
app:progressStartAngle="105"
app:progressSweepAngle="330"
app:thumbDrawable="@drawable/drawable_knob_thumb"
app:touchThreshold="64dp" />
drawable_knob_thumb.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<size android:width="32dp" android:height="32dp" />
<solid android:color="@android:color/white" />
<stroke android:color="#F3865F" android:width="2dp" />
</shape>