Custom SeekBar — пишем свой компонент для Android

Понадобилось мне тут сделать сик-бар (SeekBar) с растровым бэкграундом и бегунком, отображающим текущий прогресс. Аккуратно потыкав гугл я пришел к выводу, что задать в растровое изображение в качестве ползунка прогрессбара в принципе возможно, однако саму полоску растровой сделать не получится. О том, чтобы вывести стандартными средствами какойнить текст на ползунке прогрессбара и речи быть не может. Но надо — значит надо. И я решил пойти обходным путем.

Выбранный мной обходной путь предполагает создание собственного контрола на основе сик-бара, в котором мы полностью кастомизируем отрисовку. Итак, что мы имеем?

Три вот такие волшебные картинки:

1:

2:

3: 

Первая картинка есть ни что иное, как рамка для прогрессбара, вторая картинка — заполнитель прогресса, третья картинка — бегунок.

Итак, идем в eclipse и создаем новый проект. Добавляем в папку drawables все наши три изображения и создаем новый класс RasterSB.

Собственно сразу приведу его код, а потом разберемся, что тут к чему:

package ru.davidmd.tutorial.custompb;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.SeekBar;

public class RasterSB extends SeekBar {

	Bitmap top,filler,slider;
	Paint bmpPaint=new Paint (), textPaint = new Paint();

	private void init(Context c)
	{
		// Инициализация
		top = BitmapFactory.decodeResource(c.getResources(), R.drawable.top_pb_img);
		filler = BitmapFactory.decodeResource(c.getResources(), R.drawable.progress);
		slider = BitmapFactory.decodeResource(c.getResources(), R.drawable.slider);
		if (!this.isInEditMode()){
		textPaint.setShadowLayer(2, 0, 0, Color.WHITE);
		}
		textPaint.setColor(Color.BLUE);
		textPaint.setTextSize(10);
	}

	public RasterSB(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		this.init(context);
	}
	public RasterSB(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.init(context);
	}
	public RasterSB(Context context) {
		super(context);
		this.init(context);
	}

	@Override
	public void onDraw(Canvas canvas)
	{
		// Получаем текущую ширину и высоту прогрессбара
		int w=this.getWidth();
		int h=this.getHeight();
		// Получаем значения для масштабирования битмапов
		double sfx = (double)w/top.getWidth();
		double sfy = (double)h/top.getHeight();
		// Масштабаируем заполнитель
		Bitmap TMP = Bitmap.createScaledBitmap(filler, (int)(filler.getWidth()*sfx), (int)(filler.getHeight()*sfy), true);
		// Вычисляем где у нас должен находиться конец
		// заполняющей колбасы 🙂
		int pos = (w/this.getMax())*this.getProgress();
		// И рисуем саму колбасу
		for (int i =(int)(5*sfx); i<pos; i+=TMP.getWidth() )
		{
			canvas.drawBitmap(TMP, i,0, bmpPaint);
		}
		// Масштабируем рамку
		TMP = Bitmap.createScaledBitmap(top, (int)(top.getWidth()*sfx), (int)(top.getHeight()*sfy), true);
		// Поверх колбасы рисуем рамку
		canvas.drawBitmap(TMP, 0,0, bmpPaint);
		// масштабируем ползунок
		TMP = Bitmap.createScaledBitmap(slider, (int)(slider.getWidth()*sfx), (int)(slider.getHeight()*sfy), true);
		// Создаем ноую канву из ползунка
		Canvas c = new Canvas(TMP);
		// Вычисляем размер текста с текущим прогрессом
		Rect r = new Rect(0,0,0,0);
		if (!this.isInEditMode()){
		textPaint.getTextBounds(""+this.getProgress(), 0, (""+this.getProgress()).length(), r);
		}
		// Рисуем текст на канве ползунка
		c.drawText(""+this.getProgress(),c.getWidth()/2-r.width()/2,c.getHeight()/2+r.height()/2,textPaint);
		// А затем рисуем сам ползунок
		canvas.drawBitmap(TMP, pos-TMP.getWidth()/2,0, bmpPaint);
	}

}

В наш класс мы добавил три поля типа Bitmap. Это будут изображения которые я привел выше. Первым делом в этом классе мы реализовали все конструкторы. В каждом конструкторе мы вызвали некий метод init(). Что же он делает? Да ничего особенного. Просто загружает из ресурсов изображения и задает прараметры для кисти, которой мы будем рисовать текст на ползунке.

