Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
表是移動(dòng)端經(jīng)常使用的一種視圖展示方式,在Flutter中提供了ListView和GridView。
為了可能展示出更好的效果,我這里提供了一段Json數(shù)據(jù),所以我們可以先學(xué)習(xí)一下Json解析。
一. JSON讀取和解析
在開發(fā)中,我們經(jīng)常會(huì)使用本地JSON或者從服務(wù)器請(qǐng)求數(shù)據(jù)后回去到JSON,拿到JSON后通常會(huì)將JSON轉(zhuǎn)成Model對(duì)象來進(jìn)行后續(xù)的操作,因?yàn)檫@樣操作更加的方便,也更加的安全。
所以學(xué)習(xí)JSON的相關(guān)操作以及讀取JSON后如何轉(zhuǎn)成Model對(duì)象對(duì)于Flutter開發(fā)也非常重要。
JSON也屬于一種資源,所以在使用之前需要先進(jìn)行相關(guān)的配置
我們之前在學(xué)習(xí)使用Image組件時(shí),用到了本地圖片,本地圖片必須在pubspec.yaml中進(jìn)行配置:
JSON資源讀取
如果我們希望讀取JSON資源,可以使用package:flutter/services.dart包中的rootBundle。
在rootBundle中有一個(gè)loadString方法,可以去加載JSON資源
Future<String> loadString(String key, { bool cache=true }) async {
...省略具體代碼,可以自行查看源碼
}
代碼如下:(不要試圖拷貝這個(gè)代碼去運(yùn)行,是沒辦法運(yùn)行的)
import 'package:flutter/services.dart' show rootBundle;
// 打印讀取的結(jié)果是一個(gè)字符串
rootBundle.loadString("assets/yz.json").then((value)=> print(value));
JSON字符串轉(zhuǎn)化
拿到JSON字符串后,我們需要將其轉(zhuǎn)成成我們熟悉的List和Map類型。
我們可以通過dart:convert包中的json.decode方法將其進(jìn)行轉(zhuǎn)化
代碼如下:
// 1.讀取json文件
String jsonString=await rootBundle.loadString("assets/yz.json");
// 2.轉(zhuǎn)成List或Map類型
final jsonResult=json.decode(jsonString);
對(duì)象Model定義
將JSON轉(zhuǎn)成了List和Map類型后,就可以將List中的一個(gè)個(gè)Map轉(zhuǎn)成Model對(duì)象,所以我們需要定義自己的Model
class Anchor {
String nickname;
String roomName;
String imageUrl;
Anchor({
this.nickname,
this.roomName,
this.imageUrl
});
Anchor.withMap(Map<String, dynamic> parsedMap) {
this.nickname=parsedMap["nickname"];
this.roomName=parsedMap["roomName"];
this.imageUrl=parsedMap["roomSrc"];
}
}
1.3. JSON解析代碼
上面我們給出了解析的一個(gè)個(gè)步驟,下面我們給出完整的代碼邏輯
這里我單獨(dú)創(chuàng)建了一個(gè)anchor.dart的文件,在其中定義了所有的相關(guān)代碼:
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
import 'dart:async';
class Anchor {
String nickname;
String roomName;
String imageUrl;
Anchor({
this.nickname,
this.roomName,
this.imageUrl
});
Anchor.withMap(Map<String, dynamic> parsedMap) {
this.nickname=parsedMap["nickname"];
this.roomName=parsedMap["roomName"];
this.imageUrl=parsedMap["roomSrc"];
}
}
Future<List<Anchor>> getAnchors() async {
// 1.讀取json文件
String jsonString=await rootBundle.loadString("assets/yz.json");
// 2.轉(zhuǎn)成List或Map類型
final jsonResult=json.decode(jsonString);
// 3.遍歷List,并且轉(zhuǎn)成Anchor對(duì)象放到另一個(gè)List中
List<Anchor> anchors=new List();
for (Map<String, dynamic> map in jsonResult) {
anchors.add(Anchor.withMap(map));
}
return anchors;
}
二. ListView組件
移動(dòng)端數(shù)據(jù)量比較大時(shí),我們都是通過列表來進(jìn)行展示的,比如商品數(shù)據(jù)、聊天列表、通信錄、朋友圈等。
在Android中,我們可以使用ListView或RecyclerView來實(shí)現(xiàn),在iOS中,我們可以通過UITableView來實(shí)現(xiàn)。
在Flutter中,我們也有對(duì)應(yīng)的列表Widget,就是ListView。
ListView可以沿一個(gè)方向(垂直或水平方向,默認(rèn)是垂直方向)來排列其所有子Widget。
一種最簡(jiǎn)單的使用方式是直接將所有需要排列的子Widget放在ListView的children屬性中即可。
我們來看一下直接使用ListView的代碼演練:
class MyHomeBody extends StatelessWidget {
final TextStyle textStyle=TextStyle(fontSize: 20, color: Colors.redAccent);
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("人的一切痛苦,本質(zhì)上都是對(duì)自己無(wú)能的憤怒。", style: textStyle),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("人活在世界上,不可以有偏差;而且多少要費(fèi)點(diǎn)勁兒,才能把自己保持到理性的軌道上。", style: textStyle),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("我活在世上,無(wú)非想要明白些道理,遇見些有趣的事。", style: textStyle),
)
],
);
}
}
2.2.2. ListTile的使用
在開發(fā)中,我們經(jīng)常見到一種列表,有一個(gè)圖標(biāo)或圖片(Icon),有一個(gè)標(biāo)題(Title),有一個(gè)子標(biāo)題(Subtitle),還有尾部一個(gè)圖標(biāo)(Icon)。
這個(gè)時(shí)候,我們可以使用ListTile來實(shí)現(xiàn):
class MyHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
ListTile(
leading: Icon(Icons.people, size: 36,),
title: Text("聯(lián)系人"),
subtitle: Text("聯(lián)系人信息"),
trailing: Icon(Icons.arrow_forward_ios),
),
ListTile(
leading: Icon(Icons.email, size: 36,),
title: Text("郵箱"),
subtitle: Text("郵箱地址信息"),
trailing: Icon(Icons.arrow_forward_ios),
),
ListTile(
leading: Icon(Icons.message, size: 36,),
title: Text("消息"),
subtitle: Text("消息詳情信息"),
trailing: Icon(Icons.arrow_forward_ios),
),
ListTile(
leading: Icon(Icons.map, size: 36,),
title: Text("地址"),
subtitle: Text("地址詳情信息"),
trailing: Icon(Icons.arrow_forward_ios),
)
],
);
}
}
我們可以通過設(shè)置 scrollDirection 參數(shù)來控制視圖的滾動(dòng)方向。
我們通過下面的代碼實(shí)現(xiàn)一個(gè)水平滾動(dòng)的內(nèi)容:
class MyHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
scrollDirection: Axis.horizontal,
itemExtent: 200,
children: <Widget>[
Container(color: Colors.red, width: 200),
Container(color: Colors.green, width: 200),
Container(color: Colors.blue, width: 200),
Container(color: Colors.purple, width: 200),
Container(color: Colors.orange, width: 200),
],
);
}
}
通過構(gòu)造函數(shù)中的children傳入所有的子Widget有一個(gè)問題:默認(rèn)會(huì)創(chuàng)建出所有的子Widget。
但是對(duì)于用戶來說,一次性構(gòu)建出所有的Widget并不會(huì)有什么差異,但是對(duì)于我們的程序來說會(huì)產(chǎn)生性能問題,而且會(huì)增加首屏的渲染時(shí)間。
我們可以ListView.build來構(gòu)建子Widget,提供性能。
ListView.build適用于子Widget比較多的場(chǎng)景,該構(gòu)造函數(shù)將創(chuàng)建子Widget交給了一個(gè)抽象的方法,交給ListView進(jìn)行管理,ListView會(huì)在真正需要的時(shí)候去創(chuàng)建子Widget,而不是一開始就全部初始化好。
該方法有兩個(gè)重要參數(shù):
我們還是通過一個(gè)簡(jiǎn)單的案例來認(rèn)識(shí)它:
class MyHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 100,
itemExtent: 80,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("標(biāo)題$index"), subtitle: Text("詳情內(nèi)容$index"));
}
);
}
}
在之前,我們搞了一個(gè)yz.json數(shù)據(jù),我們現(xiàn)在動(dòng)態(tài)的來通過JSON數(shù)據(jù)展示一個(gè)列表。
思考:這個(gè)時(shí)候是否依然可以使用StatelessWidget:
答案:不可以,因?yàn)楫?dāng)前我們的數(shù)據(jù)是異步加載的,剛開始界面并不會(huì)展示數(shù)據(jù)(沒有數(shù)據(jù)),后面從JSON中加載出來數(shù)據(jù)(有數(shù)據(jù))后,再次展示加載的數(shù)據(jù)。
展示代碼如下:
import 'model/anchor.dart';
...省略中間代碼
class MyHomeBody extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return MyHomeBodyState();
}
}
class MyHomeBodyState extends State<MyHomeBody> {
List<Anchor> anchors=[];
// 在初始化狀態(tài)的方法中加載數(shù)據(jù)
@override
void initState() {
getAnchors().then((anchors) {
setState(() {
this.anchors=anchors;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.network(
anchors[index].imageUrl,
fit: BoxFit.fitWidth,
width: MediaQuery.of(context).size.width,
),
SizedBox(height: 8),
Text(anchors[index].nickname, style: TextStyle(fontSize: 20),),
SizedBox(height: 5),
Text(anchors[index].roomName)
],
),
);
},
);
}
}
ListView.separated可以生成列表項(xiàng)之間的分割器,它除了比ListView.builder多了一個(gè)separatorBuilder參數(shù),該參數(shù)是一個(gè)分割器生成器。
下面我們看一個(gè)例子:奇數(shù)行添加一條藍(lán)色下劃線,偶數(shù)行添加一條紅色下劃線:
class MySeparatedDemo extends StatelessWidget {
Divider blueColor=Divider(color: Colors.blue);
Divider redColor=Divider(color: Colors.red);
@override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: Icon(Icons.people),
title: Text("聯(lián)系人${index+1}"),
subtitle: Text("聯(lián)系人電話${index+1}"),
);
},
separatorBuilder: (BuildContext context, int index) {
return index % 2==0 ? redColor : blueColor;
},
itemCount: 100
);
}
}
GridView用于展示多列的展示,在開發(fā)中也非常常見,比如直播App中的主播列表、電商中的商品列表等等。
在Flutter中我們可以使用GridView來實(shí)現(xiàn),使用方式和ListView也比較相似。
我們先學(xué)習(xí)GridView構(gòu)造函數(shù)的使用方法
一種使用GridView的方式就是使用構(gòu)造函數(shù)來創(chuàng)建,和ListView對(duì)比有一個(gè)特殊的參數(shù):gridDelegate
gridDelegate用于控制交叉軸的item數(shù)量或者寬度,需要傳入的類型是SliverGridDelegate,但是它是一個(gè)抽象類,所以我們需要傳入它的子類:
SliverGridDelegateWithFixedCrossAxisCount
SliverGridDelegateWithFixedCrossAxisCount({
@required double crossAxisCount, // 交叉軸的item個(gè)數(shù)
double mainAxisSpacing=0.0, // 主軸的間距
double crossAxisSpacing=0.0, // 交叉軸的間距
double childAspectRatio=1.0, // 子Widget的寬高比
})
代碼演練:
class MyGridCountDemo extends StatelessWidget {
List<Widget> getGridWidgets() {
return List.generate(100, (index) {
return Container(
color: Colors.purple,
alignment: Alignment(0, 0),
child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
);
});
}
@override
Widget build(BuildContext context) {
return GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1.0
),
children: getGridWidgets(),
);
}
}
SliverGridDelegateWithMaxCrossAxisExtent
SliverGridDelegateWithMaxCrossAxisExtent({
double maxCrossAxisExtent, // 交叉軸的item寬度
double mainAxisSpacing=0.0, // 主軸的間距
double crossAxisSpacing=0.0, // 交叉軸的間距
double childAspectRatio=1.0, // 子Widget的寬高比
})
代碼演練:
class MyGridExtentDemo extends StatelessWidget {
List<Widget> getGridWidgets() {
return List.generate(100, (index) {
return Container(
color: Colors.purple,
alignment: Alignment(0, 0),
child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
);
});
}
@override
Widget build(BuildContext context) {
return GridView(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1.0
),
children: getGridWidgets(),
);
}
}
前面兩種方式也可以不設(shè)置delegate
可以分別使用:GridView.count構(gòu)造函數(shù)和GridView.extent構(gòu)造函數(shù)實(shí)現(xiàn)相同的效果,這里不再贅述。
和ListView一樣,使用構(gòu)造函數(shù)會(huì)一次性創(chuàng)建所有的子Widget,會(huì)帶來性能問題,所以我們可以使用GridView.build來交給GridView自己管理需要?jiǎng)?chuàng)建的子Widget。
我們直接使用之前的數(shù)據(jù)來進(jìn)行代碼演練:
class _GridViewBuildDemoState extends State<GridViewBuildDemo> {
List<Anchor> anchors=[];
@override
void initState() {
getAnchors().then((anchors) {
setState(() {
this.anchors=anchors;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: GridView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1.2
),
itemCount: anchors.length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Image.network(anchors[index].imageUrl),
SizedBox(height: 5),
Text(anchors[index].nickname, style: TextStyle(fontSize: 16),),
Text(anchors[index].roomName, maxLines: 1, overflow: TextOverflow.ellipsis,)
],
),
);
}
),
);
}
}
我們考慮一個(gè)這樣的布局:一個(gè)滑動(dòng)的視圖中包括一個(gè)標(biāo)題視圖(HeaderView),一個(gè)列表視圖(ListView),一個(gè)網(wǎng)格視圖(GridView)。
我們?cè)趺纯梢宰屗鼈冏龅浇y(tǒng)一的滑動(dòng)效果呢?使用前面的滾動(dòng)是很難做到的。
Flutter中有一個(gè)可以完成這樣滾動(dòng)效果的Widget:CustomScrollView,可以統(tǒng)一管理多個(gè)滾動(dòng)視圖。
在CustomScrollView中,每一個(gè)獨(dú)立的,可滾動(dòng)的Widget被稱之為Sliver。
補(bǔ)充:Sliver可以翻譯成裂片、薄片,你可以將每一個(gè)獨(dú)立的滾動(dòng)視圖當(dāng)做一個(gè)小裂片。
因?yàn)槲覀冃枰押芏嗟腟liver放在一個(gè)CustomScrollView中,所以CustomScrollView有一個(gè)slivers屬性,里面讓我們放對(duì)應(yīng)的一些Sliver:
我們簡(jiǎn)單演示一下:SliverGrid+SliverPadding+SliverSafeArea的組合
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: <Widget>[
SliverSafeArea(
sliver: SliverPadding(
padding: EdgeInsets.all(8),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment(0, 0),
color: Colors.orange,
child: Text("item$index"),
);
},
childCount: 20
),
),
),
)
],
);
}
}
這里我使用官方的示例程序,將SliverAppBar+SliverGrid+SliverFixedExtentList做出如下界面:
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return showCustomScrollView();
}
Widget showCustomScrollView() {
return new CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('Coderwhy Demo'),
background: Image(
image: NetworkImage(
"https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg",
),
fit: BoxFit.cover,
),
),
),
new SliverGrid(
gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return new Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: new Text('grid item $index'),
);
},
childCount: 10,
),
),
SliverFixedExtentList(
itemExtent: 50.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return new Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: new Text('list item $index'),
);
},
childCount: 20
),
),
],
);
}
}
對(duì)于滾動(dòng)的視圖,我們經(jīng)常需要監(jiān)聽它的一些滾動(dòng)事件,在監(jiān)聽到的時(shí)候去做對(duì)應(yīng)的一些事情。
比如視圖滾動(dòng)到底部時(shí),我們可能希望做上拉加載更多;
比如滾動(dòng)到一定位置時(shí)顯示一個(gè)回到頂部的按鈕,點(diǎn)擊回到頂部的按鈕,回到頂部;
比如監(jiān)聽滾動(dòng)什么時(shí)候開始,什么時(shí)候結(jié)束;
在Flutter中監(jiān)聽滾動(dòng)相關(guān)的內(nèi)容由兩部分組成:ScrollController和ScrollNotification。
在Flutter中,Widget并不是最終渲染到屏幕上的元素(真正渲染的是RenderObject),因此通常這種監(jiān)聽事件以及相關(guān)的信息并不能直接從Widget中獲取,而是必須通過對(duì)應(yīng)的Widget的Controller來實(shí)現(xiàn)。
ListView、GridView的組件控制器是ScrollController,我們可以通過它來獲取視圖的滾動(dòng)信息,并且可以調(diào)用里面的方法來更新視圖的滾動(dòng)位置。
另外,通常情況下,我們會(huì)根據(jù)滾動(dòng)的位置來改變一些Widget的狀態(tài)信息,所以ScrollController通常會(huì)和StatefulWidget一起來使用,并且會(huì)在其中控制它的初始化、監(jiān)聽、銷毀等事件。
我們來做一個(gè)案例,當(dāng)滾動(dòng)到1000位置的時(shí)候,顯示一個(gè)回到頂部的按鈕:
class MyHomePage extends StatefulWidget {
@override
State<StatefulWidget> createState()=> MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
ScrollController _controller;
bool _isShowTop=false;
@override
void initState() {
// 初始化ScrollController
_controller=ScrollController();
// 監(jiān)聽滾動(dòng)
_controller.addListener(() {
var tempSsShowTop=_controller.offset >=1000;
if (tempSsShowTop !=_isShowTop) {
setState(() {
_isShowTop=tempSsShowTop;
});
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ListView展示"),
),
body: ListView.builder(
itemCount: 100,
itemExtent: 60,
controller: _controller,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("item$index"));
}
),
floatingActionButton: !_isShowTop ? null : FloatingActionButton(
child: Icon(Icons.arrow_upward),
onPressed: () {
_controller.animateTo(0, duration: Duration(milliseconds: 1000), curve: Curves.ease);
},
),
);
}
}
如果我們希望監(jiān)聽什么時(shí)候開始滾動(dòng),什么時(shí)候結(jié)束滾動(dòng),這個(gè)時(shí)候我們可以通過NotificationListener。
案例: 列表滾動(dòng), 并且在中間顯示滾動(dòng)進(jìn)度
class MyHomeNotificationDemo extends StatefulWidget {
@override
State<StatefulWidget> createState()=> MyHomeNotificationDemoState();
}
class MyHomeNotificationDemoState extends State<MyHomeNotificationDemo> {
int _progress=0;
@override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: (ScrollNotification notification) {
// 1.判斷監(jiān)聽事件的類型
if (notification is ScrollStartNotification) {
print("開始滾動(dòng).....");
} else if (notification is ScrollUpdateNotification) {
// 當(dāng)前滾動(dòng)的位置和總長(zhǎng)度
final currentPixel=notification.metrics.pixels;
final totalPixel=notification.metrics.maxScrollExtent;
double progress=currentPixel / totalPixel;
setState(() {
_progress=(progress * 100).toInt();
});
print("正在滾動(dòng):${notification.metrics.pixels} - ${notification.metrics.maxScrollExtent}");
} else if (notification is ScrollEndNotification) {
print("結(jié)束滾動(dòng)....");
}
return false;
},
child: Stack(
alignment: Alignment(.9, .9),
children: <Widget>[
ListView.builder(
itemCount: 100,
itemExtent: 60,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("item$index"));
}
),
CircleAvatar(
radius: 30,
child: Text("$_progress%"),
backgroundColor: Colors.black54,
)
],
),
);
}
}
原文來自:https://mp.weixin.qq.com/s/rdmfeUECLtnZz6GeQKzOqA(侵權(quán)刪除)
端開發(fā)"酷炫技巧--虛擬滾動(dòng)。
大家好,今天我們來介紹前端開發(fā)中的酷炫技巧--虛擬滾動(dòng)。什么是虛擬滾動(dòng)?
虛擬滾動(dòng)也稱為窗口化滾動(dòng),是一種優(yōu)化技術(shù),用于在固定大小的界面中顯示大量?jī)?nèi)容,它只渲染可視區(qū)域的內(nèi)容而不是全部。
為什么使用虛擬滾動(dòng)?
虛擬滾動(dòng)有很多優(yōu)點(diǎn)。首先它可以顯著提高網(wǎng)頁(yè)性能,特別是對(duì)于需要顯示大量數(shù)據(jù)的應(yīng)用程序。其次它可以提高用戶體驗(yàn),因?yàn)橛脩舨恍枰却袃?nèi)容都加載完畢才能開始瀏覽。
如何實(shí)現(xiàn)虛擬滾動(dòng)?實(shí)現(xiàn)虛擬滾動(dòng)主要需要兩步:
·首先,需要使用JavaScript來測(cè)量視口的大小,并確定需要渲染哪些內(nèi)容。
·然后,需要使用CSS來定位和呈現(xiàn)這些內(nèi)容。
常見問題和解決方案在使用虛擬滾動(dòng)時(shí)可能會(huì)遇到一些問題,例如如果用戶滾動(dòng)速度過快,可能會(huì)出現(xiàn)內(nèi)容閃爍的問題。為了解決這個(gè)問題,你可以使用防抖(debounce)技術(shù)來限制頻繁的渲染。
虛擬滾動(dòng)是一個(gè)非常強(qiáng)大的前端技術(shù),可以幫助你優(yōu)化網(wǎng)頁(yè)性能并提高用戶體驗(yàn)。
希望這個(gè)視頻能幫助你了解并實(shí)現(xiàn)虛擬滾動(dòng)!
者 | 大澈
大家好,我是大澈!
又是好久沒更文了,前陣子駕著新車回了趟老家,很“幸運(yùn)”的經(jīng)歷了平原縣地震的余波。
回想當(dāng)時(shí),半夜凌晨,房屋晃動(dòng),如同身處過山車,一切都很不真實(shí)。雖然震感時(shí)間很短暫,但是現(xiàn)在依舊讓我記憶猶新,人類在大自然面前真的是太渺小了,很多時(shí)候真的是力不從心。
所以,真心想和大家說一句,生活不易,及時(shí)行樂,珍惜身邊人,且行且珍惜。
ONE
需求分析,問題描述
一、需求
一個(gè)可以滾動(dòng)的菜單,為它添加一個(gè)可以下拉滾動(dòng)的提示。要求滾動(dòng)到菜單最底部時(shí),隱藏下拉滾動(dòng)的提示,否則讓其一直顯示。
二、問題
1、如何實(shí)現(xiàn)滾動(dòng)條效果?
2、如何判斷是否滾動(dòng)到底部?
TWO
解決問題,答案速覽
實(shí)現(xiàn)代碼如下,復(fù)制粘貼即可直接使用。
代碼中滾動(dòng)條的實(shí)現(xiàn)使用了element的el-scrollbar組件。組件中包裹的第一個(gè)div,指的是需要滾動(dòng)的視圖。組件中包裹的第二個(gè)div,指的是下拉滾動(dòng)提示的圖標(biāo),這里根據(jù)需求進(jìn)行設(shè)置,可以更換靜態(tài)的或者那種閃爍跳躍的動(dòng)態(tài)提示圖標(biāo)。
// 1、模版
<el-scrollbar max-height="calc(100vh - 84px)" @scroll="handleScroll" ref="myScrollbar">
<div class="sideBarIn"></div>
<div class="pcSign pcIcon" v-if="isShowIcon">
<img class="iconImg" src="../assets/images/common/xiaGery.png"></div>
</el-scrollbar>
// 2、邏輯
// 滾動(dòng)條事件
const handleScroll=(val)=> {
// 防止Scrollbar實(shí)例為空
if (!myScrollbar.value) {
return
}
// 判斷是否滾動(dòng)到底部
let isScrollToEnd=Number(myScrollbar.value.wrapRef.scrollTop.toFixed(0))
+ Number(myScrollbar.value.wrapRef.clientHeight.toFixed(0))===Number(myScrollbar.value.wrapRef.scrollHeight.toFixed(0));
if (isScrollToEnd) {
// 滾動(dòng)到底部的處理邏輯
isShowIcon.value=false
} else {
// 非滾動(dòng)到底部的處理邏輯
isShowIcon.value=true
}
}
// 3、樣式
.pcIcon {
width:100%;
height: 100px;
position: absolute;
bottom: -4px;
left: 0;
text-align: center;
line-height: 130px;
background: linear-gradient(to bottom, rgba(234, 234, 234, 0.5), rgba(234, 234, 234, 1));
.iconImg {
width: 20px;
height: 20px;
}
}
.pcSign{
display: block;
}
THREE
問題解析,知識(shí)總結(jié)
一、如何實(shí)現(xiàn)滾動(dòng)條效果?
實(shí)現(xiàn)滾動(dòng)條效果有兩種實(shí)現(xiàn)方式:利用css的overflow: scroll屬性、利用element的el-scrollbar組件。
1、overflow: scroll屬性
在div元素上添加 overflow-y: scroll; css屬性,就能顯示出一個(gè)滾動(dòng)條,如果不指定是x或y軸,則水平和垂直都會(huì)出現(xiàn)滾動(dòng)條。
當(dāng)然,前提是你需要指定div元素的高度或者最大高度。
2、el-scrollbar組件
一般在vue項(xiàng)目中,我們可直接使用element的el-scrollbar組件,因?yàn)楣俜綖槲覀兲峁┝嗽S多API,以及各種適配優(yōu)化。
el-scrollbar組件的屬性如下:
el-scrollbar組件的事件如下:
el-scrollbar組件的實(shí)例屬性如下:
二、如何判斷是否滾動(dòng)到底部?
這里判斷是否滾動(dòng)到底部的關(guān)鍵在于scrollTop+clientHeight是否等于scrollHeight的值。只有當(dāng)滾動(dòng)的距離+可視區(qū)域的高度,與scrollHeight相等時(shí),才證明滾動(dòng)條滾動(dòng)到了底部。
同樣的,如果scrollHeight與可視區(qū)域的高度直接就相等時(shí),又說明元素不可以滾動(dòng),也就沒有滾動(dòng)條。這一點(diǎn)在有此需求時(shí),可以進(jìn)行實(shí)用。
元素的幾個(gè)寬高屬性釋義如下:
- END -
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。