Flutterカスタムドロップダウン選択ボックスの実装プロセス分析

一緒に書く習慣をつけましょう!「ナゲッツデイリーニュープラン・4月アップデートチャレンジ」に参加して5日目です。クリックしてイベントの詳細をご覧ください

  • 一部のリストページでは、上記の項目をフィルタリングする必要があることがよくあります。クリックして、ドロップダウンメニュー、複数選択、単一選択、リスト選択などを表示しますが、Flutterには、そのようなコンポーネントは既製ではありません。サードパーティの拡張機能は制限されることがあるので、自分で作成できるのが最善です。そうすれば、拡張しても便利になります。

最初にレンダリングを見てください。

4d675755-289e-4497-9351-d5c057784e74.gif

キーポイント:弹出、收回动画、状态改变、选项联动

  • アイデア:完全なドロップダウンボックスがヘッダーと特定のドロップダウンオプションで構成されていることがわかります。ヘッダーはドロップダウングループにリンクされています。ヘッダーは配列と見なされ、下のオプションは配列と見なされます。 、および2つの配列の数が同じで完全なドロップダウン選択ボックスを形成すると、リンケージ効果をより適切に制御できます。

まず、ポップアップとリトラクトを見てみましょう。高さが0から徐々に拡大してから0に徐々に縮小するコンポーネントと見なすことができます。アニメーションを使用してこのコンポーネントの高さを制御する限り、次のことができます。下に黒い透明度のグラデーションがあります。ここでは、上部のポップアップウィンドウのアニメーションに応じて、下の黒い影の変化を変更できます。
キーコード:

/// 下拉组件
@override
Widget build(BuildContext context) {
  return Column(
    children: [
      _MenuBuilder(
        animation: animation,
        // 这里显示我们需要的具体下拉框选项内容
        child: widget.children[widget.menuController.index],
      ),
      isShowShadow // 是否显示下方黑色阴影 只有下拉弹出才显示 这个地方我们就可以根据UI设计来进行高度自定义
          ? Expanded(
              child: InkWell(
              child: AnimatedBuilder(
                  animation: animation,
                  builder: (context, child) {
                  // 这里是下拉框下方阴影 点击阴影隐藏下拉框
                    return Container(
                      width: double.infinity,
                      height: MediaQuery.of(context).size.height,
                      color: Colors.black
                          .withOpacity(animation.value / (widget.height * 3)),
                    );
                  }),
              onTap: () {
                widget.menuController.hide();
              },
            ))
          : const SizedBox(),
    ],
  );
}

class _MenuBuilder extends StatelessWidget {
  final Animation<double> animation;
  final Widget child;

  const _MenuBuilder({required this.animation, required this.child});

// 这里我们主要用动画来控制下拉内容组件的高度
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        child: child,
        animation: animation,
        builder: (context, child) {
          return Container(
            color: Colors.white,
            height: animation.value,
            child: child,
          );
        });
  }
复制代码

次に、ヘッドのデザインを見てみましょう。ヘッドのデザインはもう少し複雑です。主に状態の変化とオプション間のリンクについてです。ここでは、新しい状態コントローラーを作成します。主に、ヘッドのいくつかの状態を制御します。ボタンの後のテキストまたは色の変更、選択した色の保存、リセットされた色の復元およびその他の状態、下部コントローラーは主に頭部の一部の状態を制御するためのものです。

/// 菜单控制器
class MenuController extends ChangeNotifier {
  // 当前组件是否显示 默认不显示 针对整个菜单数组
  bool isShow = false;

  // 显示当前组件title的文本 共用
  String title = "";

  // 显示哪个下拉框
  int index = 0;

  // 选择下拉框的的title 这个字段只有在真正选择的时候才会改变
  int titleIndex = 0;

  /// 更改Title
  changeTitle(int titleIndex, String? title) {
    this.titleIndex = titleIndex;
    this.title = title ?? "";
    hide();
  }

  // 显示下拉 index 为下拉哪一个菜单的index
  show(int index) {
    this.index = index;
    if (!isShow) {
      isShow = true;
    }
    notifyListeners();
  }

  // 隐藏 取消
  hide() {
    isShow = false;
    notifyListeners();
  }
}
复制代码

コントローラでは、ヘッダーデータも処理する必要があります。まず、オプションが選択されていない場合、ヘッダーにはデフォルトの配列があります。これは変更されないため、この配列を設定すると変更できません。次に、を作成します。現在表示されている配列である新しい動的配列。この配列のデフォルト値は、選択しなかったオプションのデフォルト値です。ここでは、表示配列を処理するためにヘッド状態の変化を監視する必要があります。
キーコード:主にヘッド状態の変化を処理するためのキーコードですが、このコードを明確にすれば基本的にはOKです。

@override
void initState() {
  super.initState();
  // changeTitles就是我们的显示数组
  changeTitles.addAll(widget.titles);
  for (var i = 0; i < changeTitles.length; i++) {
  //_chindren 是我们的头部组件数组
    _children.add(searchFilter(changeTitles[i], i));
  }
  widget.menuController.addListener(() {
   
    // 下拉 true 隐藏 false
    var isShow = widget.menuController.isShow;
    
    // 改变头部状态
    setState(() {
      if (widget.menuController.title != "") {
      // 说明当前选择了选项 赋值我选择的选项
        changeTitles[widget.menuController.titleIndex] =
            widget.menuController.title;
      } else {
      // 为空 说明当前的选项我清空了 赋值初始头部数组的数据
        changeTitles[widget.menuController.titleIndex] =
            widget.titles[widget.menuController.titleIndex];
      }
      // currentIndex 当前选择的index 默认-1 用来对比更新头部文字和颜色 
      // 如果下拉 更新当前选项inedx 如果隐藏说明没有选择任何一个下拉框 置为-1
      if (isShow && currentIndex < widget.titles.length) {
        currentIndex = widget.menuController.index;
      } else {
        currentIndex = -1;
      }
      // 每次下拉收回我们只需改变头部数据即可 changeTitles 永远都是显示的数组 直接全部更新到组件即可
      _children.clear();
      for (var i = 0; i < changeTitles.length; i++) {
        _children.add(searchFilter(changeTitles[i], i));
      }
    });
  });
}

// 这里就是一个简单的Row数组 按照百分比排列 也可以自定义不同宽度
@override
Widget build(BuildContext context) {
  return SizedBox(
    height: widget.headHeight ?? 45,
    child: Row(children: _children),
  );
}
复制代码

主にヘッダーのテキスト内容と色を更新します。ヘッダーの現在のoption=option ||またはオプション割り当ての名前が初期値と等しくない場合、このメニューが選択されていると見なして色を変更します。基本的なロジックはここで整理されており、ドロップダウンボックスのスタイルはビジネスに応じてカスタマイズできます。

Widget searchFilter(String name, int index) {
TextStyle(color: currentIndex == index || widget.titles[index] != name
                      ? widget.clickColor
                      : widget.defaultColor),
}
复制代码

添付の送信元アドレス:github.com/lixp185/flu…

要約する

アイデアは、ページ全体のドロップダウンコンポーネントを1つとして扱うことです。これの目的は、さまざまなオプションを切り替えるときに開閉する面倒なアニメーションを省略して、全体的なエクスペリエンスを実現することです。ダウンボックス、アニメーション、および状態管理に関連するスキルは、Flutterプログラミング思考における状態ベースのプログラミングとネイティブアプリケーションベースのプログラミングの最大の違いであると言えます。これら2つのスキルを真に習得することによってのみ、実装プロセスをよりよく理解できます。ドロップダウンボックス。

おすすめ

転載: juejin.im/post/7084173826761687070