Как создать свое представление
Типичным подходом к созданию своего представления является определе
-
ние класса, наследующего класс
View
,
V
iewGroup
или один из существующих их
подклассов, и добавление в него своих функциональных возможностей. Воз
-
можности пользовательского представления несколько ограничены – он мо
-
жет изменять уже нарисованное (текст, цвета, фигуры) или то, что ему под
-
чинено. Первое можно реализовать с помощью метода
onDraw
класса
View
. Этот
метод принимает один параметр – предварительно заполненный экземпляр
Canvas
, имеющий те же размеры, что и само представление. Более подробно
о доступных возможностях объекта
Canvas
можно узнать в документации для
разработчика, но если говорить кратко, здесь можно делать все, что угодно. Вы
можете вызывать методы для рисования, вывода текста и отображения гео
-
метрических фигур. Можно также использовать геометрические классы, такие
как
Rect
или
Path
, для отображения более сложных структур, и объекты
Paint
для
настройки цвета, заливки или текстуры.
Например, следующий простой подкласс
View
рисует красный круг (точнее,
овал), вписанный в границы представления:
Java
public class Oval extends View {
private Paint mPaint = new Paint();
{
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
}
public Oval(Context context) {
super(context);
}
public Oval(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Oval(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawOval(0, 0, getWidth(), getHeight(), mPaint);
}
}
62
Пользовательские компоненты
Kotlin
class Oval @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0, defStyleRes: Int = 0) : View(context, attrs, defStyleAttr,
defStyleRes) {
private val paint = Paint()
init {
paint.color = Color.RED
paint.style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
canvas.drawOval(0f, 0f, width.toFloat(), height.toFloat(), paint)
}
}
Как видите, есть возможность определить свои методы для настройки цвета
или добавить свое свойство
Paint
для вывода растрового изображения, созда
-
ния теней или градиентов.
Аналогично можно определить подкласс
TextView
, который всегда выводит
рамку вдоль нижней границы, например для использования в
LinearLayout
или
RecyclerView
:
Java
public class BottomBorderTextView extends TextView {
private Paint mPaint = new Paint();
{
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.BLACK);
}
public BottomBorderTextView(Context context) {
super(context);
}
public BottomBorderTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public BottomBorderTextView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
// не забывайте вызывать super.onDraw для вывода текста, фона, смежных
// элементов и пр.
super.onDraw(canvas);
canvas.drawLine(0, getHeight(), getWidth(), getHeight(), mPaint);
}
}
Android
63
Kotlin
class BottomBorderTextView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0, defStyleRes: Int = 0) :
TextView(context, attrs, defStyleAttr, defStyleRes) {
private val paint = Paint()
init {
paint.style = Paint.Style.FILL
paint.color = Color.RED
}
override fun onDraw(canvas: Canvas) {
// не забывайте вызывать super.onDraw для вывода текста, фона, смежных
// элементов и пр.
super.onDraw(canvas)
canvas.drawLine(0, height, width, height, paint)
}
}
Также можно добавить новый метод для изменения цвета рамки или ее пе
-
рерисовывания:
Java
public void setBorderColor(int color) {
mPaint.setColor(color);
// вызов invalidate уведомляет систему о необходимости перерисовать
// представление при отображении следующего кадра
invalidate();
}
Kotlin
fun setBorderColor(color: Int) {
paint.color = color
// вызов invalidate уведомляет систему о необходимости перерисовать
// представление при отображении следующего кадра
invalidate()
}
С другой стороны, пользовательская версия
ViewGroup
может реализовать но
-
вую стратегию размещения или включать набор дочерних экземпляров
View
и
ViewGroup
, образующих сложный компонент, такой как средство выбора даты
или медиапроигрыватель. Подобный подход к использованию
ViewGroup
обыч
-
но называют созданием пользовательского «компонента», он требует более
глубокого погружения. Мы затронем эту тему далее, но вы обязательно долж
-
ны ознакомиться с дополнительными подробностями в документации (
https://
oreil.ly/ugTKF
).
Суть этого подхода сводится к переопределению двух методов:
onMeasure
и
onLayout
.
onMeasure
сообщает родителю, сколько места
требует
пользователь
-
ский компонент. Иногда это все доступное пространство; иногда ровно столь
-
ко, сколько требуется для отображения содержимого.
64
Пользовательские компоненты
onMeasure
ожидает два параметра типа
int
:
widthMeasureSpec
и
heightMeasureSpec
.
Эти значения включают биты, определяющие «режим» (например, флаг
MATCH_
PARENT
, указывающий, что компоненту должно быть отведено все пространство
его родителя), а также размеры в пикселях.
Для чтения этих значений из параметров можно использовать методы
Mea
sureSpec.getMode
и
MeasureSpec.getSize
.
Второй метод –
onLayout
. Существуют разнообразные события, которые вы
-
зывают повторное размещение элементов в дереве представлений, например
изменение размера родительского или дочернего элемента, добавление/уда
-
ление дочернего элемента или переупорядочение дочерних элементов. Кроме
того, есть возможность явно потребовать выполнить повторное размещение
элементов вызовом
View.requestLayout
.
Реализация по умолчанию метода
onLayout
ничего не делает (хотя конкрет
-
ные подклассы
ViewGrou
p
, такие как
FrameLayout
и
Linearlayout
, переопределяют
этот метод). У вас есть возможность выбирать, как
ViewGroup
будет размещать
свои дочерние элементы. Например, вертикально ориентированный
Linear
Layout
сначала измерит все дочерние элементы (в
onMeasure
); затем, в
onLayout
,
разместит первый дочерний элемент вверху, второй дочерний элемент – под
ним, третий – под вторым и т. д. Вертикальный размер этого компонента бу
-
дет равен сумме вертикальных размеров его дочерних элементов.
FrameLayout
действует проще (и эффективнее): все дочерние элементы размещаются неза
-
висимо друг от друга, причем для каждого дочернего элемента явно опреде
-
ляется параметр
LayoutParams
со значениями координат левого верхнего угла
элемента.
Например, следующий код использует
FrameLayout
для отображения содержи
-
мого в
Ac ti vi ty
и располагает
TextView
на расстоянии ста пикселей от верхнего
и левого края. Появление других дочерних экземпляров
View
в этом контейне
-
ре не повлияет на положение
TextView
, потому что при выполнении операции
onLayout
класс
FrameLayout
проверяет только значения координат дочерних эле
-
ментов в
LayoutParams
:
Java
public class MyAc ti vi ty extends Ac ti vi ty {
@Override
public void onCreate(Bundle savedInstanceState) {
FrameLayout container = new FrameLayout(this);
setContentView(container);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.leftMargin = 100;
lp.topMargin = 100;
TextView textView = new TextView(this);
textView.setText("Hello world!");
container.addView(textView, lp);
}
}
Android
65
Kotlin
class MyAc ti vi ty : Ac ti vi ty() {
override fun onCreate(savedInstanceState: Bundle?) {
val container = FrameLayout(this)
setContentView(container)
val lp = FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
lp.leftMargin = 100
lp.topMargin = 100
val textView = TextView(this)
textView.text = "Hello world!"
container.addView(textView, lp)
}
}
Вот пример реализации
onLayout
, которая располагает дочерние элементы по
горизонтали, пока не останется свободного места, после чего переходит к сле
-
дующей строке. Иногда такое размещение называют «потоковым» («FlowLay
-
out»):
Java
@Override
protected void onLayout(boolean changed, int left, int top,
int right, int bottom) {
int x = 0;
int y = 0;
int tallest = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
tallest = Math.max(tallest, childHeight);
if (childWidth + x > getWidth()) {
x = 0;
y += tallest;
tallest = 0;
}
child.layout(x, y, c + childWidth, y + childHeight);
x += childWidth;
}
}
Kotlin
override fun onLayout(changed: Boolean, left: Int, top: Int,
right: Int, bottom: Int) {
var x = 0
var y = 0
var tallest = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
val childWidth = child.measuredWidth
66
Пользовательские компоненты
val childHeight = child.measuredHeight
tallest = Math.max(tallest, childHeight)
if (childWidth + x > width) {
x = 0
y += tallest
tallest = 0
}
child.layout(x, y, x + childWidth, y + childHeight)
x += childWidth
}
}
Также можно добавлять свои методы и свойства, чтобы получить функцио
-
нальность, необходимую вашему компоненту.
Do'stlaringiz bilan baham: |