溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Flutter仿網(wǎng)易怎么實(shí)現(xiàn)廣告卡片3D翻轉(zhuǎn)效果

發(fā)布時(shí)間:2022-04-27 10:30:08 來源:億速云 閱讀:173 作者:zzz 欄目:開發(fā)技術(shù)

這篇文章主要介紹“Flutter仿網(wǎng)易怎么實(shí)現(xiàn)廣告卡片3D翻轉(zhuǎn)效果”的相關(guān)知識(shí),小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“Flutter仿網(wǎng)易怎么實(shí)現(xiàn)廣告卡片3D翻轉(zhuǎn)效果”文章能幫助大家解決問題。

    先看下網(wǎng)易新聞的效果:

    Flutter仿網(wǎng)易怎么實(shí)現(xiàn)廣告卡片3D翻轉(zhuǎn)效果

    實(shí)現(xiàn)思路

    1、獲取各種距離

    看圖:

    Flutter仿網(wǎng)易怎么實(shí)現(xiàn)廣告卡片3D翻轉(zhuǎn)效果

    思路: 如上圖,狀態(tài)欄高度和AppBar的高度我們都可以得到,屏幕的高度我們也可以得到,那么自然我們就可以計(jì)算出內(nèi)容區(qū)域的高度,拿到內(nèi)容區(qū)域高度我們先放到一邊,接下來我們需要獲取廣告區(qū)域距離AppBar的距離,這是一個(gè)進(jìn)行翻轉(zhuǎn)核心數(shù)據(jù),這里我們可以通過GlobalKey獲取這個(gè)組件的渲染對象RenderObject并轉(zhuǎn)化為RenderBox,通過RenderBox我們可以獲取到這個(gè)組件在屏幕上的坐標(biāo),這樣我們拿到這個(gè)坐標(biāo)Y軸的值就是當(dāng)前組件距離頂部的距離

    核心代碼:

    // 這里我們獲取相對于屏幕左上角組件的坐標(biāo)y軸
    
    GlobalKey _globalKey = GlobalKey();
    
    RenderBox? renderBox =
        _globalKey.currentContext?.findRenderObject() as RenderBox?;
    double? dy = renderBox?.localToGlobal(Offset.zero).dy;

    接下來我們就可以計(jì)算出幾個(gè)關(guān)鍵數(shù)據(jù):

    狀態(tài)欄高度:stateHeight = MediaQuery.of(context).padding.top;已知。

    AppBar高度:appBarHeight = 56; 默認(rèn)高度 已知。

    內(nèi)容區(qū)域高度:contentHeight = MediaQuery.of(context).size.height - stateHeight -appBarHeight;

    假設(shè)我們廣告區(qū)域的高度是200,廣告組件的高度一般都是固定的。

    得出:廣告上方距離頂部的最大距離:maxHeight= contentheight - 200;

    還記得我們上面獲取的dy值嗎,這個(gè)值是當(dāng)前廣告上面距離屏幕頂部的距離,那么我們就可以得出當(dāng)前廣告距離AppBar底部的距離: bannerY = dy - appBarHeight - stateHeight;

    同理可以得出當(dāng)前廣告的滑動(dòng)距離:scrollY = contentheight - 200 - bannerY;

    滑動(dòng)的最大距離就是:maxSrollY = contentHeight - bannerHeight;

    2、翻轉(zhuǎn)

    搞定了這些數(shù)據(jù),接下來的工作就比較簡單了,我們使用Transform組件來進(jìn)行180度的翻轉(zhuǎn)就可以了,
    獲取當(dāng)前滑動(dòng)的比例,那就是當(dāng)前滑動(dòng)距離/最大滑動(dòng)距離,也就是 scrollY/maxHeight; 接下來我們看下Transform這個(gè)類,

    代碼:

    Container(
        padding: EdgeInsetsDirectional.only(
            start: 20, end: 20, top: 30, bottom: 30),
        height: bannerHeight,
        key: _globalKey,
        child: Transform(
          alignment: Alignment.center, //相對于坐標(biāo)系原點(diǎn)的對齊方式 從中間翻轉(zhuǎn)
          transform: Matrix4.identity()//這是一個(gè)矩陣變換類,可以對組件的坐標(biāo)進(jìn)行翻轉(zhuǎn),有興趣可以了解下
            ..rotateX(0)// 翻轉(zhuǎn)X軸
            ..rotateY(angle),// 翻轉(zhuǎn)Y軸 這里需要傳入角度
          child: Image.asset(
            "images/img.png",
            fit: BoxFit.fill,
          ),
        ));

    通過rotateY就可以將組件繞著Y軸進(jìn)行翻轉(zhuǎn),也就達(dá)到了我們想要的3D效果,上面我們得到了滑動(dòng)比例,那么我們就可以用這個(gè)比例乘以PI值,刷新頁面就可以了唄,接下來我們通過滑動(dòng)監(jiān)聽將這個(gè)數(shù)字進(jìn)行更新看下效果:

    核心代碼:

    double h = MediaQuery.of(context).size.height; //屏幕高度
    RenderBox? renderBox =
        _globalKey.currentContext?.findRenderObject() as RenderBox?;
    double? dy = renderBox?.localToGlobal(Offset.zero).dy;
    // 56 AppBar 高度
    if (dy != null) {
      // 廣告距離AppBar Y軸距離
      var bannerY = dy - appBarHeight - stateHeight;
      // 主內(nèi)容區(qū)域高度
      var contentHeight = h - appBarHeight - stateHeight;
      if (bannerY + bannerHeight < contentHeight && bannerY > 0) {
        setState(() {
          //滑動(dòng)的距離
          angle = pi * ((contentHeight - bannerHeight - bannerY) /
                  (contentHeight - bannerHeight));
         
        });
      }
    }

    效果:

    Flutter仿網(wǎng)易怎么實(shí)現(xiàn)廣告卡片3D翻轉(zhuǎn)效果

    翻轉(zhuǎn)效果確實(shí)實(shí)現(xiàn)了,不過怎么看著有點(diǎn)不對勁呢,這里有兩個(gè)問題:

    1、劃上去翻過來的圖片直接鏡像了。

    Flutter仿網(wǎng)易怎么實(shí)現(xiàn)廣告卡片3D翻轉(zhuǎn)效果

    2、當(dāng)我們滑動(dòng)到一半的時(shí)候,兩邊的寬度是一致的,3D效果不明顯。

    Flutter仿網(wǎng)易怎么實(shí)現(xiàn)廣告卡片3D翻轉(zhuǎn)效果

    其實(shí)這兩個(gè)問題都很好解決,

    第一個(gè)滑動(dòng)角度問題,我們滑動(dòng)到90度進(jìn)行翻過來的時(shí)候只需要將角度+180度進(jìn)行翻轉(zhuǎn)即可。這樣就相當(dāng)于翻了360度,最后自然會(huì)回到原來的圖片的樣子。

    第二個(gè)我們需要設(shè)置Transform的一個(gè)屬性..setEntry(3, 2, 0.002),讓卡片翻轉(zhuǎn)過程中看起來遠(yuǎn)小近大的效果。

    我們加上這兩個(gè)屬性再看看效果:

    Flutter仿網(wǎng)易怎么實(shí)現(xiàn)廣告卡片3D翻轉(zhuǎn)效果

    這樣看著是不是效果就好多了。

    這里我只簡單了插入了一條廣告,如果有多個(gè)廣告建議用一個(gè)Map對象將Key存儲(chǔ)起來,因?yàn)橐粋€(gè)Key只能對應(yīng)一個(gè)組件。

    完整代碼

    class ListViewWidgetDemo extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return ListViewState();
      }
    }
    
    class ListViewState extends State<ListViewWidgetDemo> {
      List<NewsListBean> lis = <NewsListBean>[];
    
      late ScrollController _scrollController = ScrollController();
      String imageUrl =
          "https://images.unsplash.com/photo-1451187580459-43490279c0fa?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60";
    
      GlobalKey _globalKey = GlobalKey();
    
      double angle = 0;
      double bannerHeight = 200;
    
      @override
      void initState() {
        WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
          _scrollController.addListener(() {
            double appBarHeight = 56;
            double stateHeight = MediaQuery.of(context).padding.top;
            double h = MediaQuery.of(context).size.height; //屏幕高度
    
            RenderBox? renderBox =
                _globalKey.currentContext?.findRenderObject() as RenderBox?;
            double? dy = renderBox?.localToGlobal(Offset.zero).dy;
            // 56 AppBar 高度
            if (dy != null) {
              // 廣告距離AppBar Y軸距離
              var bannerY = dy - appBarHeight - stateHeight;
              // 主內(nèi)容區(qū)域高度
              var contentHeight = h - appBarHeight - stateHeight;
              if (bannerY + bannerHeight < contentHeight && bannerY > 0) {
                setState(() {
                  //滑動(dòng)的距離
                  angle = pi *
                      ((contentHeight - bannerHeight - bannerY) /
                          (contentHeight - bannerHeight));
                  // 前半部分 0-90 后半部分 270-360
                  if (angle >= (pi / 2)) {
                    angle = angle + pi;
                  }
                });
              }
            }
          });
        });
    
        super.initState();
        for (int i = 0; i < 40; i++) {
          lis.add(NewsListBean(
            i.isEven ? 0 : 1,
            "資訊標(biāo)題$i",
            imageUrl,
          ));
        }
        // 插入廣告
        lis.insert(12, NewsListBean(2, "廣告", imageUrl));
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text("仿網(wǎng)易新聞廣告卡片翻轉(zhuǎn)"),
            ),
            body: ListView.builder(
                controller: _scrollController,
                shrinkWrap: true,
                scrollDirection: Axis.vertical,
                itemCount: lis.length,
                itemBuilder: (context, index) {
                  return _listWidget(lis[index]);
                }));
      }
    
      Widget _listWidget(NewsListBean bean) {
        late Widget widget;
        switch (bean.type) {
          case 0:
            widget = Container(
                height: 50,
                padding: EdgeInsetsDirectional.only(start: 20),
                alignment: Alignment.centerLeft,
                color: Colors.blue[200],
                child: Text(
                  bean.title,
                  style: TextStyle(),
                ));
            break;
          case 1:
            widget = Row(
              children: [
                Expanded(
                  child: Container(
                      height: 80,
                      alignment: Alignment.center,
                      color: Colors.red[200],
                      margin: EdgeInsets.all(10),
                      child: Text(bean.title)),
                ),
                Image.network(
                  bean.image,
                  width: 40,
                  height: 40,
                )
              ],
            );
            break;
          case 2:
            widget = Container(
                padding: EdgeInsetsDirectional.only(
                    start: 20, end: 20, top: 30, bottom: 30),
                height: bannerHeight,
                key: _globalKey,
                child: Transform(
                  alignment: Alignment.center, //相對于坐標(biāo)系原點(diǎn)的對齊方式
                  transform: Matrix4.identity()
                    ..setEntry(3, 2, 0.002)
                    ..rotateX(0)
                    ..rotateY(angle),
                  child: Image.asset(
                    "images/img.png",
                    fit: BoxFit.fill,
                  ),
                ));
            break;
          default:
            widget = SizedBox();
            break;
        }
        return widget;
      }
    }
    class NewsListBean {
      //資訊類型 0:資訊無圖 1:資訊有圖 2:3d廣告
      final int type;
      final bool isFirst;
      final String title;
      final String image;
    
      NewsListBean(this.type, this.title, this.image, {this.isFirst = false});
    }

    關(guān)于“Flutter仿網(wǎng)易怎么實(shí)現(xiàn)廣告卡片3D翻轉(zhuǎn)效果”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI