Понадобилось мне тут сделать сик-бар (SeekBar) с растровым бэкграундом и бегунком, отображающим текущий прогресс. Аккуратно потыкав гугл я пришел к выводу, что задать в растровое изображение в качестве ползунка прогрессбара в принципе возможно, однако саму полоску растровой сделать не получится. О том, чтобы вывести стандартными средствами какойнить текст на ползунке прогрессбара и речи быть не может. Но надо — значит надо. И я решил пойти обходным путем.
Выбранный мной обходной путь предполагает создание собственного контрола на основе сик-бара, в котором мы полностью кастомизируем отрисовку. Итак, что мы имеем?
Три вот такие волшебные картинки:
Первая картинка есть ни что иное, как рамка для прогрессбара, вторая картинка — заполнитель прогресса, третья картинка — бегунок.
Итак, идем в 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 часа утра, и я слишком хочу спать, чтоб это все проверять. Если есть вопросы тоже пишите.
DavidMD спасибо за Ваши старания. Расскажите про волшебное @Override простыми словами, тем более если нагуглить то видно, что очень много новичков в ступоре от него 😉 !!!
Метод помеченный @Override будет переопределен в текущем классе.
Пример: Имеется некий класс который имеет метод например draw(). При создании класса который расширяет этот класс, наследуются все его методы в том числе и метод draw(), но допустим метод draw(), в таком виде в каком он есть, нам не подходит.. и что бы изменить его мы переопределяем этот метод командой @Override. Для того что бы дополнить метод, и не писать его с нуля можно использовать super.draw() в переопределенном методе, тогда он выполнить все что должен и можно добавить еще пару строк.
Вроде все так надеюсь нигде не наврал)
Перегрузка оператора??? Как в C#?????
Да перегрузка, но не оператора а метода. Операторы в java нельзя перегружать. А перегрузка методов есть практически во всех современных объектно ориентированных языках
Оппа. Тутта у меня ошибочка. Я ж комменты в отрыве от контекста просматриваю… Тут имелась в виду не перегрузка методов а их переопределение. Сорри что ввел кого-то в заблуждение.
чучуть соврал 🙂
Аннотация @Override лижь информирует о том что переопределяется метод родителя. Можете собрать под code style 1.5, убрать аннотацию и результат будет тот же. Это пришло с 1.6.
Эта аннотация предназначена для того, чтобы в случае когда родительный класс изменится, а точнее переопределяемый метод (название, параметры), то при компиляции выдаст ошибку.
Скажите пожалуйста, как мне добавить в свой компонент свойства (properties)?
Я пробовал добавить переменные в классе, объявленные как public, таким образом я могу менять значения свойств в конструкторе Activity или еще где-то в коде, но как мне прописывать свойства в XML? и вообще, правильно ли я делаю?
Я не понял честно говоря какие properties. 🙁
В ApiDemos есть примерчик — в /res/values/attrs.xml перечисляются — это и есть объявление атрибутов.
Использование их в конструкторе вьюхи с двумя параметрами (Context, AttributeSet) можно посмотреть в той же демке — com.example.android.apis.view.LabelView
А, вот про какие атрибуты. Понятно 🙂
Хорошо написано!
Вот только нет самого способа вставки компонента на Layout..
Может где-то можно скачать полноценный исходник, а то у меня всё, что получилось это java.lang.NullPointerException 🙂
Если не сложно, расскажите ещё на примере Button, как сменить\перерисовать внешний вид стандартной кнопки на произвольный растр.
Ок. Постараюсь. Занесу в todo лист.
А как его подцепить?
Не понял, кого подцепить? 🙂
А можно дать объяснение к методу createScaledBitmap, и к тому что в скобках…
непонятно что по чём и зачем это там ???