관리 메뉴

Bull

[Dev] LLM을 활용 단어장 앱 개발일지 006: 단어장 UI 변경 본문

일상/개발일지

[Dev] LLM을 활용 단어장 앱 개발일지 006: 단어장 UI 변경

Bull_ 2024. 8. 27. 08:22

서론

기존 단어장

기존 단어장 UI는 임의의 Card 을 채워서 구성해놨다. UI는 감으로 했기 때문에 크게 어디를 설명해야 할지 모르겠지만 일단 되는대로 적어볼 것이다.

데이터

우선 단어로 들어갈 임의의 데이터를 추가했다. 코드가 길기 때문에 따로 적어보겠다.

final List<String> words = [
  'contradictory',
  'incongruent',
  'paradoxical',
  'ambivalent',
  'discrepant',
  'discordant',
  'opposing',
  'clashing',
  'conflicting',
  'polarizing',
  'divergent',
  'ambiguous',
  'equivocal',
  'uncertain',
  'doubtful',
  'indeterminate',
  'indecisive',
  'vacillating',
  'wavering',
  'irresolute',
];
final List<String> meanings = [
  '상반되는',
  '불일치하는',
  '역설적인',
  '양면적인',
  '모순된',
  '불협화음의',
  '반대하는',
  '충돌하는',
  '모순된',
  '분열시키는',
  '다른',
  '모호한',
  '애매한',
  '불확실한',
  '의심스러운',
  '불확정된',
  '우유부단한',
  '흔들리는',
  '동요하는',
  '결단력이 없는',
];

final List<List<String>> tags = [
  ['#모순', '#상반되는', '#논쟁', '#의견불일치', '#학문적논의'],
  ['#불일치', '#부조화', '#상반됨', '#모호함', '#이견'],
  ['#역설적', '#모순된', '#비논리적', '#논리적충돌', '#대조'],
  ['#양면성', '#모호함', '#상반된감정', '#이중성', '#불확실성'],
  ['#불일치', '#모순', '#충돌', '#대립', '#상반'],
  ['#조화롭지않은', '#모순', '#불협화음', '#갈등', '#대립'],
  ['#반대', '#모순', '#대립', '#의견차이', '#갈등'],
  ['#충돌', '#대립', '#모순', '#불화', '#논쟁'],
  ['#상충', '#모순', '#대립', '#충돌', '#갈등'],
  ['#분열', '#대립', '#모순', '#갈등', '#논쟁'],
  ['#분기', '#차이', '#대립', '#모순', '#갈등'],
  ['#모호한', '#불확실한', '#모순된', '#혼란', '#불분명'],
  ['#애매한', '#모호한', '#상반된', '#혼란스러운', '#모순된'],
  ['#불확실한', '#모호한', '#결정되지않은', '#애매한', '#변화하는'],
  ['#의심스러운', '#불확실한', '#모호한', '#결정되지않은', '#우유부단한'],
  ['#미정의', '#불확실한', '#모호한', '#애매한', '#결정되지않은'],
  ['#우유부단한', '#변화하는', '#모호한', '#상반된', '#불확실한'],
  ['#동요하는', '#흔들리는', '#모호한', '#결정되지않은', '#모순된'],
  ['#흔들리는', '#불확실한', '#모호한', '#우유부단한', '#결정되지않은'],
  ['#결정하지못한', '#흔들리는', '#모호한', '#불확실한', '#모순된'],
];

