作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263
郵箱 :291148484@163.com
本文地址:https://blog.csdn.net/qq_28550263/article/details/134149018
1. 一些AppBar效果
在 Flutter 中,最簡(jiǎn)單的 appbar 就是 Appbar 組件,它沒(méi)有任何難點(diǎn),任何剛剛?cè)腴T的開(kāi)發(fā)著在 Flutter 腳手架創(chuàng)建的計(jì)數(shù)器應(yīng)用中就使用了它。但是現(xiàn)實(shí)的開(kāi)發(fā)場(chǎng)景中,Appbar 組件往往難以適應(yīng)復(fù)雜的需求場(chǎng)景。
比如以下是 “王者營(yíng)地” APP (即王者榮耀官方的社區(qū)應(yīng)用) 的 Appbar,這個(gè)據(jù)說(shuō)也是 Flutter 實(shí)現(xiàn)的:
這種向下滾動(dòng)時(shí),AppBar出現(xiàn),向上滾到頂AppBar逐漸隱藏的效果還是比較簡(jiǎn)單,可以直接使用SliverAppBar。
與之相比,下面這個(gè)高德地圖滾動(dòng)方向與王者營(yíng)地是相反的,并且還帶有一個(gè)相遇于下面內(nèi)容部分似乎在向下跑的圖片:
這些效果當(dāng)然不是使用 Appbar 組件做的。
在 Flutter 中,最簡(jiǎn)單的隨著滾動(dòng)帶有顯影效果的appbar可以使用 SliverAppBar 組件實(shí)現(xiàn)。
但是實(shí)際上appbar僅僅是一個(gè)應(yīng)用頂部導(dǎo)航的效果不僅僅局限于 Flutter 原生的 Appbar 和 SliverAppBar 。實(shí)際上,為了實(shí)現(xiàn)更加靈活的 appbar,還可以考慮基于 Sliver 協(xié)議 實(shí)現(xiàn)外觀類似的組件,將它放在頁(yè)面的頂部,著很好理解,因?yàn)樵?寫 Web 的時(shí)候就可以這樣干(事實(shí)上我就是這樣干過(guò))。因此先從一個(gè)類似的 Web 中手寫的例子看起。
2. 一個(gè)Web移動(dòng)端上的復(fù)雜AppBar例子
先看效果吧(其實(shí)就是模仿上面的高德地圖的大概效果):
(附:感謝圖片來(lái)源地址,我在網(wǎng)絡(luò)隨便拿的,僅僅用于此示例,祝愿貴App、貴店鋪生意紅火。)
這個(gè)Appbar以及相關(guān)其它動(dòng)畫效果,本質(zhì)上都是與滾動(dòng)相關(guān)的??偨Y(jié)起來(lái),我們要實(shí)現(xiàn)的效果如下:
-
頁(yè)面上方有一個(gè)固定的Appbar,背景顏色為藍(lán)色,內(nèi)部包含一個(gè)輸入框;
-
頁(yè)面的上半部分有一個(gè)背景圖像,通過(guò)
#bg-item
元素實(shí)現(xiàn),并且這個(gè)元素在最下層; -
頁(yè)面的下半部分分為兩部分:
-
#scroll-item
:一個(gè)滾動(dòng)元素,包含一張圖片,它會(huì)隨著頁(yè)面的滾動(dòng)而滾動(dòng),但在背景元素之上。 -
#content
:一個(gè)內(nèi)容區(qū)域,包含一個(gè)標(biāo)題,它也會(huì)隨著頁(yè)面的滾動(dòng)而滾動(dòng),但在滾動(dòng)元素之上。
-
-
使用JavaScript監(jiān)聽(tīng)頁(yè)面的滾動(dòng)事件,根據(jù)滾動(dòng)距離動(dòng)態(tài)改變以下效果:
- Appbar的背景顏色透明度,使其在頁(yè)面滾動(dòng)時(shí)逐漸變?yōu)橥该鳌?/li>
- Appbar內(nèi)文字的顏色透明度,同樣逐漸變?yōu)橥该鳌?/li>
- Appbar內(nèi)輸入框的透明度,使其在頁(yè)面滾動(dòng)時(shí)逐漸變?yōu)橥该鳌?/li>
- 滾動(dòng)元素的位置,隨著頁(yè)面滾動(dòng)而向上移動(dòng),實(shí)現(xiàn)視差效果。
- 內(nèi)容區(qū)域的位置,隨著頁(yè)面滾動(dòng)而向上移動(dòng),也實(shí)現(xiàn)視差效果。
-
使用CSS的變量
--my-height
定義了滾動(dòng)元素的初始高度,以便在JavaScript中使用。
在 Web 中,要實(shí)現(xiàn)總體思路是通過(guò) JavaScript 監(jiān)聽(tīng)頁(yè)面滾動(dòng)事件,根據(jù)滾動(dòng)距離動(dòng)態(tài)改變頁(yè)面元素的樣式,從而實(shí)現(xiàn)Appbar背景顏色和文字顏色的漸變效果,以及滾動(dòng)元素和內(nèi)容區(qū)域的視差滾動(dòng)效果。這種交互設(shè)計(jì)可以提升頁(yè)面的視覺(jué)吸引力和用戶體驗(yàn)。
Web代碼如下:
<!DOCTYPE html>
<html>
<head>
<!-- 作者信息 -->
<!-- Author: 李俊才 -->
<!-- Email: 291149494@163.com -->
<!-- 許可證信息 -->
<!-- LICENSE: MIT -->
<style>
body {
margin: 0;
padding: 0;
--my-height: 460px; // 定義一個(gè)CSS變量,表示滾動(dòng)元素的初始高度
}
#appbar {
position: fixed;
top: 0;
width: 100%;
height: 50px;
background: rgba(0, 123, 255, 1);
color:white;
font-size: large;
transition: background 0.3s;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
padding-right: 20px;
z-index: 3; // 設(shè)置appbar在最上層
}
#appbar-input {
padding: 5px;
border-radius: 5px;
border: 1px solid white;
margin-right: 20px;
}
#appbar-input::placeholder {
color: white;
}
#bg-item {
position: fixed;
top: 0;
width: 100%;
height: var(--my-height);
z-index: 0; // 設(shè)置背景元素在最下層
}
#bg-item img {
width: 100%;
height: auto;
object-fit: cover;
}
#scroll-item {
position: absolute;
top: var(--my-height);
width: 100%;
z-index: 1; // 設(shè)置滾動(dòng)元素在背景元素之上
}
#scroll-item img {
width: 100%;
height: auto;
object-fit: cover;
}
#content {
position: absolute;
top: var(--my-height);
height: 610px;
width: 100%;
background-color: #ececec;
z-index: 2; // 設(shè)置內(nèi)容元素在滾動(dòng)元素之上
}
</style>
</head>
從CSS部分就可以看出,實(shí)際上歸納起來(lái),我把頁(yè)面拆分為了 appbar、背景圖層、滾動(dòng)圖層、內(nèi)容層,通過(guò) z-index
屬性來(lái)控制層級(jí)關(guān)系(可以結(jié)合下面html部分)。代碼接上:
<!-- 作者信息 -->
<!-- Author: 李俊才 -->
<!-- Email: 291149494@163.com -->
<!-- 許可證信息 -->
<!-- LICENSE: MIT -->
<body>
<div id="appbar">
<div>我是appbar</div>
<input id="appbar-input" type="text" placeholder="我是輸入框">
</div>
<div id="bg-item">
<img src="https://gw.alicdn.com/imgextra/i4/2212013333132/O1CN01DetIjE1Z0VMx4155t_!!2212013333132.jpg_Q75.jpg_.webp" alt="Image">
</div>
<div id="scroll-item">
<img src="https://gitee.com/jacklee1995/example-pictures/raw/master/piano/jonathanvasquez8950_piano_795a8e31-a910-48aa-9eae-45b1602f7cba.png" alt="Image"/>
</div>
<div id="content">
<h1>我是內(nèi)容區(qū)域</h1>
</div>
<script>
// 獲取 appbar 以及appbar內(nèi)的輸入框元素節(jié)點(diǎn)
const appbar = document.getElementById('appbar');
const appbarInput = document.getElementById('appbar-input');
// 獲取滾動(dòng)項(xiàng)節(jié)點(diǎn),這是一個(gè)與內(nèi)容節(jié)點(diǎn)差速滾動(dòng)的元素
const scrollItem = document.getElementById('scroll-item');
// 獲取內(nèi)容節(jié)點(diǎn)
const content = document.getElementById('content');
// 定義頁(yè)面滾動(dòng)的最大距離,在這個(gè)距離內(nèi)appbar的背景顏色和文字顏色會(huì)發(fā)生變化
const maxScroll = 280;
window.addEventListener("scroll", function() {
let scrollTop = window.pageYOffset || document.documentElement.scrollTop;
let opacity = (scrollTop / maxScroll);
opacity = opacity < 0 ? 0 : opacity;
// 根據(jù)滾動(dòng)距離動(dòng)態(tài)改變appbar的背景顏色透明度
appbar.style.background = `rgba(0, 123, 255, ${opacity})`;
// 根據(jù)滾動(dòng)距離動(dòng)態(tài)改變appbar內(nèi)文字的顏色透明度
appbar.style.color = `rgba(255, 255, 255, ${opacity})`;
appbarInput.style.opacity = `${opacity}`;
// 根據(jù)滾動(dòng)距離動(dòng)態(tài)改變滾動(dòng)項(xiàng)的位置
scrollItem.style.top = `calc(var(--my-height) - ${scrollTop}px)`;
// 根據(jù)滾動(dòng)距離動(dòng)態(tài)改變內(nèi)容的位置
content.style.top = `calc(var(--my-height) - ${scrollTop / 2}px)`;
});
</script>
</body>
</html>
所有的控制邏輯我在 scroll 監(jiān)聽(tīng)中完成的,實(shí)際上就是對(duì)各個(gè)層的控制。
3. Flutter小試:我就不用SliverAppBar了
其實(shí)我的意思就是想自定義與滾動(dòng)效果相關(guān)的appbar。既然需要滾動(dòng)控制,而且使用 SliverAppBar 套參數(shù)也不是那么方便,即使這樣的效果無(wú)非是控制一個(gè)盒子的透明度變化以及其它的位置移動(dòng)。說(shuō)來(lái)說(shuō)去, SliverAppBar 也可以通過(guò)其它的組件實(shí)現(xiàn),最后轉(zhuǎn)為 Sliver 協(xié)議放入CustomScrollView不就可以了。
整體思路和 Web 中差不多,由于是分層,需要使用 Stack 與 Positioned 組件(相比于上一小節(jié)在Web中我們使用的是html+CSS的z-index,然后在 JS 代碼中動(dòng)態(tài)調(diào)整opacity)。
整體思路完全一樣。于是,可以將上一個(gè)小節(jié)的 Web 代碼 改用Flutter實(shí)現(xiàn)一下:
import 'package:flutter/material.dart';
// Author: 李俊才
// Email: 291149494@163.com
// https://blog.csdn.net/qq_28550263/article/details/134149018
class WebAppBarScaffold extends StatefulWidget {
const WebAppBarScaffold({Key? key}) : super(key: key);
State<WebAppBarScaffold> createState() => _WebAppBarScaffoldState();
}
class _WebAppBarScaffoldState extends State<WebAppBarScaffold> {
double _opacity = 0.0;
double _offsetImage = 0.0;
double _offsetContent = 0.0;
Widget build(BuildContext context) {
return Scaffold(
body: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo is ScrollUpdateNotification) {
setState(() {
_opacity = scrollInfo.metrics.pixels / 280;
_opacity = _opacity.clamp(0.0, 1.0);
_offsetImage = scrollInfo.metrics.pixels * 1.5; // 修改這里
_offsetContent = scrollInfo.metrics.pixels / 2; // 修改這里
});
}
return true;
},
child: Stack(
children: <Widget>[
Positioned(
top: 0,
child: Image.network(
'https://gw.alicdn.com/imgextra/i4/2212013333132/O1CN01DetIjE1Z0VMx4155t_!!2212013333132.jpg_Q75.jpg_.webp',
width: MediaQuery.of(context).size.width,
fit: BoxFit.cover,
),
),
Positioned(
top: 460 - _offsetImage,
child: Image.network(
'https://gitee.com/jacklee1995/example-pictures/raw/master/piano/jonathanvasquez8950_piano_795a8e31-a910-48aa-9eae-45b1602f7cba.png',
width: MediaQuery.of(context).size.width,
fit: BoxFit.cover,
),
),
Positioned(
top: 460 - _offsetContent,
child: Container(
color: Colors.grey[200],
width: MediaQuery.of(context).size.width,
height: 610,
child: const Center(child: Text('我是內(nèi)容區(qū)域')),
),
),
Positioned(
top: 0,
child: Container(
width: MediaQuery.of(context).size.width,
height: 50,
color: Colors.blue.withOpacity(_opacity),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
'我是appbar',
style: TextStyle(
color: Colors.white.withOpacity(_opacity)),
),
),
Padding(
padding: const EdgeInsets.only(right: 20),
child: Opacity(
opacity: _opacity,
child: const SizedBox(
width: 200, // 限定寬度
child: TextField(
decoration: InputDecoration(
hintText: '我是輸入框',
hintStyle: TextStyle(color: Colors.white),
),
),
),
),
),
],
),
),
),
ListView.builder(
itemCount: 1,
itemBuilder: (context, index) {
return Container(
height: MediaQuery.of(context).size.height * 2);
},
),
],
),
),
);
}
}
這里其實(shí)有一個(gè)小缺陷,就是滾動(dòng)過(guò)頭我沒(méi)去做處理了。這里是一點(diǎn)小數(shù)學(xué)問(wèn)題,就是計(jì)算中間層的圖片相對(duì)于內(nèi)容層圖片滾動(dòng)的位移值恰好為圖片的高度時(shí),讓中間滾動(dòng)圖片層和內(nèi)容層一起滾動(dòng),就可以避免看到中間滾動(dòng)圖層相比于內(nèi)容層越來(lái)越遠(yuǎn)。讀者可以嘗試修改一下代碼。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-743164.html
4. 結(jié)論
其實(shí)本文主要目的還是比較??梢钥吹?,使用Web事件監(jiān)聽(tīng)處理滾動(dòng)事件,其實(shí)和Flutter中使用滾動(dòng)控制差不多。對(duì)于一些復(fù)雜的效果,沒(méi)有必要拘束于現(xiàn)有的組件,可以基于一些更加基礎(chǔ)的部件構(gòu)成復(fù)雜的效果。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-743164.html
到了這里,關(guān)于Flutter vs 前端 雜談:SliverAppBar、手動(dòng)實(shí)現(xiàn)Appbar、前端Html+JS怎么實(shí)現(xiàn)滾動(dòng)變化型Appbar - 比較的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!