Собственные контролы в андроиде бывают нескольких видов:
- Расширение существующего контрола. Например, при написании собственного
ToggleButton
имеет смысл наследоваться отButton
. - Составные компоненты. Например, контрол
NumberPicker
можно представить, как комбинацию двух кнопок (+ и −) иTextView
, в котором выводится текущее значение. При разработке подобных контролов можно наследоваться от всяческих Layout-ов, а расположение контролов задавать либо в коде, либо в XML-разметке. - Полностью настраиваемые компоненты. Когда ни один из существующих контролов не обладает нужной фунцкциональностью, мы реализуем всю логику и отрисовку компонента сами.
- Наследуемся от
View
. - Реализуем метод
onMeasure
, в котором выделяем место под компонент. - Реализуем метод
onDraw
, осуществляющий отрисовку компонента. - Ну и конечно, по ходу пишем логику работы компонента, добавляем листенеры и т.д.
VerticalProgressBar
VerticalProgressBar.java
public class VerticalProgressBar extends View { private static final int WIDTH = 20; private static final int REMAIN = Color.rgb(49, 49, 49); private static final int PROCEED = Color.rgb(22, 72, 237); private static final int FINISHED = Color.rgb(124, 209, 15); private int mHeight; private int mWidth; private int mProgress; private int mMax; public VerticalProgressBar(Context context) { this(context, null); } public VerticalProgressBar(Context context, AttributeSet attrs) { super(context, attrs); } public synchronized void incrementProgressBy(int delta) { this.setProgress(mProgress + delta); } public synchronized void setProgress(int progress) { if (progress < 0) { progress = 0; } if (progress > mMax) { progress = mMax; } if (progress != mProgress) { mProgress = progress; refreshProgress(); } } public synchronized void setMax(int max) { this.mMax = max; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setColor(REMAIN); paint.setStyle(Style.FILL); if (mProgress == 0) { canvas.drawRect(0, 0, this.mWidth, this.mHeight, paint); } else if (mProgress >= mMax) { paint.setColor(FINISHED); paint.setStyle(Style.FILL); canvas.drawRect(0, 0, this.mWidth, this.mHeight, paint); } else { float proceedHeight = ((float) mProgress / mMax) * (float) this.mHeight; canvas.drawRect(0, proceedHeight, this.mWidth, this.mHeight, paint); paint.setColor(PROCEED); paint.setStyle(Style.FILL); canvas.drawRect(0, 0, this.mWidth, proceedHeight, paint); } paint.setColor(Color.BLACK); paint.setStyle(Style.FILL); canvas.drawLine(0, this.mHeight, this.mWidth, this.mHeight, paint); canvas.drawLine(0, 0, this.mWidth, 0, paint); } @Override protected void onMeasure(int widthSpecId, int heightSpecId) { this.mHeight = View.MeasureSpec.getSize(heightSpecId); this.mWidth = WIDTH; setMeasuredDimension(this.mWidth, this.mHeight); } private synchronized void refreshProgress() { invalidate(); } }
А вот и пример работы:
А можно немного поменять способ отрисовки, и получится вот что:
Код этого класса я представлять не буду, желающие могут посмотреть исходники примера.
прикольно.
ОтветитьУдалитьвозможно есть способ проще - перегрузить ProgressBar.onDraw примерно так:
canvas.setMatrix(rotate270matrix);
super(canvas)
Оригинально, мне такое даже в голову не пришло.
ОтветитьУдалитьПравда, моей задачей была именно "зебра", а ProgressBar так вроде не умеет.
Спасибо за очередную отличную статью!
ОтветитьУдалитьА не подскажете ли, никак не соображу:
- есть TextView
- нужно, чтобы в нём отображалось текущее время (например, 14:20) (системное как бы)
Как это реализовать?
На самом деле умеет, для этого нужно ему скармливать специальные xml'ки (например с ClipDrawable). Другое дело что документации по этому я не нашел, но в исходниках это видно.
ОтветитьУдалитьjeck_landin и darja
ОтветитьУдалитьУже несколько раз читал, что видно в исходниках. А где эти исходники лежат?!
Документация часто написана только для простых случаев. Как только становится сложнее, документации на это не найти.
2 Мур Вотема:
ОтветитьУдалитьhttp://android.git.kernel.org/
Там много всякого интересного. А контролы лежат в platform/frameworks/base.git и дальше в /core/java/android/ и т.д.
Познавательно! Спасибо!
ОтветитьУдалитьБудет ли обзор создания собственного листнера событий?)
Создал свой в отдельном потоке - сильно грузит процессор.
Соглашусь с jeck_landin. Делал вертикальный прогресс. Переопределял стандартный progressBar, рисование. Если нужно переопределение жестов изменения прогресса тоже не сложная задача.
ОтветитьУдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьЗдравствуйте, Дарья.
ОтветитьУдалитьУ меня в проге будет пара десятков кнопочек, немного отличающихся друг от друга.
Я затеял MyButton -> Button, прописав все базовые свойства в конструкторах, вот так
...
Resources _res = getResources();
setBackgroundResource(R.drawable.button_selector);
setHeight((int)_res.getDimension(R.dimen.button_height));
ColorStateList _csl = _res.getColorStateList(R.drawable.color_selector);
setTextColor(_csl);
,
предполагая, что впоследствии в XML смогу кое-какие изменять, типа
com.example.picafama.MyButton
android:id="@+id/button7"
android:height="80dp"
android:text="@string/digit7"
android:textColor="@android:color/black"
Однако, свойства не меняются... В частности, android:height и android:textColor остаются такими, какие назначены у класса.
Подскажите, плз. реализуема ли такая вроде рядовая техника кастомизации объектов класса? Где и в чем тонкости?
PS. В XML-сниппете я убрал теги у com.example.picafama.MyButton, а то парсер блога резал весь код...
С уважением. Валерий
Я так понимаю, конструктор выглядит как-то так:
ОтветитьУдалитьpublic MyButton(Context context, AttributeSet attrs)
{
super(context, attrs);
...
Resources _res = getResources();
setBackgroundResource(R.drawable.button_selector);
setHeight((int)_res.getDimension(R.dimen.button_height));
ColorStateList _csl = _res.getColorStateList(R.drawable.color_selector);
setTextColor(_csl);
}
Тогда всё правильно. При вызове конструктора базового класса выставились значения атрибутов из XML, а потом перезаписались захардкоженными значениями (setHeight, setTextColor и пр.). Если нужны значения из XML — уберите эти вызовы. Если нужны захардкоженные значения — всё и так работает. Если нужно, чтобы захардкоженные значения были значениями по умолчанию — придётся городить огород с атрибутами, как рассказано здесь
угу... порыл исходники, убедился, что так и есть.
ОтветитьУдалитьДо этого, по поведению штатных контролов, а-ля Button, TextView я предполагал, что первична инициализация в конструкторе, а разметка применяется потом. И что просто я что-то не так делаю...
Исходники же как раз и содержат танцы с атрибутами...