레이아웃빌더, stack Positioned 위젯 작업
레이아웃빌더와 stack 위젯 Positioned 위젯을 사용해야하는 작업을 했습니다.
투표의 결과가 그래프로 표현되어야하는데,,, 먼저 최종 UI는 다음과 같습니다.
투표부분의 레이아웃 구조를 보자면 이렇습니다.
3번은 전체 테두리 영역이고, 1번은 개별 투표 결과의 겉테두리, 그리고 2번은 그 투표결과의 퍼센테이지가 색칠된
Container입니다. 그리고 2번 컨테이너는 1번 컨테이너의 child가 아니라 서로 동일한 위치에 동시에 겹쳐져있다고 생각하면됩니다. 1번컨테이너의 child로 2번컨테이너를 만들게되면 그안에 내부적으로 들어가야될 텍스트를 정렬하기가 복잡해집니다. 때문에 1번컨테이너 2번 컨테이너, 그리고 사진에 표시는 안했지만 Text위젯을 child로 삼는 Container도 존재하고 이 세가지 컨테이너가 3중으로 동일한 위치에 겹쳐지게 해야합니다.
3번 컨테이너는 앱 전체 크기에서 양쪽에 살짝씩 여백을 줬고 코드로 보면 다음과 같습니다.
현재 위젯이 최상위 바탕이 되는 위젯이기 때문에 MediaQuery.of(context).size.width가 리턴하는 기기 전체 가로길이를 최상위 부모 크기로 잡습니다.
@override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration(
border: Border.all(width: 1, color: AppColors.border),
borderRadius: AppBorderRadius.circular8),
양끝에 여백을 주기위해 총 최대 가로폭 Width 에서 32를 뺐습니다. 양쪽에 16씩 여백이 생깁니다.
이제 테두리 바탕 컨테이너가 할당되었으니 투표결과 리스트의 하나하나의 컨테이너 사이즈가 필요합니다.
색이 없고 테두리만 있는 컨테이너 1개와, 투표결과 백분율이 퍼센테이지만큼 칠해진 컨테이너를 동일한
위치에 겹칠겁니다. 이렇게하면 그래프처럼 보이게 할 수 있습니다.
플러터에서는Stack위젯을 사용하여 위젯을 겹치게 할 수 있습니다.
공식문서에는 Stack위젯을 상자의 가장자리를 기준으로 자식 위치를 지정하는 위젯이라고 설명합니다.
Stack의 하위로 children을 갖는데, Positioned위젯을 통해 자식들은 동일한 기준점을 기준으로 배치될 수 있습니다.
상위부모크기가 예를들어 360이고 그안에 border가 4를 잡아먹고 왼쪽 마진이6이면
그하위 위젯은 360에서 10을 뺀 350이 자동적으로 최대 크기로 할당됩니다.
1번컨테이너의 전체길이를 백분율로 환산한 뒤 결과 퍼센트만큼 연산하여
2번 컨테이너 길이에 할당한 뒤 색상을 지정했습니다.
텍스트가 들어가는 컨테이너의 길이도 절대값으로 넣지않고 최대길이의 어느정도 비율을 마진으로 할당하여 텍스트를 넣었습니다.
LayoutBuilder(
builder: (BuildContext context, BoxConstraints boxConstraints) {
return
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: state.voteList.length,
itemBuilder: (context, index) {
return
// 1번 투표아이템 테두리 컨테이너
Container(
margin: AppEdgeInsets.horizontal16,
height: 50,
width: boxConstraints.maxWidth,
decoration: BoxDecoration(
border:
state.voteList![index]!.voteUserArray.contains(userId)
? Border.all(width: 1, color: AppColors.primary)
: Border.all(width: 1, color: AppColors.black12),
borderRadius: AppBorderRadius.circular8
),
child:
Stack(
children: [
// 2번 투표아이템에서 색칠되는 컨테이너
Positioned(child:
Container(
height: 50.0,
width: ( (state.voteList![index]!.percent! / 100) * 100) == 100.0 ?
boxConstraints.maxWidth :
((state.voteList![index]!.percent! / 100) * 100 ) == 0.00 ? 0 :
((state.voteList![index]!.percent! / 100) * boxConstraints.maxWidth - 18) ,
decoration: BoxDecoration(
color: AppColors.gray5,
border: Border.all(width: 0, color: AppColors.gray5),
borderRadius:
// 100프로일때는 모서리가 삐져나가서 예외처리
( (state.voteList![index]!.percent! / 100) * 100) == 100.0 ? AppBorderRadius.circular8 :
BorderRadius.only(
topLeft: Radius.circular(8),
bottomLeft: Radius.circular(8))
),
)
),
Positioned(child: Container(width: boxConstraints.maxWidth, height: 50,
child: Text(state.voteList![index]!.text.toString())), left : boxConstraints.maxWidth * 0.04, top : 15),
Positioned(child: Container(width: boxConstraints.maxWidth, height: 50,
child: Text(state.voteList![index]!.percent!.round().toString() + '%',
overflow: TextOverflow.ellipsis)), left : boxConstraints.maxWidth * 0.7, top : 15),