CustomPaint class
페인트 단계에서 그림을 그릴 수 있는 캔버스를 제공하는 위젯입니다.
페인트 작업을 요청받으면 CustomPaint는 먼저 페인터에게 현재 캔버스에 페인트 작업을 요청한 다음, 자식 캔버스를 페인트 하고, 자식 캔버스를 페인트 한 후, 포그라운드 페인터에게 페인트 작업을 요청합니다. 캔버스의 좌표계는 CustomPaint 객체의 좌표계와 일치합니다. 페인터는 원점에서 시작하여 주어진 크기의 영역을 포함하는 사각형 안에 페인트 작업을 해야 합니다. (페인터가 해당 경계를 벗어나 페인트 작업을 수행하면 페인트 명령을 래스터화 하는 데 필요한 메모리가 부족하여 결과 동작이 정의되지 않을 수 있습니다.) 이러한 경계 내에서 페인트 작업을 수행하려면 이 CustomPaint 객체를 ClipRect 위젯 으로 래핑 하는 것을 고려해 보세요.
페인터는 CustomPainter를 하위 클래스 화하여 구현됩니다.
공식 문서 코드
결국에 CustomPaint 를 알아야 하는 시간이 되었다. 플러터를 시작한 지 별로 되지 않았을 때 이 위젯을 살짝 맛만 보고 바로 빤스런을 쳤던 기억이 남아있다. 인도행님들이 설명하시는 영상을 봐도 무색하게 그냥 나중에 공부해야지.. 하고 노트북을 접지 않았나 한다.. 오늘 한번 제대로 초보자의 입장에서 설명을 하려 한다. 그러니 무심코 들어온 초보자 형님들도 이참에 한번 맛은 보고 가는 것이 어떠한가 싶다. 그럼 시작하겠다.
CustomPaint
하위 속성
속성명 | 타입 | 기본값 | 설명 |
painter | CustomPainter? | null | 자식 위젯이 렌더링되기 전에 배경을 그리는 데 사용되는 페인터 |
foregroundPainter | CustomPainter? | null | 자식 위젯이 렌더링된 후 전경을 그리는 데 사용되는 페인터 |
size | Size | Size.zero | 자식이 없을 경우 이 위젯이 가지려는 기본 크기 |
isComplex | bool | false | 렌더링이 복잡하여 래스터 캐시에 저장할 가치가 있는지 여부를 나타냄 |
willChange | bool | false | 다음 프레임에서 그림이 변경될 가능성이 있는지에 대한 힌트를 제공 |
child | Widget? | – | 페인터와 함께 렌더링될 자식 위젯 |
그럼 일단 가장 먼저 알아볼 것은 CustomPaint 이다. 이 친구는 CustomPianter 로 그린 그림을 자식 요소로 갖는 친구인데 굳이 비교하자면 Theme 이랑 비슷하다고 생각하면 된다. ThemeData 를 사용하기 위해선 Theme 으로 먼저 감싸줘야 하듯이 똑같다.
근데 이제 child 값도 받는데 기본값이 Wdiget? 이기 때문에 Container 같은 거 넣어서 CustomPaint 자체를 꾸밀 수도 있다.
CustomPaint(
painter: MyExample(), // 자신이 만든 커스텀 페인트 넣기
child: Container(), // 꾸미기 가능
),
CustomPainter
앞서 언급했듯 이놈이 "진짜"인데 이것만 알면 다 할 수 있다.
CustomPainter 는 CustomPaint 와 함께 사용되어 캔버스에 직접 그리기 작업을 수행하는 데 사용된다.
이 친구는 우리가 흔히 사용하는 "일반적인 위젯" (Text, Container, Column) 이 아닌 delegate class 라 불리는 화면을 그리는데 도움을 주는 대리자 클래스 정도로 말할 수 있다.
class MyExample extends CustomPainter {
보통 extends 에 들어가는 StatelessWidget 이나 StatefulWidget 이 아닌 CustomPainter 가 들어간 모습이다. 화면에 뭘 그릴지는 처음에 넣는 이곳에서 정의되기 때문에 CustomPainter 안에서는 이제 평소에 StatelessWidget 안에서 사용하던 빌드함수라든가 그런 건 사용 못 한다고 생각하면 된다.
class MyExample extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// TODO 그림 그리는 곳!!
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO 그림이 다시 그려질때 조종하는 곳!!
throw UnimplementedError();
}
}
CustomPainter 는 하위분류로 paint 와 shouldRePaint 를 필요로 하는데 extends CustomPainter 를 했다면 아마 자동완성으로 작성이 될 것이다.
paint 는 그림을 그리는 곳인데 캔버스(Canvas)와 사이즈(Size) 객체가 주어진다. 반드시 모든 그리기 명령(그릴 그림)은 주어진 사이즈 내에서 일어나야 한다.
shouldRePaint 이곳은 페인트가 다시 그려져야 할 때 조종할 수 있는 곳이다. 다시 그려지지 않을 거라면
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
=> false 로 선언해 주면 된다. 절대 바뀌지 않을 때 이렇게 하면 귀찮게 코드 작성 더 안 해도 되고 확실하다. (공식 왈)
그렇다면 한번 그림을 그려보자. (간단하게만 할 거다.)
그림을 그릴 때는 canvas 객체를 사용하여 그려야 한다.
그러나 canvas 는 보이는 스타일을 지정할 수 없다. 그렇기 때문에 따로 Text 에서 style: TextStyle() 하듯이 따로 스타일을 지정해줘야 하는데 그럴 때 사용하는 것이 Paint 다.
Paint paint = Paint()
..color = Colors.blue // .. <- 이게 cascade notation
..strokeWidth = 1;
일단 무조건 Paint paint = Paint() 를 적어라. 아까도 말했듯이 이게 style: TextStyle() 같은 거다. 이제 여기서 여러 속성을 연달아 설정하는 문법인 cascade notation 을 사용하여 꾸며주면 된다. 그리고 마무리는 세미콜론으로. 지금 나 같은 경우는 사실 Paint paint 가 아니라 final paint 로 선언해도 된다. 왜냐하면 다시 그려지질 않을 거니까. 자 이걸로 스타일하는 법은 알았다.
그럼 이제 실질적인 공간차지를 할 시간이다.
여러 가지 방법이 있지만 공식 코드적인 방향(?)으로 작성해 보겠다.
그릴 때는 캔버스 내에 있는 함수를 사용할 것이다.
drawLine 이라는 선 그리는 함수를 적으니 p1, p2, paint 를 필요로 한다. paint 는 우리가 아까 style 먹인 거니까 생략하고 p1 이랑 p2는 뭘까 싶을 것이다. 바로 시작점과 끝점이다.
시작점과 끝점은 우리가 직접 좌표로 넣어야 하는데 Offest 을 사용하여 2D 평면상의 좌표를 나타낼 수 있다.
canvas.drawLine(
Offset(10, 10),
Offset(200, 100),
paint,
);
이렇게 하여 대충 실행하면 이런 느낌으로 나오지 않을까 싶다.
초간단 코드 (Run 누르면 실행됨)
실제로 실행을 해보면 잘 나온 다는 것을 알 수 있다.
근데 생각보다 쉽지 않은가? 그렇다 어느샌가 완벽하게 그려내고 있다.
이번 글은 초보자의 입장에서 이해에 중점을 뒀기 때문에 더욱 그런 것도 있지만 생각보다 더 쉽다.(사실 제일 쉬운 함수 쓰고 설정 뭐 별로 안 해서 쉬운 건 비밀)
그래도 겁나 함수 많고 파면 팔수록 더 어려워지는 건 이치다. 암튼 도움이 되었길 바라며 마치겠다.