[Flutter] 도서 목록을 나타내는 Flutter Widget : listItem

StudipU·2024년 3월 3일
0

listItem 위젯 소개 ✨

Flutter에서 UI를 구축할 때 사용되는 lstItem 위젯은 리스트 아이템을 표시하는데 사용됩니다. 이 위젯은 MainScreen 클래스부터 QuarterlyBookListScreen 클래스, CategoryBookListScreen 등 거의 모든 클래스에서 활용되는 위젯으로, 화면에 책에 대한 정보를 표시하고 사용자가 특정 책을 탭할 때 해당 책의 상세 정보를 보여주는 데 사용됩니다.

주요 기능 및 코드 분석 🎭

1. listItem이 받아오는 인자

책 제목들로 구성된 List와, index 정보, 적응형 위젯을 위한 widthRatioheightRatio를 인자로 받아온다. 따라서 해당 listItem에 해당하는 책 제목을 bookList[index]와 같은 방식으로 가져옵니다.

Widget lstItem(List<String> bookList, int index, widthRatio, heightRatio){
	//...
}

2. GestureDetector를 사용한 탭 처리

GestureDetector 위젯을 사용하여 리스트 아이템이 탭되었을 때 onTap 이벤트를 처리합니다. 사용자가 아이템을 탭하면 해당 책의 상세 정보를 보여주는 BookDetailScreen으로 이동합니다.

return GestureDetector(
  onTap: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => BookDetailScreen(
          bookTitle: bookList[index],
        ),
      ),
    );
  },
  child: Container(
    // 위젯 내용 생략
  ),
);

BookDetailScreen 화면은 아래와 같은데, 해당 클래스에 대해서는 다음 글에서 자세히 다루도록 하겠습니다.

3. FutureBuilder를 사용한 비동기 이미지 로딩

getBookImage 함수를 사용하여 책의 이미지를 비동기적으로 로드합니다. getBookImage 함수는 Firestore에서 bookInfos collection에서 인자로 받은 bookTitleid값이 일치하는 문서를 가져와 해당 문서의 thumnail 필드 값을 imageUrl에 저장한 후, getImage(imageUrl)를 반환합니다. getImage함수는 imageUrl를 받아서 http 요청을 통해 이미지 정보를 받아오는 함수입니다.

Future<Image> getBookImage(String bookTitle) async {
  FirebaseFirestore _firestore = FirebaseFirestore.instance;
  QuerySnapshot querySnapshot = await _firestore
      .collection('bookInfos')
      .where('title', isEqualTo: bookTitle)
      .limit(1)
      .get();
  Map<String, dynamic>? data = querySnapshot.docs[0].data() as Map<String, dynamic>?;
  String imageUrl = data!['thumbnail'];
  return getImage(imageUrl);
}
Future<Image> getImage(String imageUrl) async {
  if (imageUrl.isEmpty) {
    throw Exception('$imageUrl');
  }

  try {
    http.Response response = await http.get(Uri.parse(imageUrl));
    if (response.statusCode == 200) {
      return Image.memory(response.bodyBytes);
    } else {
      throw Exception('Failed to load image - ${response.statusCode}');
    }
  } catch (e) {
    throw Exception('Failed to load image - $e');
  }
}

FutureBuilder를 사용하여 이미지가 로딩 중인지, 오류가 발생했는지, 성공적으로 로딩되었는지를 확인합니다. 로딩 중일 때는 CircularProgressIndicator를 표시하고, 오류가 발생했을 때는 기본 이미지를 표시합니다. 성공적으로 이미지를 로드했을 때는 해당 이미지를 표시합니다.

FutureBuilder(
  future: getBookImage(bookList[index]),
  builder: (BuildContext context, AsyncSnapshot<Image> snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    } else if (snapshot.hasError) {
      return Image.asset(
        'assets/app_logo.png',
        width: widthRatio * 105,
        fit: BoxFit.fill,
      );
    } else {
      return Container(
        // 이미지 표시 및 스타일 설정
      );
    }
  },
),

4. 책 제목 및 설명 표시

getAladinDescription함수는 Firestore에서 aladinBooks collection에서 인자로 받은 bookTitleid값이 일치하는 문서를 가져와 해당 문서의 description 필드 값을 bookContents 에 저장한 후 이를 반환합니다.

Future<String> getAladinDescription(String bookTitle) async {
  FirebaseFirestore _firestore = FirebaseFirestore.instance;
  QuerySnapshot querySnapshot = await _firestore
      .collection('aladinBooks')
      .where('title', isEqualTo: bookTitle)
      .limit(1)
      .get();
  Map<String, dynamic>? data = querySnapshot.docs[0].data() as Map<String, dynamic>?;
  String bookContents = data!['description'];

  return bookContents;
}