final List<List<String>> exampleSentences = [
  [
    "The scientist's new theory was met with contradictory evidence from other researchers, creating a heated debate in the academic community.",
    "과학자의 새로운 이론은 다른 연구자들의 모순된 증거와 만나면서 학계에 뜨거운 논쟁을 불러일으켰습니다."
  ],
  [
    "The results were incongruent with our expectations, causing confusion.",
    "결과가 우리의 기대와 일치하지 않아 혼란을 일으켰습니다."
  ],
  [
    "His paradoxical statement left everyone puzzled.",
    "그의 역설적인 발언은 모두를 당황하게 만들었습니다."
  ],
  [
    "She felt ambivalent about the decision, unable to choose a side.",
    "그녀는 결정을 내리지 못하고 양면성을 느꼈습니다."
  ],
  [
    "The reports were discrepant, leading to further investigation.",
    "보고서가 서로 일치하지 않아 추가 조사가 필요했습니다."
  ],
  [
    "The discordant notes in the music created an unsettling atmosphere.",
    "음악의 불협화음이 불안한 분위기를 조성했습니다."
  ],
  [
    "Their opposing views led to a heated debate.",
    "그들의 반대되는 의견은 뜨거운 논쟁을 불러일으켰습니다."
  ],
  [
    "The clashing colors of the painting made it hard to look at.",
    "그림의 상충하는 색상들이 보기 어렵게 만들었습니다."
  ],
  [
    "The conflicting reports made it difficult to know the truth.",
    "모순된 보고서들이 진실을 알기 어렵게 만들었습니다."
  ],
  [
    "The polarizing issue split the community in two.",
    "극단적인 문제로 인해 커뮤니티가 두 개로 분열되었습니다."
  ],
  [
    "Their divergent paths in life led them to different destinations.",
    "그들의 갈라진 삶의 길은 그들을 다른 목적지로 이끌었습니다."
  ],
  [
    "The ambiguous instructions left the students confused.",
    "모호한 지시로 학생들이 혼란스러워했습니다."
  ],
  [
    "His equivocal response made it hard to know his true feelings.",
    "그의 애매한 대답은 그의 진심을 알기 어렵게 만들었습니다."
  ],
  [
    "The uncertain weather made planning difficult.",
    "불확실한 날씨 때문에 계획을 세우기가 어려웠습니다."
  ],
  ["She was doubtful about the success of the plan.", "그녀는 계획의 성공을 의심했습니다."],
  [
    "The indeterminate outcome left everyone on edge.",
    "불확실한 결과가 모두를 긴장하게 만들었습니다."
  ],
  [
    "He remained indecisive until the last moment.",
    "그는 마지막 순간까지 우유부단하게 남아 있었습니다."
  ],
  [
    "The vacillating opinions made it hard to reach a consensus.",
    "흔들리는 의견들로 합의에 도달하기 어려웠습니다."
  ],
  [
    "She was wavering between two choices, unable to commit.",
    "그녀는 두 가지 선택 사이에서 흔들리며 결정을 내리지 못했습니다."
  ],
  [
    "His irresolute nature made him an unreliable leader.",
    "그의 우유부단한 성격은 그를 신뢰할 수 없는 리더로 만들었습니다."
  ],
];

위젯 부분은 Padding, Column, Row의 단순 감과 노가다이지만 여기서 특별한 부분은 ListView.builder 안에 ListView.builder를 연속해서 사용했다. 그 부분을 한번 살펴 보겠다.

