Flutter SingleChildScrollView 仿淘宝商品详情实现

187 阅读2分钟

1、布局

```
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:xiaomishop/app/service/MyScreenExtention.dart';

import '../controllers/gooddetail_controller.dart';

class GooddetailView extends GetView<GooddetailController> {
  GooddetailView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        extendBodyBehindAppBar: true, //实现透明导航,
        appBar: _appBar(),
        body: _body());
  }

  _appBar() {
    return PreferredSize(
        key: controller.appBarKey,
        preferredSize: Size.fromHeight(110.hh),
        child: Obx(() => AppBar(
              //leading默认会有一个区块,如果需要设置自定义的leading大小
              // ,可以自定义一个Container,再嵌套一个Container
              //在里层的Container设置大小
              leading: Container(
                  alignment: Alignment.center,
                  // color: Colors.blue,
                  child: SizedBox(
                      width: 78.ww,
                      height: 78.ww,
                      child: ElevatedButton(
                          onPressed: () {
                            Get.back();
                          },
                          style: ButtonStyle(
                              backgroundColor:
                                  MaterialStateProperty.all(Colors.black12),
                              foregroundColor:
                                  MaterialStateProperty.all(Colors.white70),

                              ///注意去除ElevatedButton的默认pading
                              padding: MaterialStateProperty.all(
                                  const EdgeInsets.all(0)),
                              shape: MaterialStateProperty.all(
                                  const CircleBorder())),
                          child:
                              const Icon(Icons.arrow_back_ios_new_outlined)))),
              actions: [
                Container(
                    margin: EdgeInsets.only(right: 30.ww),
                    width: 78.ww,
                    height: 78.ww,
                    child: ElevatedButton(
                        onPressed: () {
                          Get.back();
                        },
                        style: ButtonStyle(
                            backgroundColor:
                                MaterialStateProperty.all(Colors.black12),
                            foregroundColor:
                                MaterialStateProperty.all(Colors.white70),

                            ///注意去除ElevatedButton的默认pading
                            padding: MaterialStateProperty.all(
                                const EdgeInsets.all(0)),
                            shape: MaterialStateProperty.all(
                                const CircleBorder())),
                        child: const Icon(Icons.share))),
                Container(
                    margin: EdgeInsets.only(right: 30.ww),
                    width: 78.ww,
                    height: 78.ww,
                    child: ElevatedButton(
                        onPressed: () {
                          Get.back();
                        },
                        style: ButtonStyle(
                            //设置button的背景
                            backgroundColor:
                                MaterialStateProperty.all(Colors.black12),
                            //设置button里元素的颜色,这里就是设置icon的字体颜色
                            foregroundColor:
                                MaterialStateProperty.all(Colors.white70),

                            ///注意去除ElevatedButton的默认pading
                            padding: MaterialStateProperty.all(
                                const EdgeInsets.all(0)),
                            shape: MaterialStateProperty.all(
                                const CircleBorder())),
                        child: const Icon(Icons.more_horiz_rounded)))
              ],
              title: controller.showTab.value
                  ? Container(
                      alignment: Alignment.center,
                      child: Obx(() => SizedBox(
                          width: 360.ww,
                          child: Row(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: controller.titleMap
                                  .map((v) => Expanded(
                                      flex: 1,
                                      child: InkWell(
                                          onTap: () {
                                            controller
                                                .setCurrentTitle(v["id"]!);
                                            //实现描点,跳到指定位置
                                            if (v["id"] == "1") {
                                              Scrollable.ensureVisible(
                                                  controller.key1.currentContext
                                                      as BuildContext);
                                            }
                                            if (v["id"] == "2") {
                                              Scrollable.ensureVisible(
                                                  controller.key2.currentContext
                                                      as BuildContext);
                                            }
                                            if (v["id"] == "3") {
                                              Scrollable.ensureVisible(
                                                  controller.key3.currentContext
                                                      as BuildContext);
                                            }
                                          },
                                          child: Column(
                                            children: [
                                              Text(v["title"]!,
                                                  style: TextStyle(
                                                      fontSize: 38.ss)),
                                              v["id"] ==
                                                      controller.titleId.value
                                                  ? Container(
                                                      height: 5.hh,
                                                      width: 60.ww,
                                                      color: Colors.red)
                                                  : SizedBox(
                                                      height: 5.hh,
                                                      width: 60.ww,
                                                    )
                                            ],
                                          ))))
                                  .toList()))))
                  : const Text(""),
              backgroundColor:
                  Colors.white70.withOpacity(controller.withOpacity.value),
              elevation: 0,
            )));
  }

  Widget _body() {
    return SingleChildScrollView(
        controller: controller.scrollController,
        child: Column(
          key: controller.containerKey,
          children: [
            Container(
                key: controller.key1,
                width: double.infinity,
                child: Image.asset("assets/images/xiaomiBanner2.png"),
                color: Colors.yellow),
            Container(
              key: controller.key2,
              width: double.infinity,
              height: 1080,
              color: Colors.red,
            ),
            Container(
              key: controller.key3,
              width: double.infinity,
              height: 1980,
              color: Colors.blue,
            )
          ],
        ));
  }
}
```

