Как рисовать в custom View: Canvas, Paint и отладка

Для рисования в custom View переопределите onDraw(Canvas), заранее инициализируйте Paint в конструкторе/init(), используйте Canvas.draw* (circle, line, path, text, bitmap) и для анимации вызывайте postInvalidateOnAnimation() или invalidate(Rect) для частичной перерисовки — так вы получите корректную, быструю и отлаживаемую отрисовку.

Основы Canvas и Paint

Canvas — холст; Paint — кисть с настройками (цвет, ширина, стиль, шейдеры, сглаживание).

  • Создайте View и инициализируйте ресурсы один раз:
public class MyView extends View {
  private Paint paint;
  public MyView(Context ctx) {
    super(ctx);
    init();
  }
  private void init() {
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(Color.RED);
    paint.setStyle(Paint.Style.FILL);
    paint.setStrokeWidth(4f);
  }
  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawCircle(100, 100, 50, paint);
  }
}
  • Частые методы Canvas: drawLine, drawRect, drawCircle, drawPath, drawText, drawBitmap.
  • Path для сложных фигур:
Path p = new Path();
p.moveTo(50,50);
p.lineTo(150,50);
p.lineTo(100,150);
p.close();
canvas.drawPath(p, paint);
  • Градиенты и эффекты: Shader (LinearGradient, RadialGradient) через paint.setShader(...). Для заливки фигур используйте Paint.Style.FILL, для обводки — STROKE.

Инициализируйте Paint, Path и Bitmap в init/конструкторе. Создание объектов в onDraw сильно снижает производительность.

Интерактив и анимация

  • Обработка касаний: переопределите onTouchEvent, сохраняйте координаты или Path и вызывайте invalidate() для перерисовки.
@Override public boolean onTouchEvent(MotionEvent e) {
  if (e.getAction() == MotionEvent.ACTION_DOWN || e.getAction() == MotionEvent.ACTION_MOVE) {
    path.lineTo(e.getX(), e.getY());
    invalidate(); // либо invalidate(dirtyRect)
  }
  return true;
}
  • Анимация: используйте postInvalidateOnAnimation() или Choreographer для 60 FPS; избегайте таймеров с частыми invalidate без синхронизации.
// пример простого шага анимации
void step() {
  updateState();
  postInvalidateOnAnimation();
}
  • Для частичной перерисовки передавайте область: invalidate(left, top, right, bottom) или invalidate(Rect), это экономит CPU/GPU.
  • Аппаратное/программное рендеринг: при проблемах с некоторыми эффектами попробуйте setLayerType(LAYER_TYPE_SOFTWARE, paint) или наоборот — аппаратное ускорение обычно быстрее.

Отладка и оптимизация отрисовки

  • Логируйте вызовы onDraw для понимa частоты:
Log.d("DRAW", "onDraw at " + System.currentTimeMillis());
  • Profile GPU Rendering (включается в Developer Options) показывает, где узкие места.
  • Layout Inspector/Hierarchy Viewer в Android Studio помогают увидеть сложную иерархию View и FPS.
  • Советы по оптимизации:
    • Минимизируйте создание объектов в onDraw.
    • Кэшируйте сложные рендеры в Bitmap и рисуйте уже готовый bitmap.
    • Используйте альфа-канал и blend-режимы экономно.
    • Обновляйте только области, которые изменились.
    • Для часто обновляемых анимаций используйте SurfaceView или RenderThread при необходимости.

Не вызывайте invalidate() в tight loop без throttling — это быстро разряжает батарею и может превысить возможности устройства.

Частые ошибки

  • Инициализация Paint в onDraw → падение FPS.
  • Путаница invalidate() и requestLayout() — requestLayout() пересчитывает измерения и вызывает больше работы.
  • Неправильные координаты: не забывайте учитывать padding, canvas.translate() и размеры getWidth()/getHeight().
  • Мерцание из‑за повторной смены LayerType или частых изменений background — используйте кэширование.

FAQ

  • Нужно ли всегда использовать Path для сложных форм?
    • Да, Path даёт гибкость; для статичных сложных элементов лучше отрисовать в Bitmap один раз.
  • Как плавно анимировать при 60 FPS?
    • Используйте postInvalidateOnAnimation() или Choreographer, обновляйте состояние с учётом дельта‑времени.
  • Как отрисовать текст с выравниванием?
    • Настройте paint.setTextSize(), paint.setTextAlign(Paint.Align.CENTER) и рассчитывайте baseline через paint.getFontMetrics().

Внедрите эти практики: инициализируйте ресурсы заранее, перерисовывайте только нужные области и используйте встроенные инструменты профилирования — это даст стабильную, плавную и предсказуемую графику в ваших custom View.