Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

1,034 views

Published on

Доклад с GDG DEvFest Voronezh 2015.

Published in: Software
  • Be the first to like this

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

  1. 1. Тонкости реализации Custom View в Android Илья Демидов, Tinkoff Bank
  2. 2. Структура
  3. 3. Пример ➔ CustomLayout ➔ TextView ➔ CustomView ➔ ImageView ➔ CustomLayout ➔ ImageView ➔ ImageView
  4. 4. Типы компонентов и их свойства - 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); - взаимодействие с пользователем;
  5. 5. Отрисовка @Override protected void onDraw(Canvas canvas) { canvas.drawColor(backgroundColor); if (!isEmpty()) { canvas.drawText(text, textBound.left, -textBound.top, textPaint); } } setWillNotDraw(false) - позволяет вызывать отрисовку у ViewGroup invalidate() - служит для перерисовки View. Нельзя производить инициализацию объектов и выполнять очень тяжелые операции. Все данные для отрисоки уже должны быть готовы к моменту вызова метода.
  6. 6. Расчет размеров 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); }
  7. 7. Расчет размеров MeasureSpec.EXACTLY; - точные размеры MeasureSpec.AT_MOST; - не больше, чем размеры родителя MeasureSpec.UNSPECIFIED; - любые размеры setMeasuredDimension(width, height); - после расчета данные обязательно должны публиковаться с помощью этого метода requestLayout(); - служит для перерасчета и перерисовки View
  8. 8. Расчет размеров для 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.
  9. 9. 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();
  10. 10. Расстановка детей 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(); } }
  11. 11. Взаимодействие с пользователем @Override public 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; } }
  12. 12. Взаимодействие с пользователем MotionEvent.ACTION_DOWN- первое касание MotionEvent.ACTION_MOVE - движение пальца MotionEvent.ACTION_UP - пользователь убрал палец MotionEvent.ACTION_CANCEL - текущее действие было отменено Так-же содержит индекс касания, время, координаты и т.д. Если View обработала событие, метод должен вернуть true, иначе false.
  13. 13. Диспетчеризация событий События пользователя обрабатываются с верхнего уровня до нижнего. Если старший контейнер может обработать событие, то дети это событие не получат. boolean dispatchTouchEvent(); - решает, что делать с событием. Если контейнер может сам обработать событие, вызывает onTouchEvent(), иначе вызывает dispatchTouchEvent() своих детей. boolean interceptTouchEvent() - решает, прокидывать событие дальше или обработать его самому. void requestDisallowInterceptTouchEvent(boolean) - запрещает родителям перехватывать события
  14. 14. Диспетчеризация событий 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
  15. 15. <?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.GdgableTextView android:id="@+id/gdg_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" /> <...gdgvrn2015.widget.GdgView android:id="@+id/gdg_id1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ImageView android:layout_width="75dp" android:layout_height="50dp" android:src="@drawable/gdg"/> <...gdgvrn2015.widget.GdgLayout android: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"/> <ImageView android:layout_width="75dp" android:layout_height="50dp" android:src="@drawable/gdg"/> </...gdgvrn2015.widget.GdgLayout> </...gdgvrn2015.widget.GdgLayout>
  16. 16. Ссылки 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;

×