🐵

ニューモフィズムデザインを入力の合わせて高さを可変させる

2024/07/14に公開

概要

Flutterで個人開発をしリリースした以下のアプリをニューモフィズムデザインを作りました。
その後、入力画面を改行したら合わせて高さを可変するようにアプデしました。
https://apps.apple.com/jp/app/memoplace/id6479640629

やり方

ニューモフィズムについては以下の記事をご確認ください。
https://zenn.dev/hiromuto/articles/c97e8ff07daa5f

以下の様になります。

自分のHooksの理解も含めてHooksを使い実現しました。
TextFormFieldをContainerでラップしているのでContainerの高さ(containerHeight)をuseStateで、変更前の行としてpreviousLineCount(初期値を1)をuseRefで値を定義します。

今回は改行した際にContainerの高さを可変させたいのでTextFieldの変更をuseEffectで監視をし、
textControllerにtextListenerという関数をリスナーとして追加します。
これにより、テキストフィールドの内容が変更されるたびにtextListener関数が呼び出されます。

TextListener関数の中身はTextFieldの値の改行の数(currentLineCount)とpreviousLineCountを比較してcurrentLineCountの方が大きければ
currentLineCount+=20,少なければ-=20し再描画され、Containerの高さが変わる仕組みとなっています。

そして、最後にコンポーネントがアンマウントされる際にリスナーをクリーンアップするための関数を返します。useEffectフックのクリーンアップをされることで不要になったリスナーがメモリに残り続けることを防ぎ、パフォーマンスの問題や意図しない挙動を避けることができる様です。



  Widget build(BuildContext context, WidgetRef ref) {
    final TextEditingController textController = useTextEditingController();
    final previousLineCount = useRef(1);

    final textMemo = ref.watch(memoProvider);
    User? user = FirebaseAuth.instance.currentUser;
    final FocusNode focusNode = useFocusNode();
    bool shouldDisplayContainer = checkedMarkerNames.isNotEmpty &&
        checkedMarkerNames.any((name) => name != null && name.isNotEmpty);
    Color baseColor = Colors.orange.shade100;

    final containerHeight = useState<double>(60.0);
    useEffect(() {
      void textListener() {
        final currentLineCount =
            '\n'.allMatches(textController.text).length + 1;
        if (currentLineCount > previousLineCount.value) {
          containerHeight.value += 20;
        } else if (currentLineCount < previousLineCount.value) {
          containerHeight.value -= 20;
        }
        previousLineCount.value = currentLineCount;
      }

      textController.addListener(textListener);
      return () => textController.removeListener(textListener);
    }, [textController]);

    return GestureDetector(
      onTap: () {
        FocusScope.of(context).unfocus();
      },
      child: Scaffold(
        resizeToAvoidBottomInset: false,
        body: Center(
          child: SingleChildScrollView(
            child: Container(
              padding: const EdgeInsets.all(32),
              child: Column(
                children: <Widget>[
                  SizedBox(height: MediaQuery.of(context).size.height / 20),
                  Container(
                    height: containerHeight.value,
                    width: 350,
                    decoration: BoxDecoration(
                      color: baseColor,
                      borderRadius: BorderRadius.circular(30),
                      boxShadow: [
                        BoxShadow(
                          color: Colors.orange.withOpacity(0.4),
                          spreadRadius: 5,
                          blurRadius: 7,
                          offset: const Offset(-3, -3),
                        ),
                        BoxShadow(
                          color: Colors.white.withOpacity(0.6),
                          spreadRadius: 5,
                          blurRadius: 7,
                          offset: const Offset(3, 3),
                        ),
                      ],
                    ),
                    child: TextFormField(
                      focusNode: focusNode,
                      controller: textController,
                      maxLength: null,
                      maxLines: null,
                      keyboardType: TextInputType.multiline,
                      decoration: InputDecoration(
                        fillColor: Colors.orange.shade100,
                        filled: true,
                        isDense: true,
                        hintText: memo,
                        hintStyle: const TextStyle(
                            fontSize: 12, fontWeight: FontWeight.w100),
                        prefixIcon: const Icon(Icons.create),
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(32),
                          borderSide: BorderSide.none,
                        ),
                      ),
                      textAlign: TextAlign.left,
                      onChanged: (String value) async {
                        ref.read(memoProvider.notifier).state = value;
                      },
                    ),
                  ),

Discussion