«Custom View. Делаем быстро, красиво, чисто». Илья Демидов

Preview:

Citation preview

Тонкости реализации

Custom View в AndroidИлья Демидов, Tinkoff Bank

Структура

Пример

➔ CustomLayout

➔ TextView

➔ CustomView

➔ ImageView

➔ CustomLayout

➔ ImageView

➔ ImageView

Типы компонентов и их свойства

- View.

- ViewGroup extends View.

void onDraw(Canvas canvas); - отрисовка;

void onMeasure(int widthMeasureSpec, int heightMeasureSpec); - расчет

размеров;

void onLayout(boolean changed, int l, int t, int r, int b); - расстановка

детей;

void onTouchEvent(MotionEvent event); - взаимодействие с пользователем;

Отрисовка

@Overrideprotected void onDraw(Canvas canvas) {

canvas.drawColor(backgroundColor);if (!isEmpty()) {

canvas.drawText(text, textBound.left, -textBound.top, textPaint);}

}

setWillNotDraw(false) - позволяет вызывать отрисовку у ViewGroup

invalidate() - служит для перерисовки View.

Нельзя производить инициализацию объектов и выполнять очень тяжелые

операции. Все данные для отрисоки уже должны быть готовы к моменту

вызова метода.

Расчет размеров

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int widthMode = MeasureSpec.getMode(widthMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int width = 0;switch (widthMode) {

case MeasureSpec.EXACTLY:width = widthSize;break;

case MeasureSpec.AT_MOST:width = Math.min(widthSize, textBound.left + textBound.right);break;

case MeasureSpec.UNSPECIFIED:width = textBound.left + textBound.right;

}setMeasuredDimension(width, height);

}

Расчет размеров

MeasureSpec.EXACTLY; - точные размеры

MeasureSpec.AT_MOST; - не больше, чем размеры родителя

MeasureSpec.UNSPECIFIED; - любые размеры

setMeasuredDimension(width, height); - после расчета данные обязательно

должны публиковаться с помощью этого метода

requestLayout(); - служит для перерасчета и перерисовки View

Расчет размеров для ViewGroup

private void measureViews(int widthMeasureSpec, int heightMeasureSpec) {for (int i = 0; i < getChildCount(); i++) {

int childWidthSpec;View child = getChildAt(i);LayoutParams lp = child.getLayoutParams();if (lp.width == LayoutParams.MATCH_PARENT) {

childWidthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);

} else {childWidthSpec = getChildMeasureSpec(widthMeasureSpec,

0, lp.width);}child.measure(childWidthSpec, childHeightSpec);

}}

getChildMeasureSpec(int spec, int padding, int childDimension) - does the hard

part of measureChildren: figuring out the MeasureSpec to pass to a particular child.

Custom LayoutParams

LayoutParams extends ViewGroup.LayoutParams

LayoutParams extends ViewGroup.MarginLayoutParams

ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs);

ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p);

boolean checkLayoutParams(ViewGroup.LayoutParams p);

ViewGroup.LayoutParams generateDefaultLayoutParams();

Расстановка детей

protected void onLayout(boolean changed, int l, int t, int r, int b) {int count = getChildCount();int childLeft = 0;int childTop = 0;for (int i = 0; i < count; i++) {

View child = getChildAt(i);int lc = (int) (childLeft + offset);if (lc + child.getMeasuredWidth() > r - l) {

lc = (r - l) - child.getMeasuredWidth();} else if (lc < 0) {

lc = 0;}child.layout(lc, childTop, lc + child.getMeasuredWidth(),

childTop + child.getMeasuredHeight());childLeft += child.getMeasuredWidth();childTop += child.getMeasuredHeight();

}}

Взаимодействие с пользователем

@Overridepublic boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {case MotionEvent.ACTION_DOWN:

performPressedState();return true;

case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:

performUnpressedState();return true;

default:return false;

}}

Взаимодействие с пользователем

MotionEvent.ACTION_DOWN- первое касание

MotionEvent.ACTION_MOVE - движение пальца

MotionEvent.ACTION_UP - пользователь убрал палец

MotionEvent.ACTION_CANCEL - текущее действие было отменено

Так-же содержит индекс касания, время, координаты и т.д.

Если View обработала событие, метод должен вернуть true, иначе false.

Диспетчеризация событий

События пользователя обрабатываются с верхнего уровня до нижнего. Если

старший контейнер может обработать событие, то дети это событие не

получат.

boolean dispatchTouchEvent(); - решает, что делать с событием. Если

контейнер может сам обработать событие, вызывает onTouchEvent(), иначе

вызывает dispatchTouchEvent() своих детей.

boolean interceptTouchEvent() - решает, прокидывать событие дальше или

обработать его самому.

void requestDisallowInterceptTouchEvent(boolean) - запрещает родителям

перехватывать события

Диспетчеризация событий

Activity.

dispatchTouchEvent()

Activity.

onTouchEvent()

GdgLayout.

dispatchTouchEvent()

GdgLayout.

onTouchEvent()

GdgView.

dispatchTouchEvent()

GdgView.

onTouchEvent()

Activity.

dispatchTouchEvent()

Activity.

onTouchEvent()

GdgLayout.

dispatchTouchEvent()

GdgLayout.

onTouchEvent()

GdgView.

dispatchTouchEvent()

GdgView.

onTouchEvent()

ACTION_CANCEL

<?xml version="1.0" encoding="utf-8"?><....gdgvrn2015.widget.GdgLayout

android:id="@+id/gdg_group1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center">

<...gdgvrn2015.widget.GdgableTextViewandroid:id="@+id/gdg_tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textStyle="bold" />

<...gdgvrn2015.widget.GdgViewandroid:id="@+id/gdg_id1"android:layout_width="wrap_content"android:layout_height="wrap_content"

/>

<ImageViewandroid:layout_width="75dp"android:layout_height="50dp"android:src="@drawable/gdg"/>

<...gdgvrn2015.widget.GdgLayoutandroid:id="@+id/gdg_group2"android:layout_width="wrap_content"android:layout_height="wrap_content"><ImageView

android:layout_width="75dp"android:layout_height="50dp"android:src="@drawable/gdg"/>

<ImageViewandroid:layout_width="75dp"android:layout_height="50dp"android:src="@drawable/gdg"/>

</...gdgvrn2015.widget.GdgLayout></...gdgvrn2015.widget.GdgLayout>

Ссылки

https://developer.android.com/intl/ru/training/custom-views/index.html - Creating

custom View;

https://developer.android.com/intl/ru/guide/topics/ui/how-android-draws.html - How

Android Draws Views;

https://youtu.be/EZAoJU-nUyI - Mastering the Android Touch System;

https://github.com/dem1d/GDGfestVoronezh2015 - пример из презентации;

Исходный код View, ViewContainer и стандартных компонентов SDK;

https://twitter.com/dem1d - мой twitter;

Recommended