

만들어야 하는 모습 html 로는 구현 한 적이 있는데 플러터로는 처음이라 당황스럽다.
제일 큰 차이점은
제목 (접기/열기)
내용
이 아니라 제목에 해당하는 칸 들도 내용에 해당한다는 부분이었다.
열나는 서치 후 ExpansionPanel class 이라는 클래스를 발견하였다.
이 역시 제목-내용 형식을 띄고 있으나 진행하기로 했다.
class Cate {
int categoryId;
String categoryName;
Cate({
required this.categoryId,
required this.categoryName,
});
}
List<Cate> cateList = [
Cate(categoryId: 112011, categoryName: '소설'),
Cate(categoryId: 170, categoryName: '경제경영'),
//생략
Cate(categoryId: 8257, categoryName: '대학교재/전문서적'),
];
cateList 를 확인하여 for 문을 돌릴 수 있게 되었습니다.

오늘 만들 것. 제일 처음 넣은 더미 데이터들이 버튼이 되어 나오고 있습니다.
이런 기능을 지원하는
ExpansionPanel
클래스를 사용하여 구현해 봅니다.bool _isOpen = false; // 상태 변수 초기화
해당 칸이 열려 있는지 여부를 저장하는 isOpen 이 필요하고, 따라서 이 클래스는
StatefulWidget
을 상속하도록 변경 되어야 합니다.ExpansionPanelList 내부에 간단한 스타일 지정을 하고, children 내부에 판넬 코드를 작성합니다.
Widget _buildExpansionPanelList(TextTheme theme) {
return ExpansionPanelList(
dividerColor: Colors.transparent,
elevation: 0, // 그림자 효과
expandedHeaderPadding: EdgeInsets.all(0),
children: [
_buildExpansionPanel(theme), // 메서드로 분리된 개별 패널
],
expansionCallback: (panelIndex, isOpen) {
setState(() {
_isOpen = !_isOpen;
}); // 패널 상태 변경
},
);
}
보통은 노션의 토글 기능처럼 제목-컨텐츠로 이루어 지지만, 이번 경우에는
카테고리[0-3], 카테고리[4-끝] 이 안에 나와야 하는 문제가 있었습니다.

child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: cateList.take(4).map((cate) {
return _buildCategoryButton(cate.categoryName, cate.categoryId);
}).toList(),
),
Wrap 을 사용하여 해당 넓이를 초과하지 않도록 하고, cateList.take(4).map 을 통해 초반 표시될 카테고리의 갯수를 지정했습니다.
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: cateList.sublist(4).map((cate) {
return _buildCategoryButton(cate.categoryName, cate.categoryId);
}).toList(),
),
cateList.sublist(4).map 을 통해
인문학
이후의 카테고리 부터 표시 되도록 설정했습니다.
전체코드(_buildExpansionPanel)
// 개별 ExpansionPanel 빌드 메서드
ExpansionPanel _buildExpansionPanel(TextTheme theme) {
final double panelHeight = 20.0;
return ExpansionPanel(
backgroundColor: Colors.white,
headerBuilder: (context, isOpen) {
return Container(
height: panelHeight, // Set the height
alignment: Alignment.centerLeft, // Optional: Align content
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: cateList.take(4).map((cate) {
return _buildCategoryButton(cate.categoryName, cate.categoryId);
}).toList(),
),
);
},
body: Container(
alignment: Alignment.centerLeft,
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: cateList.sublist(4).map((cate) {
return _buildCategoryButton(cate.categoryName, cate.categoryId);
}).toList(),
),
),
isExpanded: _isOpen,
);
}
}
_buildCategoryButton
버튼 스타일 입니다. 버튼이 눌릴 때 마다 ajax 요청을 보내야 하기 때문에
onTap
사용이 필요합니다. 따라서 container 였던 위젯을 InkWell
로 변경 했습니다.Widget _buildCategoryButton(String categoryName, int categoryId) {
return InkWell(
onTap: () {
print('카테고리 ID: $categoryId');
},
borderRadius: BorderRadius.circular(20), // 클릭 영역 둥글게 설정
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Colors.grey[200], // 배경 색상 설정
borderRadius: BorderRadius.circular(20), // 모서리 둥글게
border: Border.all(color: Colors.grey[400]!),
),
child: Text(
categoryName, // 카테고리 이름 표시
style: TextStyle(
color: Colors.black, // 텍스트 색상
),
),
),
);
}
Share article