프로젝트 디자인을 하다 테마가 Material design과 Neumorphic design의 중간 정도의 어떤것이 나오게 됐다.
생각하는 거의 모든 것이 구현 되어 있는 flutter라서 당연히 inner shadow도 어떻게 방법이 있을 줄 알았는데 없었다.
문제의 디자인
CustomPaint의 painter속성에 그림자를 구현한 CustomPainter 객체를 주기로했다.
Container(
height: height, //MediaQuery의 height*0.1값
margin: const EdgeInsets.fromLTRB(20, 10, 20, 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(height*0.25),
bottomLeft: Radius.circular(height*0.25),
topRight: Radius.circular(height*0.5),
bottomRight: Radius.circular(height*0.5)
),
color: Colors.green,
),
),
Container(
...
child: CustomPaint(
painter: MyPainter(height: height),
child: Container(
margin: const EdgeInsets.all(8),
),
),
),
최상위 컨테이너 위젯의 child로 CustiomPaint위젯을 주고 painter속성에 MyPainter를 준다.
그림자가 필요한 영역을 딴다.
우선 영역을 채울 Paint객체부터
class MyPainter extends CustomPainter{
double height;
MyPainter({required this.height});
@override
void paint(ui.Canvas canvas, ui.Size size) {
var paint = Paint()
..strokeWidth = 2
..style = ui.PaintingStyle.stroke;
// path객체를 구현하는 부분
canvas.drawPath(path, paint4);
}
@override
bool shouldRepaint(covariant CustomPainter
...
}
테스트를 위해 선 형태의 페인트 객체를 생성
위젯의 왼쪽 위 부분
var path = Path()
..moveTo(0, height*0.25)
..addArc(
Rect.fromCircle(center: Offset(height*0.25, height*0.25), radius: height*0.25),
degToRad(180),
degToRad(90)
);
BorderRadius 값은 위의 이미지처럼 작은 원의 반지름 값을 가진다.
시작 점을 (0, radius)로 이동하고 1/4크기의 부채꼴을 그린다.
addArc의 2,3번째 인자는 시작 각도와 끝 각도인데 degree값이 아닌 radian값을 가지기 때문에 바꿔줘야한다.
왼쪽 구석에 부채꼴 모양으로 그려진 모습
처음 작은 부채꼴에서 그 다음 부채꼴까지 직선
var path = Path()
..moveTo(0, height*0.25)
..addArc(
Rect.fromCircle(center: Offset(height*0.25, height*0.25), radius: height*0.25),
degToRad(180),
degToRad(90)
)
..lineTo(size.width-height*0.5, 0);
선이 연장됐다.
위와 같은 방식으로 진행
var path = Path()
..moveTo(0, height*0.25)
..addArc(
Rect.fromCircle(center: Offset(height*0.25, height*0.25), radius: height*0.25),
degToRad(180),
degToRad(90)
)
..lineTo(size.width-height*0.5, 0)
..addArc(
Rect.fromCircle(center: Offset(size.width-height/2, height/2), radius: height/2),
degToRad(270),
degToRad(90)
);
절반 해냈다.
이제는 베지어 곡선을 이용한다.
현재 recent점까지 이동한 상황이고 quadraticBezierTo메소드에 (x1, y1), (x2, y2)좌표를 준다.
var path = Path()
..moveTo(0, height*0.25)
..addArc(
Rect.fromCircle(center: Offset(height*0.25, height*0.25), radius: height*0.25),
degToRad(180),
degToRad(90)
)
..lineTo(size.width-height*0.5, 0)
..addArc(
Rect.fromCircle(center: Offset(size.width-height/2, height/2), radius: height/2),
degToRad(270),
degToRad(90)
)
..quadraticBezierTo(size.width-7, 7, size.width-height/2, 7);
위의 선과 임의의 수 7만큼 거리를 뒀다. 이 다음 직선을 그리고
같은 방식으로 베지어 곡선을 그린다
이제 마지막 부분을 이어준다.
여기서 꼬리를 길게 이어주는 작업을 한다.
var path = Path()
..moveTo(0, height*0.25)
..addArc(
Rect.fromCircle(center: Offset(height*0.25, height*0.25), radius: height*0.25),
degToRad(180),
degToRad(90)
)
..lineTo(size.width-height*0.5, 0)
..addArc(
Rect.fromCircle(center: Offset(size.width-height/2, height/2), radius: height/2),
degToRad(270),
degToRad(90)
)
..quadraticBezierTo(size.width-7, 7, size.width-height/2, 7)
..lineTo(0, height*0.75)
..lineTo(0, height*0.25);
그림자의 테두리를 다 만들었다.
페인트 객체의 style을 stroke가 아닌 fill로 채운 모습
이제 이 영역을 그림자처럼 투명도를 낮추고 블러 효과를 주면된다.
var paint = Paint()
..color = Colors.black.withOpacity(0.25)
..maskFilter = MaskFilter.blur(BlurStyle.normal, convertRadiusToSigma(4))
..style = ui.PaintingStyle.fill;
페인트 객체의 maskFilter속성을 주면 되는데 MaskFilter.blur메소드의 두 번째 인자를 주목하면 저 값은 시그마(sigma)값을 필요로 한다. 이 시그마가 뭐냐면 가우시안 커널의 표준편차(sigma)를 의미한다.
double convertRadiusToSigma(double radius) {
return radius * 0.57735 + 0.5;
}
Radius를 이용해서 값을 만든다. 이런 식으로 페인트 객체를 만들면
그럴싸한 그림자가 완성 됐다!
class MyPainter extends CustomPainter{
double height;
MyPainter({required this.height});
@override
void paint(ui.Canvas canvas, ui.Size size) {
var paint = Paint()
..color = Colors.black.withOpacity(0.25)
..maskFilter = MaskFilter.blur(BlurStyle.normal, convertRadiusToSigma(4))
..style = ui.PaintingStyle.fill;
var path = Path()
..moveTo(0, height*0.25)
..addArc(
Rect.fromCircle(center: Offset(height*0.25, height*0.25), radius: height*0.25),
degToRad(180).toDouble(),
degToRad(90).toDouble()
)
..lineTo(size.width-height*0.5, 0)
..addArc(
Rect.fromCircle(center: Offset(size.width-height/2, height/2), radius: height/2),
degToRad(270).toDouble(),
degToRad(90).toDouble()
)
..quadraticBezierTo(size.width-7, 7, size.width-height/2, 7)
..lineTo(height*0.25, 7)
..quadraticBezierTo(5, 7, 4, height*0.3)
..lineTo(0, height*0.75)
..lineTo(0, height*0.25);
}
double convertRadiusToSigma(double radius) {
return radius * 0.57735 + 0.5;
}
double degToRad(double degree){
return degree * (math.pi / 180);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return false;
}
}
CustiomPaint를 쓸 일이 있을까 싶었는데 쓰게 됐고 이번에 써보면서 이제 어떤 모양의 위젯이라도 만들 수 있을 것만 같은 자신감이 생겼다.
이거 왠지 제가 필요한거같은 삘! 잘 보겠습니다 선댓글!