А самое главное — это конечно переопределенный метод onDraw(). Этот метод получает в качестве параметра канву, на которой должен отрисовываться элемент интерфейса, а всю отрисовку мы возьмем на себя! Итак, начинаем с того что вычисляем на сколько надо изменить размер наших изображений, чтобы они заняли все место отводимое компоненту. Получаем масштабные множители sfx и sfy. Идем дальше, считаем сколько примерно раз надо отрисовать заполнитель. Затем отрисовываем его.  Поверх него отрисовываем рамку, ну а на ней рисуем сам бегунок, предварительно написав на нем текущий прогресс.

Собственно на этом на сегодня все. Надеюсь кому-то это будет полезно, потому как сходного примера в инете я не нашел :(. Если увидели ошибки или очепятки пишите в комментах, уже 4 часа утра, и я слишком хочу спать, чтоб это все проверять. Если есть вопросы тоже пишите.

Вам понравилось? Было полезно? Поделитесь!

Опубликовать в Facebook
Опубликовать в Google Buzz
Опубликовать в Google Plus
Опубликовать в LiveJournal
Опубликовать в Мой Мир
Опубликовать в Одноклассники
Опубликовать в Яндекс
Запись опубликована в рубрике Android, Программирование с метками , , , , , , . Добавьте в закладки постоянную ссылку.

15 комментариев на «Custom SeekBar — пишем свой компонент для Android»

  1. Николай говорит:

    DavidMD спасибо за Ваши старания. Расскажите про волшебное @Override простыми словами, тем более если нагуглить то видно, что очень много новичков в ступоре от него 😉 !!!

    • Михаил говорит:

      Метод помеченный @Override будет переопределен в текущем классе.

      Пример: Имеется некий класс который имеет метод например draw(). При создании класса который расширяет этот класс, наследуются все его методы в том числе и метод draw(), но допустим метод draw(), в таком виде в каком он есть, нам не подходит.. и что бы изменить его мы переопределяем этот метод командой @Override. Для того что бы дополнить метод, и не писать его с нуля можно использовать super.draw() в переопределенном методе, тогда он выполнить все что должен и можно добавить еще пару строк.

      Вроде все так надеюсь нигде не наврал)

      • slash говорит:

        Перегрузка оператора??? Как в C#?????

        • davidmd говорит:

          Да перегрузка, но не оператора а метода. Операторы в java нельзя перегружать. А перегрузка методов есть практически во всех современных объектно ориентированных языках

          • davidmd говорит:

            Оппа. Тутта у меня ошибочка. Я ж комменты в отрыве от контекста просматриваю… Тут имелась в виду не перегрузка методов а их переопределение. Сорри что ввел кого-то в заблуждение.

      • kirill говорит:

        чучуть соврал 🙂
        Аннотация @Override лижь информирует о том что переопределяется метод родителя. Можете собрать под code style 1.5, убрать аннотацию и результат будет тот же. Это пришло с 1.6.

        Эта аннотация предназначена для того, чтобы в случае когда родительный класс изменится, а точнее переопределяемый метод (название, параметры), то при компиляции выдаст ошибку.

  2. Игорь говорит:

    Скажите пожалуйста, как мне добавить в свой компонент свойства (properties)?
    Я пробовал добавить переменные в классе, объявленные как public, таким образом я могу менять значения свойств в конструкторе Activity или еще где-то в коде, но как мне прописывать свойства в XML? и вообще, правильно ли я делаю?

    • davidmd говорит:

      Я не понял честно говоря какие properties. 🙁

    • QuickNick говорит:

      В ApiDemos есть примерчик — в /res/values/attrs.xml перечисляются — это и есть объявление атрибутов.
      Использование их в конструкторе вьюхи с двумя параметрами (Context, AttributeSet) можно посмотреть в той же демке — com.example.android.apis.view.LabelView

  3. Вячеслав говорит:

    Хорошо написано!
    Вот только нет самого способа вставки компонента на Layout..
    Может где-то можно скачать полноценный исходник, а то у меня всё, что получилось это java.lang.NullPointerException 🙂
    Если не сложно, расскажите ещё на примере Button, как сменить\перерисовать внешний вид стандартной кнопки на произвольный растр.

  4. Александр говорит:

    А как его подцепить?

  5. shalom говорит:

    А можно дать объяснение к методу createScaledBitmap, и к тому что в скобках…
    непонятно что по чём и зачем это там ???

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *