目標
使用flutter快速開發(fā) Android 和 iOS 的簡易的新聞客戶端
API使用的是 showapi(易源數據) 加載熱門微信文章
效果對比
| Android | iOS |
|---|---|
![]() image
|
![]() image
|
![]() image
|
![]() image
|
![]() image
|
![]() image
|
簡介
這是一個建議的新聞客戶端 頁面非常簡單
通過網絡請求加載 分類數據 和 分類詳情數據 (key都在代碼里了,輕量使用~)
UI上幾乎是沒有任何特點
使用BottomNavigationBar 分成3個控制器
首頁使用DefaultTabController管理內容
相關依賴:
http: "^0.11.3" #網絡請求
cached_network_image: "^0.4.1" #圖片加載
cupertino_icons: ^0.1.2 #icon
flutter_webview_plugin: ^0.1.6 #webview
shared_preferences: ^0.4.2 #持久化數據
url_launcher: ^3.0.3 #調用系統(tǒng)瀏覽器
代碼
使用單例來保存數據
由于分類原則上是沒有變化的,我這里就使用單例來保存從API請求的分類數據,減少請求次數(API請求次數有限)
class UserSinglen {
List<WeType> allTypes = [];
static final UserSinglen _singleton = new UserSinglen._internal();
factory UserSinglen() {
return _singleton;
}
UserSinglen._internal();
}
使用Shared保存數據
保存當前選中的分類
class Shared {
//保存分類
static Future saveSelectedType(List<String> list) async {
SharedPreferences pre = await SharedPreferences.getInstance();
await pre.setStringList("selectedTypeIds",list);
print(list);
return;
}
//獲取已選擇的分類
static Future<List<String>> getSelectedType() async {
SharedPreferences pre = await SharedPreferences.getInstance();
try {
List<String> typeIds = pre.getStringList("selectedTypeIds");
print("typeids = $typeIds");
return typeIds;
} catch (e) {
return null;
}
}
}
BottomNavigationBar的使用
構建NavigationIcon
為底部的icon封裝,方便找到對應的控制器
class NavigationIcon {
NavigationIcon({
Widget icon,
Widget title,
TickerProvider vsync,
}) : item = new BottomNavigationBarItem(
icon: icon,
title: title,
),
controller = new AnimationController(
duration: kThemeAnimationDuration,
vsync: vsync,
);
final BottomNavigationBarItem item;
final AnimationController controller;
}
構建當前控制器
當前控制器是Stateful類型,刷新頁面
初始化3個控制器
class Index extends StatefulWidget {
const Index({ Key key }) : super(key: key);
@override
State<StatefulWidget> createState() => new IndexState();
}
class IndexState extends State<Index> with TickerProviderStateMixin {
List<NavigationIcon> navigationIcons;
List<StatefulWidget> pageList;
int currentPageIndex = 0;
StatefulWidget currentWidget;
@override
void initState() {
// TODO: implement initState
super.initState();
navigationIcons = <NavigationIcon>[
new NavigationIcon(
icon: new Icon(Icons.home),
title: new Text("首頁"),
vsync: this,
),
new NavigationIcon(
icon: new Icon(Icons.category),
title: new Text("分類"),
vsync: this,
),
new NavigationIcon(
icon: new Icon(Icons.info),
title: new Text("關于"),
vsync: this,
)
];
pageList = <StatefulWidget>[
new Home(key: new Key("home"),),
new Category(key: new Key("category"),),
new About(),
];
for(NavigationIcon view in navigationIcons) {
view.controller.addListener(rebuild());
}
currentWidget = pageList[currentPageIndex];
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
for (NavigationIcon view in navigationIcons) {
view.controller.dispose();
}
}
rebuild(){
print("rebuild");
setState(() {});
}
@override
Widget build(BuildContext context) {
final BottomNavigationBar bottomNavigationBar = new BottomNavigationBar(
items: navigationIcons.map(((i) => i.item)).toList(),
currentIndex: currentPageIndex,
fixedColor: Config.mainColor,
type: BottomNavigationBarType.fixed,
onTap: (i) {
setState(() {
navigationIcons[i].controller.reverse();
currentPageIndex = i;
navigationIcons[i].controller.forward();
currentWidget = pageList[i];
});
},
);
return new MaterialApp(
theme: Config.themeData,
home: new Scaffold(
body: Center(
child: currentWidget,
),
bottomNavigationBar: bottomNavigationBar,
),
);
}
}
首頁
首頁實時獲取存儲在本地的已選擇分類,與單例中的所有分類做對比,獲取對應的類型id
(shared_preferences只能存儲基本數據類型)
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new HomeState();
}
}
class HomeState extends State<Home> {
bool _isloading = true;
List<WeType> _list;
@override
void initState() {
// TODO: implement initState
super.initState();
API.featchTypeListData((List<WeType> callback) {
Shared.getSelectedType().then((onValue) {
print(onValue);
if (onValue != null) {
print("lentch = ");
print(onValue.length);
setState(() {
_isloading = false;
_list = callback.where((t) => onValue.contains(t.id)).toList();
});
}
});
}, errorback: (error) {
print("error:$error");
});
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return _isloading
? new Scaffold(
appBar: new AppBar(
title: new Text("loading..."),
),
body: new Center(
child: new CircularProgressIndicator(
backgroundColor: Colors.black,
),
),
)
: new DefaultTabController(
length: _list.length,
child: new Scaffold(
appBar: new AppBar(
title: new Text("新聞"),
bottom: new TabBar(
isScrollable: true,
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
tabs: _list.map((f) => new Tab(text: f.name)).toList()),
),
body: new TabBarView(
children:
_list.map((f) => new Content(channelId: f.id)).toList(),
)),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
}
分類
這個頁面也很簡單,將已選擇的分類id存進shared_preferences中就行
class Category extends StatefulWidget {
const Category({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() => new CategoryState();
}
class CategoryState extends State<Category> {
List<WeType> _list = [];
bool _isloading = true;
@override
void initState() {
// TODO: implement initState
super.initState();
API.featchTypeListData((List<WeType> callback) {
Shared.getSelectedType().then((onValue) {
setState(() {
_isloading = false;
_list = callback
.map((f) => new WeType(f.id, f.name, onValue.contains(f.id)))
.toList();
});
});
}, errorback: (error) {
print("error:$error");
});
}
_onTapButton(WeType type) {
setState(() {
_list = _list.map((WeType f) {
if (f.id == type.id) {
f.isSelected = !f.isSelected;
}
return f;
}).toList();
Shared.saveSelectedType(
_list.where((t) => t.isSelected).map((f) => f.id).toList());
});
}
_selectAll() {
print("all");
var r = _list.where((t) => t.isSelected).toList();
bool res = r.length < _list.length ? true : false;
setState(() {
_list = _list.map((f) => new WeType(f.id, f.name, res)).toList();
Shared.saveSelectedType(
_list.where((t) => t.isSelected).map((f) => f.id).toList());
});
}
@override
Widget build(BuildContext context) {
return _isloading
? CircularProgressIndicator()
: new Scaffold(
appBar: new AppBar(
title: new Text("分類"),
actions: <Widget>[
new FlatButton(
onPressed: _selectAll,
child: new Center(
child: new Text(
"全選",
style: new TextStyle(color: Colors.white),
)),
)
],
),
body: new GridView.count(
// Create a grid with 2 columns. If you change the scrollDirection to
// horizontal, this would produce 2 rows.
crossAxisCount: 3,
crossAxisSpacing: 0.0,
childAspectRatio: 2.0,
// Generate 100 Widgets that display their index in the List
children: _list
.map((f) => new FlatButton(
padding: const EdgeInsets.all(10.0),
onPressed: () {
_onTapButton(f);
},
child: new Center(child: _Button(f.name, f.isSelected)),
))
.toList(),
));
}
}
class _Button extends StatelessWidget {
final String title;
final bool isSelected;
_Button(this.title, this.isSelected);
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
new Container(
child: new Center(
child: new Text(title),
),
decoration: new BoxDecoration(
color: isSelected ? Colors.blue[200] : Colors.white,
borderRadius: new BorderRadius.all(
const Radius.circular(20.0),
),
border: new Border.all(
color: isSelected ? Colors.white : Colors.blue[100],//邊框顏色
width: 1.0,//邊框寬度
),
),
),
new Container(
child: new Center(
child: isSelected
? new Icon(
Icons.check,
color: Colors.white,
)
: null,
),
),
],
);
}
}
代碼地址
Flutter-news
歡迎點贊





