만들어야 하는 모습 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