Column 위젯을 사용하여 책 제목과 설명을 표시합니다. 책 제목은 Row 위젯을 사용하여 표시하고, 책 설명은 FutureBuilder를 사용하여 비동기적으로 로드한 후 표시합니다. 설명을 로드하는 동안 로딩 인디케이터를 표시하고, 오류가 발생한 경우 오류 메시지를 표시합니다. 성공적으로 책 설명을 로드한 경우 해당 설명을 표시합니다.

Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    // 책 제목 표시
    Row(
      children: [
        // 책 제목 설정 및 스타일
      ],
    ),
    // 책 설명 표시
    FutureBuilder(
      future: getAladinDescription(bookList[index]),
      builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return CircularProgressIndicator();
        } else if (snapshot.hasError) {
          return Text(
            '책소개를 불러오는데 실패했습니다.',
            style: TextStyle(
              // 텍스트 스타일 설정
            ),
          );
        } else {
          return Container(
            // 책 설명 표시 및 스타일 설정
          );
        }
      },
    ),
  ],
),

lstItem 위젯 전체 코드 🎍

Widget lstItem(List<String> bookList, int index, widthRatio, heightRatio) {
  return GestureDetector(
    onTap: () {
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => BookDetailScreen(
            bookTitle: bookList[index],
          ),
        ),
      );
    },
    child: Container(
      margin: EdgeInsets.only(bottom: heightRatio * 20),
      padding: EdgeInsets.symmetric(
        vertical: heightRatio * 10,
        horizontal: widthRatio * 10,
      ),
      decoration: ShapeDecoration(
        color: Colors.white.withOpacity(0),
        shape: RoundedRectangleBorder(
          side: BorderSide(width: 2, color: Color(0xBFFFFFFF)),
          borderRadius: BorderRadius.circular(20),
        ),
      ),
      child: Container(
        margin: EdgeInsets.only(left: widthRatio * 5),
        child: Row(
          children: [
            FutureBuilder(
              future: getBookImage(bookList[index]),
              builder: (BuildContext context, AsyncSnapshot<Image> snapshot) {
                if (snapshot.connectionState == ConnectionState.waiting) {
                  return CircularProgressIndicator();
                } else if (snapshot.hasError) {
                  return Image.asset(
                    'assets/app_logo.png',
                    width: widthRatio * 105,
                    fit: BoxFit.fill,
                  );
                } else {
                  return Container(
                    decoration: BoxDecoration(
                      boxShadow: [
                        BoxShadow(
                          color: Color(0x3F000000),
                          blurRadius: 5,
                          offset: Offset(2, 4),
                          spreadRadius: 3,
                        )
                      ],
                    ),
                    child: Image(
                      image: snapshot.data!.image,
                      width: widthRatio * 105,
                      height: widthRatio * 105 * 1.48,
                      fit: BoxFit.fill,
                    ),
                  );
                }
              },
            ),
            Container(
              margin: EdgeInsets.only(left: 20),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Container(
                        margin: EdgeInsets.only(bottom: heightRatio * 20),
                        constraints: BoxConstraints(
                          maxWidth: 200.0,
                        ),
                        child: FittedBox(
                          fit: BoxFit.fitWidth,
                          child: Text(
                            bookList[index],
                            textAlign: TextAlign.center,
                            style: TextStyle(
                              color: Color(0xE5001F3F),
                              fontSize: 18,
                              fontFamily: 'GowunBatang',
                              fontWeight: FontWeight.bold,
                              height: 0,
                              letterSpacing: -0.40,
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                  FutureBuilder(
                    future: getAladinDescription(bookList[index]),
                    builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
                      if (snapshot.connectionState == ConnectionState.waiting) {
                        return CircularProgressIndicator();
                      } else if (snapshot.hasError) {
                        return Text(
                          '책소개를 불러오는데 실패했습니다.',
                          style: TextStyle(
                            color: Color(0xE5001F3F),
                            fontFamily: 'GowunBatang',
                            fontWeight: FontWeight.bold,
                            height: 0,
                            letterSpacing: -0.40,
                          ),
                        );
                      } else {
                        return Container(
                          width: widthRatio * 190,
                          height: heightRatio * 101,
                          child: Text(
                            snapshot.data!,
                            maxLines: 4,
                            overflow: TextOverflow.ellipsis,
                            textAlign: TextAlign.center,
                            style: TextStyle(
                              color: Color(0xE5001F3F),
                              fontFamily: 'GowunBatang',
                              fontWeight: FontWeight.bold,
                              height: 0,
                              letterSpacing: -0.40,
                            ),
                          ),
                        );
                      }
                    },
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    ),
  );
}
profile
컴공 대학생이 군대에서 작성하는 앱 개발 블로그

0개의 댓글