Scaffold(
  backgroundColor: Colors.grey.shade100,
  floatingActionButtonLocation:
      FloatingActionButtonLocation.miniCenterFloat,
  floatingActionButton: FloatingActionButton(
    onPressed: () => showWordDialog(context),
    child: Icon(Icons.add, color: Colors.white),
    backgroundColor: Colors.lightGreen,
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.all(Radius.circular(40)),
      side: BorderSide(
        style: BorderStyle.solid,
        width: 0.0,
        color: Colors.transparent,
      ),
    ),
  ),
  body: SafeArea(
    child: Padding(
      padding: const EdgeInsets.all(5),
      child: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: words.length,
              itemBuilder: (context, index) {
                return Padding(
                  padding: const EdgeInsets.symmetric(vertical: 1),
                  child: Card(
                    color: Colors.white,
                    shape: RoundedRectangleBorder(
                      borderRadius:
                          const BorderRadius.all(Radius.circular(5)),
                      side: BorderSide(
                        style: BorderStyle.solid,
                        width: 1.5,
                        color: Colors.grey.shade100,
                      ),
                    ),
                    child: Column(
                      children: [
                        ListTile(
                          title: Row(
                            mainAxisAlignment:
                                MainAxisAlignment.spaceBetween,
                            children: [
                              Expanded(
                                child: Text(
                                  words[index],
                                  style: const TextStyle(
                                    fontSize: 18,
                                    fontWeight: FontWeight.bold,
                                  ),
                                ),
                              ),
                              Row(
                                children: [
                                  Padding(
                                    padding: const EdgeInsets.fromLTRB(
                                        10, 10, 0, 0),
                                    child: Column(
                                      crossAxisAlignment:
                                          CrossAxisAlignment.end,
                                      children: const [
                                        Text(
                                          '정답률: 53%',
                                          style: TextStyle(
                                            fontSize: 12,
                                            color: Colors.grey,
                                          ),
                                        ),
                                        Text(
                                          '★★★☆☆',
                                          style: TextStyle(
                                            fontSize: 12,
                                            color: Colors.grey,
                                          ),
                                        ),
                                      ],
                                    ),
                                  ),
                                  IconButton(
                                    icon: const Icon(
                                      Icons.keyboard_arrow_right,
                                      color:
                                          Color.fromARGB(255, 38, 21, 21),
                                    ),
                                    onPressed: () {},
                                  ),
                                ],
                              ),
                            ],
                          ),
                          subtitle: Text(
                            meanings[index],
                            style: TextStyle(
                              fontSize: 14,
                            ),
                          ),
                          leading: const Icon(
                            Icons.volume_up,
                            color: Colors.lightGreen,
                          ),
                          tileColor: Colors.white,
                          contentPadding:
                              const EdgeInsets.fromLTRB(15, 0, 0, 5),
                          shape: RoundedRectangleBorder(
                            borderRadius:
                                const BorderRadius.all(Radius.circular(5)),
                            side: BorderSide(
                              style: BorderStyle.solid,
                              width: 1.5,
                              color: Colors.grey.shade100,
                            ),
                          ),
                        ),
                        Row(
                          children: [
                            Expanded(
                              child: Padding(
                                padding: const EdgeInsets.fromLTRB(
                                    15, 10, 0, 15),
                                child: Text(
                                  tags[index].join(' '),
                                  style: const TextStyle(
                                    fontSize: 14,
                                  ),
                                ),
                              ),
                            ),
                          ],
                        ),
                        ListView.builder(
                          shrinkWrap: true,
                          physics: const NeverScrollableScrollPhysics(),
                          itemCount: exampleSentences[index].length,
                          itemBuilder: (_, sentenceIndex) {
                            return Padding(
                              padding:
                                  const EdgeInsets.fromLTRB(15, 0, 15, 15),
                              child: Text(
                                exampleSentences[index][sentenceIndex],
                                style: TextStyle(
                                  fontSize: 12,
                                  color: Colors.grey.shade600,
                                ),
                              ),
                            );
                          },
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    ),
  ),
);

전체 코드에서 중복된 내부 ListView.builder만 보자.

ListView.builder(
  shrinkWrap: true,
  physics: const NeverScrollableScrollPhysics(),
  itemCount: exampleSentences[index].length,
  itemBuilder: (_, sentenceIndex) {
    return Padding(
      padding:
          const EdgeInsets.fromLTRB(15, 0, 15, 15),
      child: Text(
        exampleSentences[index][sentenceIndex],
        style: TextStyle(
          fontSize: 12,
          color: Colors.grey.shade600,
        ),
      ),
    );
  },
),

기존과 차이가 나는 점은 shrinkWrapphysics 프로퍼티가 쓰였다. 나도 간단하게 스택오버플로우에서 답변을 긁어왔지만 중복해서 리스트뷰를 추가하려면 저 프로퍼티를 저렇게 추가해야한다. 프로퍼티 내용은 다음과 같다.

  • shrinkWrap: true: ListView가 필요한 만큼의 공간만 차지하게 하여 크기를 축소.
  • physics: const NeverScrollableScrollPhysics(): ListView가 스크롤되지 않도록 함.

원본에는 이걸 사용하면 해결된다는 내용만 나와있지만, 개인적으로 해석해보겠다.
ListView는 기본적으로 스크롤뷰를 제공하는데 중복 스크롤뷰가 되지 않도록해준다. 그리고 리스트의 각 아이템에 맞게 크기를 fit 하도록한다. 스크롤이 두 번 사용되면 기본적으로 스크롤 뷰가 무한한 크기를 가져서 레이아웃이 충돌날 수도 있고 어떤 객체에 스크롤을 적용해야할 지 혼란이 생길 수 있기 때문이다.

 

https://stackoverflow.com/questions/53465394/flutter-listview-builder-inside-another-listview

 

Flutter - Listview.builder inside another Listview

I want my screen to be scrollable so I put everything in a Listview. Now I want to show another Listview inside to show details of a List. When I try this an error is thrown - " Expanded widgets m...

stackoverflow.com

 

나머지 Card 에 들어간 위젯들의 위치는 정말 감으로만 했고 코드가 직관적이기 때문에 설명을 생략한다.

결과

변경된 단어장