2、控制滑动

import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';

class GooddetailController extends GetxController {
  //TODO: Implement GooddetailController
  ScrollController scrollController = ScrollController();

  RxDouble withOpacity = 0.0.obs;
  GlobalKey key1 = GlobalKey();
  GlobalKey key2 = GlobalKey();
  GlobalKey key3 = GlobalKey();
  GlobalKey appBarKey = GlobalKey();

  GlobalKey containerKey = GlobalKey();

  final titleMap = [
    {"id": "1", "title": "商品"},
    {"id": "2", "title": "详情"},
    {"id": "3", "title": "评价"}
  ];
  Rx<String> titleId = "1".obs;

  RxBool showTab = false.obs;

  void setCurrentTitle(String id) {
    titleId.value = id;
    update();
  }

  final String name = "11";

  void setName() {
    print('hello world name');
  }

  @override
  void onInit() {
    super.onInit();
    print('GooddetailController onInit');
    WidgetsBinding.instance!.addPostFrameCallback(_afterLayout);
    initListener();
  }

  double h1 = 0;
  double h2 = 0;
  double h3 = 0;
  double appBarH = 0;

  /**
   * 获取SingleChildScrollView 各个元素的高度
   */
  void _afterLayout(_) {
    final RenderBox? containerRenderBox1 =
        key1.currentContext?.findRenderObject() as RenderBox?;
    if (containerRenderBox1 != null) {
      h1 = containerRenderBox1.size.height;
    }
    final RenderBox? containerRenderBox2 =
        key2.currentContext?.findRenderObject() as RenderBox?;
    if (containerRenderBox2 != null) {
      h2 = containerRenderBox2.size.height;
    }
    final RenderBox? containerRenderBox3 =
        key3.currentContext?.findRenderObject() as RenderBox?;
    if (containerRenderBox3 != null) {
      h3 = containerRenderBox3.size.height;
    }

    final RenderBox? containerRenderBox4 =
    appBarKey.currentContext?.findRenderObject() as RenderBox?;
    if (containerRenderBox4 != null) {
      appBarH = containerRenderBox4.size.height;
    }

    print('_afterLayout ${h1} ${h2} ${h3} ${appBarH}');
  }

  @override
  void onReady() {
    super.onReady();
    print('GooddetailController onReady');
  }

  @override
  void onClose() {
    super.onClose();
    print('GooddetailController onClose');
  }

  void initListener() {
    scrollController.addListener(() {
      var pixels = scrollController.position.pixels;
      print('${pixels}');
      withOpacity.value = pixels / appBarH;
      if (withOpacity.value > 1) {
        withOpacity.value = 1;
      }
      if (withOpacity.value >=0.1) {
        if (showTab.value == false) {
          showTab.value = true;
        }
      } else {
        if (showTab.value == true) {
          showTab.value = false;
        }
      }
      if (showTab.value) {
        if (pixels < h1-appBarH) {
          setCurrentTitle("1");
        } else if (pixels > h1-appBarH && pixels < h2+h1-appBarH) {
          setCurrentTitle("2");
        }
        if (pixels > h2+h1-appBarH) {
          setCurrentTitle("3");
        }
      }

      update();

    });
  }
}

3、实现效果

优速GIF大师2023-06-17 10-16-46.gif