В этом уроке:
- рассматриваем PathEffect-объекты
У класса PathEffect есть несколько наследников, которые позволяют влиять на рисуемые нами объекты. Рассмотрим на примерах их использование.
Создадим проект:
Project name: P1511_PathEffect
Build Target: Android 2.3.3
Application name: PathEffect
Package name: ru.startandroid.develop.p1511patheffect
Create Activity: MainActivity
CornerPathEffect
Эффект CornerPathEffect просто закругляет углы. На вход принимает радиус закругления.
Пишем в MainActivity.java:
package ru.startandroid.develop.p1511patheffect;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new DrawView(this));
}
class DrawView extends View {
Path path;
Paint p1;
Paint p2;
Paint p3;
public DrawView(Context context) {
super(context);
path = new Path();
path.rLineTo(100, 300);
path.rLineTo(100, -100);
path.rLineTo(100, 300);
p1 = new Paint(Paint.ANTI_ALIAS_FLAG);
p1.setStyle(Paint.Style.STROKE);
p1.setStrokeWidth(3);
p2 = new Paint(p1);
p2.setColor(Color.GREEN);
p2.setPathEffect(new CornerPathEffect(25));
p3 = new Paint(p1);
p3.setColor(Color.BLUE);
p3.setPathEffect(new CornerPathEffect(50));
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawARGB(80, 102, 204, 255);
canvas.translate(100, 100);
canvas.drawPath(path, p1);
canvas.translate(250, 0);
canvas.drawPath(path, p2);
canvas.translate(250, 0);
canvas.drawPath(path, p3);
}
}
}
Смотрим код. Мы создаем Path, состоящий из трех линий. Создаем три кисти с разными цветами: черную без эффектов, зеленую с закруглением с радиусом в 25, и синюю с закруглением 50. И рисуем фигуру три раза.
Результат:

DiscretePathEffect
DiscretePathEffect позволяет получить ломанную линию из прямой. Полученная ломанная линия будет состоять из фрагментов, а мы можем повлиять на длину этих фрагментов (первый параметр конструктора) и степень излома (второй параметр).
Перепишем класс DrawView:
class DrawView extends View {
Path path;
Paint p1;
Paint p2;
Paint p3;
public DrawView(Context context) {
super(context);
path = new Path();
path.rLineTo(100, 300);
path.rLineTo(100, -100);
path.rLineTo(100, 300);
p1 = new Paint(Paint.ANTI_ALIAS_FLAG);
p1.setStyle(Paint.Style.STROKE);
p1.setStrokeWidth(3);
p2 = new Paint(p1);
p2.setColor(Color.GREEN);
p2.setPathEffect(new DiscretePathEffect(10,5));
p3 = new Paint(p1);
p3.setColor(Color.BLUE);
p3.setPathEffect(new DiscretePathEffect(10,15));
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawARGB(80, 102, 204, 255);
canvas.translate(100, 100);
canvas.drawPath(path, p1);
canvas.translate(250, 0);
canvas.drawPath(path, p2);
canvas.translate(250, 0);
canvas.drawPath(path, p3);
}
}
Для зеленой линии используем степень излома – 5, а для синей – 15. Длина фрагментов = 10.
Результат:

DashPathEffect
С помощью DashPathEffect мы из сплошной линии можем получить прерывистую. От нас требуется задать длину участка который будет прорисован и длину участка, который прорисован не будет, т.е. «пусто». Далее эта комбинация будет циклично использована для прорисовки всей линии.
Перепишем класс DrawView:
class DrawView extends View {
Path path;
Paint p1;
Paint p2;
Paint p3;
public DrawView(Context context) {
super(context);
path = new Path();
path.rLineTo(100, 300);
path.rLineTo(100, -100);
path.rLineTo(100, 300);
p1 = new Paint(Paint.ANTI_ALIAS_FLAG);
p1.setStyle(Paint.Style.STROKE);
p1.setStrokeWidth(7);
p2 = new Paint(p1);
p2.setColor(Color.GREEN);
p2.setPathEffect(new DashPathEffect(new float[] { 30, 10}, 0));
p3 = new Paint(p1);
p3.setColor(Color.BLUE);
p3.setPathEffect(new DashPathEffect(new float[] { 50, 10, 5, 10 }, 25));
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawARGB(80, 102, 204, 255);
canvas.translate(100, 100);
canvas.drawPath(path, p1);
canvas.translate(250, 0);
canvas.drawPath(path, p2);
canvas.translate(250, 0);
canvas.drawPath(path, p3);
}
}
Для зеленой линии мы настраиваем длину выводимого участка = 30, длину пустоты = 10. Мы помещаем эти значение в массив и передаем в DashPathEffect-конструктор первым параметром. Вторым параметром идет отступ, его не используем.
Для синей линии мы задаем чуть более сложную последовательность: 50 выводить, 10 пусто, 5, выводить, 10 пусто. Т.е. принцип наверно уже понятен. Система будет поочередно использовать значения из массива для определения длины рисуемого куска линии и длины следующей за ним пустоты. Отступ используем в 25.
Результат:

Зеленая линяя состоит из отрезков длиной 30 и пустоты длиной 10. А синяя из отрезка длиной 50, пустоты 10, отрезка 5, пустоты 10. У синей линии первый отрезок выглядит короче остальных больших. Это сработал отступ в 25. Если вы этот отступ повесите в цикл, то линия оживет и поедет на месте, что выглядит достаточно эффектно.
PathDashPathEffect
PathDashPathEffect позволяет сделать пунктирную линию, но в качестве пунктира можно использовать свой Path-объект.
Перепишем класс DrawView:
class DrawView extends View {
Path path;
Path pathStamp;
Paint p1;
Paint p2;
Paint p3;
Paint p4;
public DrawView(Context context) {
super(context);
path = new Path();
path.addRect(-100, 0, 100, 500, Path.Direction.CW);
pathStamp = new Path();
pathStamp.lineTo(-10, -10);
pathStamp.lineTo(10, 0);
pathStamp.lineTo(-10, 10);
pathStamp.close();
p1 = new Paint(Paint.ANTI_ALIAS_FLAG);
p1.setStyle(Paint.Style.STROKE);
p1.setStrokeWidth(20);
p2 = new Paint(p1);
p2.setColor(Color.GREEN);
p2.setPathEffect(new PathDashPathEffect(pathStamp, 20, 0, PathDashPathEffect.Style.MORPH));
p3 = new Paint(p1);
p3.setColor(Color.BLUE);
p3.setPathEffect(new PathDashPathEffect(pathStamp, 20, 0, PathDashPathEffect.Style.ROTATE));
p4 = new Paint(p1);
p4.setColor(Color.RED);
p4.setPathEffect(new PathDashPathEffect(pathStamp, 20, 10, PathDashPathEffect.Style.TRANSLATE));
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawARGB(80, 102, 204, 255);
canvas.translate(120, 100);
canvas.drawPath(path, p1);
canvas.translate(250, 0);
canvas.drawPath(path, p2);
canvas.translate(250, 0);
canvas.drawPath(path, p3);
canvas.translate(250, 0);
canvas.drawPath(path, p4);
}
}
Создаем объект path с прямоугольником и pathStamp в виде стрелки. Далее на кисти вешаем эффект PathDashPathEffect. Его конструктор на вход принимает:
- path-объект, который будет использован в качестве пунктира
- расстояние между пунктирами
- отступ от начала
- стиль эффекта
Результат:

Линии состоят из стрелок (объект pathStamp). Расстояние между ними = 20. По стилям хелп не дает толковой инфы. По моим наблюдениям могу предположить следующее:
PathDashPathEffect.Style.MORPH – срезает пунктир на углах (видно по зеленой линии)
PathDashPathEffect.Style.ROTATE – корректно работает с углами (видно по синей линии)
PathDashPathEffect.Style.TRANSLATE – не поворачивает pathStamp по направлению основной линии (видно по красной фигуре)
Для красной линии я использовал небольшой отступ, это видно – стрелки идут не из самого угла. Опять же, повесив отступ в цикл вы получите ожившую линию.
SumPathEffect и ComposePathEffect
Позволяют нам комбинировать два эффекта, которые подаются им на вход.
ComposePathEffect применит сначала один эффект, потом к получившемуся результату – второй и выведет результат. SumPathEffect – применит к искомой фигуре один эффект, выведет результат, затем применит к искомой фигуре второй эффект и выведет результат.
Перепишем класс DrawView:
class DrawView extends View {
Path path;
Paint p1;
Paint p2;
Paint p3;
Paint p4;
Paint p5;
public DrawView(Context context) {
super(context);
path = new Path();
path.addRect(-100, 0, 100, 500, Path.Direction.CW);
PathEffect pe1 = new CornerPathEffect(100);
PathEffect pe2 = new DashPathEffect(new float[] { 20, 5}, 0);
PathEffect pe3 = new ComposePathEffect(pe2, pe1);
PathEffect pe4 = new SumPathEffect(pe1, pe2);
p1 = new Paint(Paint.ANTI_ALIAS_FLAG);
p1.setStyle(Paint.Style.STROKE);
p1.setStrokeWidth(3);
p2 = new Paint(p1);
p2.setColor(Color.GREEN);
p2.setPathEffect(pe1);
p3 = new Paint(p1);
p3.setColor(Color.BLUE);
p3.setPathEffect(pe2);
p4 = new Paint(p1);
p4.setColor(Color.RED);
p4.setPathEffect(pe3);
p5 = new Paint(p1);
p5.setColor(Color.YELLOW);
p5.setPathEffect(pe4);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawARGB(80, 102, 204, 255);
canvas.translate(120, 100);
canvas.drawPath(path, p1);
canvas.translate(250, 0);
canvas.drawPath(path, p2);
canvas.translate(250, 0);
canvas.drawPath(path, p3);
canvas.translate(250, 0);
canvas.drawPath(path, p4);
canvas.translate(250, 0);
canvas.drawPath(path, p5);
}
}
Создаем 4 эффекта.
pe1 – закругление
pe2 – прерывистая линяя
pe3 – комбинация, сначала будет применен pe1, затем к получившемуся результату - pe2
pe4 – сумма линия будет нарисована с эффектом pe1 и с эффектом pe2
Результат:

Зеленый прямоугольник закруглен (pe1). Синий нарисован прерывистым (pe2). Красный сначала закруглен, затем сделан прерывистым (pe1, затем pe2). Желтый – просто вывод обоих эффектов отдельно (pe1 и pe2).
В ComposePathEffect имеет значение порядок эффектов. Немного изменим предыдущий пример
class DrawView extends View {
Path path;
Paint p1;
Paint p2;
Paint p3;
Paint p4;
Paint p5;
public DrawView(Context context) {
super(context);
path = new Path();
path.addRect(-100, 0, 100, 500, Path.Direction.CW);
PathEffect pe1 = new CornerPathEffect(100);
PathEffect pe2 = new DiscretePathEffect(15, 10);
PathEffect pe3 = new ComposePathEffect(pe1, pe2);
PathEffect pe4 = new ComposePathEffect(pe2, pe1);
p1 = new Paint(Paint.ANTI_ALIAS_FLAG);
p1.setStyle(Paint.Style.STROKE);
p1.setStrokeWidth(7);
p2 = new Paint(p1);
p2.setColor(Color.GREEN);
p2.setPathEffect(pe1);
p3 = new Paint(p1);
p3.setColor(Color.BLUE);
p3.setPathEffect(pe2);
p4 = new Paint(p1);
p4.setColor(Color.RED);
p4.setPathEffect(pe3);
p5 = new Paint(p1);
p5.setColor(Color.YELLOW);
p5.setPathEffect(pe4);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawARGB(80, 102, 204, 255);
canvas.translate(120, 100);
canvas.drawPath(path, p1);
canvas.translate(250, 0);
canvas.drawPath(path, p2);
canvas.translate(250, 0);
canvas.drawPath(path, p3);
canvas.translate(250, 0);
canvas.drawPath(path, p4);
canvas.translate(250, 0);
canvas.drawPath(path, p5);
}
}
pe1 – закругление
pe2 – излом
pe3 – сначала применен pe2, затем pe1
pe4 – сначала применен pe1, затем pe2
Результат:

Красная фигура – это результат применения сначала излома, затем закругления. Т.е. закругление было применено уже к излому и он получился сглаженным.
А желтая фигура – результат применения сначала закругления, затем излома. Т.е. излом был применен к уже закругленному прямоугольнику.
Я везде использовал Path, но эти эффекты можно применять и при рисовании объектов канвы, например, Canvas.drawRect или Canvas.drawCircle
На следующем уроке:
- работаем с Picture
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
