Drawing a custom SeekBar on Android with Canvas

- Andrés Cruz

En español
Drawing a custom SeekBar on Android with Canvas

Download

In this post we will see how to create a simple SeekBar using the Canvas API in Android; we will make use of the Telegram project that you can find in the resources section of this website, and with this it would be the second entry that we dedicate to analyzing certain sections of the Telegram application:

How to make an animated presentation activity in Android with ViewPager

The SeekBar to be made is really simple, it only consists of a sidebar (a flattened rectangle) and a circumference that will act as a control and is the one that we will manipulate by clicking or "gesturing" to move it from right to left and vice versa.

Defining the bases: the structure of the activity

First we must define the structure of our activity, which is what will support the SeekBar drawn by Canvas in another class; in the layout of our activity we do very little; we simply define an empty layout like the following:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/cons"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   tools:context="com.presentacion.desarrollolibre.audioplayer.MainActivity">
</LinearLayout>

We already took care of the activity of creating a FrameLayout with fixed dimensions of 300 x 300 using the following Java code:

SeekBarView seekBarView = new SeekBarView(MainActivity.this);
FrameLayout.LayoutParams myFrameLayoutParams = new FrameLayout.LayoutParams(300,300);
seekBarView.setLayoutParams(myFrameLayoutParams);
setContentView(seekBarView);

The SeekBarView class to draw the SeekBar using Canvas

As we will see in the experiment, the SeekBarView class extends the FrameLayout class and therefore must override certain methods of this class; in the constructor method of the class we take care of creating a Paint type object that allows us to draw geometric shapes (our rectangle and circle) and draw it inside a Canvas; we also define two variables with a fixed size that will serve us for another purpose:

thumbWidth = dp(24);
thumbHeight = dp(24);

These variables are in charge of defining the position of the rectangle that we draw with the onDraw() method of this SeekBarView class; the onDraw() method that we override is responsible for creating the canvas/canvas and drawing the geometric figures (rectangle and circle) on our canvas/canvas:

canvas.drawRect(thumbWidth / 2, getMeasuredHeight() / 2 - dp(1), thumbWidth / 2 + thumbX, getMeasuredHeight() / 2 + dp(1), outerPaint1);

The getMeasuredHeight() method returns the length of the container that we defined in the activity, which for this experiment is 300.

The getMeasuredWidth() method returns the width of the container that we defined in the activity, which for this experiment is 300.

In addition to a bar that we draw with the drawRect function above, we draw a circle with the help of the drawCircle primitive.

canvas.drawCircle(thumbX + thumbWidth / 2, y + thumbHeight / 2, dp(pressed ? 8 : 6), outerPaint1);

Finally, an onTouch method is created that is executed to move the circle according to the user's touch on the Canvas (a kind of maintained onClick event). The onTouch method is quite interesting since the calculations are made to relocate the ball according to the update of the thumbX variable which is updated according to the position of the user's click on the SeekBar; the onTouch method is responsible for automatically calling the onDraw method and in this way the ball is redrawed according to the updated position:

boolean onTouch(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        getParent().requestDisallowInterceptTouchEvent(true);

        int additionWidth = (getMeasuredHeight() - thumbWidth) / 2;
        if (thumbX - additionWidth <= ev.getX() && ev.getX() <= thumbX + thumbWidth + additionWidth && ev.getY() >= 0 && ev.getY() <= getMeasuredHeight()) {
            pressed = true;
            thumbDX = (int) (ev.getX() - thumbX);
            invalidate();
            return true;
        }
    } else if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
        if (pressed) {
            if (ev.getAction() == MotionEvent.ACTION_UP) {
//                        onSeekBarDrag((float) thumbX / (float) (getMeasuredWidth() - thumbWidth));
            }
            pressed = false;
            invalidate();
            return true;
        }
    } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
        if (pressed) {
            thumbX = (int) (ev.getX() - thumbDX);
            if (thumbX < 0) {
                thumbX = 0;
            } else if (thumbX > getMeasuredWidth() - thumbWidth) {
                thumbX = getMeasuredWidth() - thumbWidth;
            }
            invalidate();
            return true;
        }
    }
    return false;
}

In the above onTouch method there are several events for; we have the ACTION_DOWN constant that is executed when the person's "gesture" or click is started on the Canvas and the ACTION_UP constant that is executed when the person's "gesture" or click is finished on the Canvas.

As a result of these states, the thumbX variable is updated, which is what "moves" or allows the ball to be redrawed using the onDraw() method.

In the same way you can find the source code on github at the beginning and end of this entry:

Download

Andrés Cruz

Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz In Udemy

I agree to receive announcements of interest about this Blog.