发现:
列表显示:
继续接着上一次https://www.cnblogs.com/webor2006/p/13098765.html的功能往下进行编写,接下来则来写发现这个页面的功能,先来看一下最终它长啥样?
![](https://i-blog.csdnimg.cn/blog_migrate/2a8af366a7a988c64c3c143e5f15733e.png)
先来显示列表,关于列表的显示在我的界面中已经使用过了,这里再来快速的温故一下,目前发现只是占了一个位:
import 'package:flutter/material.dart';
class DiscoveryPage extends StatefulWidget {
@override
_DiscoveryPageState createState() => _DiscoveryPageState();
}
class _DiscoveryPageState extends State<DiscoveryPage> {
@override
Widget build(BuildContext context) {
return Center(
child: Text('发现'),
);
}
}
先定义个数组,代表列表选项:
import 'package:flutter/material.dart';
class DiscoveryPage extends StatefulWidget {
@override
_DiscoveryPageState createState() => _DiscoveryPageState();
}
class _DiscoveryPageState extends State<DiscoveryPage> {
List<Map<String, IconData>> blocks = [
{
'开源众包': Icons.pageview,
'开源软件': Icons.speaker_notes_off,
'码云推荐': Icons.screen_share,
'代码片段': Icons.assignment,
},
{
'扫一扫': Icons.camera_alt,
'摇一摇': Icons.camera,
},
{
'码云封面人物': Icons.person,
'线下活动': Icons.android,
}
];
@override
Widget build(BuildContext context) {
return Center(
child: Text('发现'),
);
}
}
为啥这次定义的数据分开了呢?
![](https://i-blog.csdnimg.cn/blog_migrate/b1e13d357b1c9840394dc6cb31b1a465.png)
这是因为本身这次的列表界面就有分组存在,看一下界面效果图就知道了:
![](https://i-blog.csdnimg.cn/blog_migrate/8255e8decd4d80d3656d278019078f00.png)
接下来继续, 先来显示一个轮廓:
![](https://i-blog.csdnimg.cn/blog_migrate/1cbad2a406e111a0366b5d743709c73c.png)
运行:
![](https://i-blog.csdnimg.cn/blog_migrate/09e124cde78c004a54677ae4b0e2be86.png)
接下来则每个块中得填充列表项,也就是ListView中再次嵌ListView,这种用法咱们还是第一次用,来瞅一下:
import 'package:flutter/material.dart';
class DiscoveryPage extends StatefulWidget {
@override
_DiscoveryPageState createState() => _DiscoveryPageState();
}
class _DiscoveryPageState extends State<DiscoveryPage> {
List<Map<String, IconData>> blocks = [
{
'开源众包': Icons.pageview,
'开源软件': Icons.speaker_notes_off,
'码云推荐': Icons.screen_share,
'代码片段': Icons.assignment,
},
{
'扫一扫': Icons.camera_alt,
'摇一摇': Icons.camera,
},
{
'码云封面人物': Icons.person,
'线下活动': Icons.android,
}
];
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: blocks.length,
itemBuilder: (context, index) {
return Container(
height: 200.0,
decoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 1.0,
color: Color(0xffaaaaaa),
),
bottom: BorderSide(
width: 1.0,
color: Color(0xffaaaaaa),
),
),
),
child: ListView.separated(
itemBuilder: (context, itemIndex) {
return InkWell(
onTap: () {},
child: Container(
child: ListTile(
leading: Icon(blocks[index].values.elementAt(itemIndex)),
title: Text(blocks[index].keys.elementAt(itemIndex)),
trailing: Icon(Icons.arrow_forward_ios),
),
),
);
},
separatorBuilder: (context, itemIndex) {
return Divider();
},
itemCount: blocks[index].length),
);
},
);
}
}
运行看一下:
![](https://i-blog.csdnimg.cn/blog_migrate/d57755a236188305cdc18bd367053b60.png)
不过布局看着有点别扭,原因是咱们这个高度定死了:
![](https://i-blog.csdnimg.cn/blog_migrate/ae8ae75d6fccf38215779764def2388b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/652db829d7215ff7348706c80af6106d.png)
发现内容出不来了:
![](https://i-blog.csdnimg.cn/blog_migrate/97dba20dd1e3b778ca481325d154e7cb.png)
这是咋回事呢?由于咱们目前的场景是ListView里面又套了一个ListView,这里需要增加一个属性,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/355100c7b19fbf1d12ed3e50a5be88c2.png)
此时内容就正常了:
![](https://i-blog.csdnimg.cn/blog_migrate/730952373270f411b3750bcecf60e332.png)
接下来其实目前是有个问题的,目前由于用的手机分辨率比较高还看不出来,咱们换一台较低分辨率的演示一下就知道了:
![](https://i-blog.csdnimg.cn/blog_migrate/387ecc2bb693c37805cfee6cfaa2bd93.gif)
看到木有,滑不动了,其实是由于滑动事件对第一层的ListView给消费掉了,对于ListView里面再嵌ListView是会有滑动冲突的问题,这也是问题之所在,而在Flutter中解决滑动冲突非常之简单,加个属性就解决了,而在Android中想想是多么麻烦,具体如下:
![](https://i-blog.csdnimg.cn/blog_migrate/b6a4a8a88180d6a257eb3aebbcef7835.png)
然后咱们再将块与块之间增加一点间距,好看一点,那如何增加呢?看修改:
![](https://i-blog.csdnimg.cn/blog_migrate/75c6e0e4cced99c0a4be2c5bfba13062.png)
运行看一下效果:
![](https://i-blog.csdnimg.cn/blog_migrate/1827f65a92aecd431b4c174ac91c0d26.png)
其中有个小细节提一下,在之前已经有提过:
![](https://i-blog.csdnimg.cn/blog_migrate/d3faabfba49ac24f90d39bbe15da501a.png)
其实就是android material风格,看一下:
![](https://i-blog.csdnimg.cn/blog_migrate/3bf715e4fe77765f5664b02b9286bb9e.gif)
就是这种效果。
处理点击事件:
开源众包:
这里先来处理这块的点击:
![](https://i-blog.csdnimg.cn/blog_migrate/57525b4499aa528feadf332b1fa8b5ca.png)
这里只以“开源众包”点击事件为例实现一下:
![](https://i-blog.csdnimg.cn/blog_migrate/d947e48390969e8c05601a877b68f47f.png)
然后找一个开源众包的URL,直接上开源中国的官网找一下链接:
![](https://i-blog.csdnimg.cn/blog_migrate/5726ec86a9e5ba0e43809ad32679d6b3.png)
"https://zb.oschina.net/",是它,所以咱们写一下跳转,在之前的登录中已经用到了,直接贴代码:
![](https://i-blog.csdnimg.cn/blog_migrate/2ae74043cb8824cb504f750567e4db93.png)
然后新建一个WebView的页面:
![](https://i-blog.csdnimg.cn/blog_migrate/37033976762dd624e6c76b72cc7ab3b2.png)
import 'package:flutter/material.dart';
class CommonWebPage extends StatefulWidget {
final String title;
final String url;
CommonWebPage({Key key, this.title, this.url})
: assert(title != null),
assert(url != null),
super(key: key);
@override
_CommonWebPageState createState() => _CommonWebPageState();
}
class _CommonWebPageState extends State<CommonWebPage> {
@override
Widget build(BuildContext context) {
return Container();
}
}
接下来则来编写这个WebView页面,之前已经使用过一次了,这里再来温故一下:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_osc_client/constants/constants.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
class CommonWebPage extends StatefulWidget {
final String title;
final String url;
CommonWebPage({Key key, this.title, this.url})
: assert(title != null),
assert(url != null),
super(key: key);
@override
_CommonWebPageState createState() => _CommonWebPageState();
}
class _CommonWebPageState extends State<CommonWebPage> {
bool isLoading = true;
@override
Widget build(BuildContext context) {
List<Widget> _appBarTitle = [
Text(
widget.title,
style: TextStyle(
color: Color(AppColors.APPBAR),
),
),
];
if (isLoading) {
_appBarTitle.add(SizedBox(
width: 10.0,
));
_appBarTitle.add(CupertinoActivityIndicator());
}
return WebviewScaffold(
url: widget.url,
appBar: AppBar(
title: Row(
children: _appBarTitle,
),
iconTheme: IconThemeData(color: Color(AppColors.APPBAR)), //0412 added
),
withJavascript: true,
//允许执行js
withLocalStorage: true,
//允许本地存储
withZoom: true, //允许网页缩放
);
}
}
接下来增加WebView的监听:
![](https://i-blog.csdnimg.cn/blog_migrate/b885f2ca9fc978ba896ef7488986e256.png)
运行一下:
![](https://i-blog.csdnimg.cn/blog_migrate/1e95c1637fbc879d2912dd76c8879022.gif)
扫一扫:
接下来再来处理扫一扫的点击事件:
![](https://i-blog.csdnimg.cn/blog_migrate/3bc846cbd5082eafa4538b7f57111018.png)
对于扫一扫肯定得用三方开源的,所以用这个库来做:
![](https://i-blog.csdnimg.cn/blog_migrate/89da76d68dc45493b9ff27c3ab37ab04.png)
看一下怎么用的:
![](https://i-blog.csdnimg.cn/blog_migrate/0c5c74b85f89c0451aff6ca5d410e125.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d1a434a96b01bcc63032689e3f00b1cb.png)
然后看下拉取下来的版本号:
![](https://i-blog.csdnimg.cn/blog_migrate/9fe20a4d22f5465ef1af12c64f3c1ee0.png)
所以:
![](https://i-blog.csdnimg.cn/blog_migrate/ef37fa861f029b804ae5a0ed742d4cf2.png)
那如何调用呢?
![](https://i-blog.csdnimg.cn/blog_migrate/5751daece0b34347a5c733c7fcdbd124.png)
所以咱们来整一下:
![](https://i-blog.csdnimg.cn/blog_migrate/734b27fc776c243b67a498bd1b64a111.png)
运行看一下:
![](https://i-blog.csdnimg.cn/blog_migrate/7c2dfdbbb3217c3bc7a7024e7b190c66.png)
报错了。。上面错误提示说是需要改一下最小支持的sdk为16以上,其实改了之后还是报,这里换一个稍低的版本:
![](https://i-blog.csdnimg.cn/blog_migrate/916da49cae28af0a0766380192ebc746.png)
然后再编译,发现还是出错:
![](https://i-blog.csdnimg.cn/blog_migrate/3091cfab0a9814fecc9c57bfafb72cd8.png)
androidx,也就是咱们目前还是用的support,那具体怎么解决这个错呢,得按如下步骤改一下:
1、修改 android/gradle/wrapper/gradle-wrapper.properties 为gradle-4.10.2-all.zip及以上:
![](https://i-blog.csdnimg.cn/blog_migrate/f3e21ac18f612d7aa88a9d27a0cd0982.png)
2、修改 android/build.gradle :
groovy
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
改为
groovy
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
}
![](https://i-blog.csdnimg.cn/blog_migrate/18a7a9a6e0ee004a6715a1ae36ca63fc.png)
改为:
![](https://i-blog.csdnimg.cn/blog_migrate/db43a07b809eea29c99f3c4d654d7320.png)
3、修改 android/gradle.properties,加上下面两句 :
android.enableJetifier=true
android.useAndroidX=true
![](https://i-blog.csdnimg.cn/blog_migrate/5c76119a44a758646ea2e5ceb992215f.png)
4、修改 android/app/build.gradle :
首先,确保 compileSdkVersion 和 targetSdkVersion 至少为 28 :
android{
...
compileSdkVersion 28
...
defaultConfig{
...
targetSdkVersion 28
...
}
...
}
5、将以下的support都改为androidx:
![](https://i-blog.csdnimg.cn/blog_migrate/21681cc5f0b9191a02638c3d9f0b8b75.png)
![](https://i-blog.csdnimg.cn/blog_migrate/937d0f247da0c61d1e133e5976a68d2e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/22a8a1fafe2053e2bc9e2ae4419085ec.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a2ba51822e33c9caf0872bf0d0dc5330.png)
一切修改就绪,接下来运行扫一扫,发现还是报错了:
![](https://i-blog.csdnimg.cn/blog_migrate/dd9fe70459422202b42b274c7b1e1639.png)
哦,难道是忘了加相机权限了,其实不是的,这是需要在项目工程下执行“flutter clean”既可以解决这个错,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/a04278da1210f1c01ea8f8371c236063.png)
然后再次运行就好了:
![](https://i-blog.csdnimg.cn/blog_migrate/6583e71ca5ca7447cb22205deeceaf95.gif)
咱们这里找一个二维码扫一下能出结果就成:
![](https://i-blog.csdnimg.cn/blog_migrate/41d97de8d122357ea1966db585051986.png)
![](https://i-blog.csdnimg.cn/blog_migrate/efe1701ec5481f58ffec710a542bc250.png)
摇一摇:
添加点击事件:
这个就稍复杂一些,先添加一个点击事件跳到一个页面:
![](https://i-blog.csdnimg.cn/blog_migrate/96ed290ceb10dd8a0322a11429f7fc71.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7a8ffe7431ae6d32205e603a9b9d7034.png)
import 'package:flutter/material.dart';
import 'package:flutter_osc_client/constants/constants.dart';
class ShakePage extends StatefulWidget {
@override
_ShakePageState createState() => _ShakePageState();
}
class _ShakePageState extends State<ShakePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
title: Text(
'摇一摇',
style: TextStyle(color: Color(AppColors.APPBAR)),
),
iconTheme: IconThemeData(color: Color(AppColors.APPBAR)),
),
body: Center(
child: Text('摇一摇'),
),
);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/032498df2e5b7f120a3fe5970ae813af.gif)
页面搭建:
先来看一下它长啥样:
![](https://i-blog.csdnimg.cn/blog_migrate/0eca254e9137a1b9e72af9834afb3930.png)
所以咱们来搭建一下,由于这样的界面咱们在之前已经用过了,所以这里就不过多的解释说明了:
import 'package:flutter/material.dart';
import 'package:flutter_osc_client/constants/constants.dart';
class ShakePage extends StatefulWidget {
@override
_ShakePageState createState() => _ShakePageState();
}
class _ShakePageState extends State<ShakePage> {
bool isShaked = false;
int _curentIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
title: Text(
'摇一摇',
style: TextStyle(color: Color(AppColors.APPBAR)),
),
iconTheme: IconThemeData(color: Color(AppColors.APPBAR)),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/images/shake.png',
width: 120.0,
height: 120.0,
),
SizedBox(
height: 10.0,
),
Text('摇一摇获取礼品'),
],
),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.folder), title: Text('礼品')),
BottomNavigationBarItem(
icon: Icon(Icons.assignment), title: Text('资讯'))
],
currentIndex: _curentIndex,
onTap: (index) {
if (!mounted) return;
setState(() {
_curentIndex = index;
});
},
),
);
}
}
其中用到了一个新图片:
![](https://i-blog.csdnimg.cn/blog_migrate/6824ece64eeabfb2ef6a6cb9b954cd06.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c6891492d5b968275c6b7924402a6350.png)
在yaml文件中声明一下:
![](https://i-blog.csdnimg.cn/blog_migrate/2b1cae2aba54a763d06e9fc1ccbd1bef.png)
运行:
![](https://i-blog.csdnimg.cn/blog_migrate/3af30a7cd0a91581589a721e98193a51.gif)
开始实现:
1、集成三方依赖库:
这里对于摇一摇其实没有太成熟好用的库,不过也能搜到一些,比如:
![](https://i-blog.csdnimg.cn/blog_migrate/322a645581da35f425d4ecd388c466f2.png)
但是这里采用自己来编写逻辑,只集成传感器的功能库,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/e1ca5f7044f39a9cc2acf8c9e493b4d9.png)
而它的使用看一下:
![](https://i-blog.csdnimg.cn/blog_migrate/8e7ec3de16fa359b19fb0455daabb5fa.png)
所以咱们来将它集成进来:
![](https://i-blog.csdnimg.cn/blog_migrate/00beb1438a2a336a448912480809cefe.png)
2、增加加速度感应监听:
![](https://i-blog.csdnimg.cn/blog_migrate/fb90b53a3c1f79f128e50108ab029322.png)
3、摇一摇逻辑实现:
![](https://i-blog.csdnimg.cn/blog_migrate/6c2241594d85703f8ca3eeb123d5d446.png)
接下来则实现摇一摇的具体处理,不过先来给它加一个震动,此时又需要用到三方的库了:
![](https://i-blog.csdnimg.cn/blog_migrate/edc00f6290df6d90be1c3d56e06be2a7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4299d23b8c5071ba5066eba7bf457dad.png)
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_osc_client/constants/constants.dart';
import 'package:sensors/sensors.dart';
import 'package:vibration/vibration.dart';
class ShakePage extends StatefulWidget {
@override
_ShakePageState createState() => _ShakePageState();
}
class _ShakePageState extends State<ShakePage> {
bool isShaked = false;
int _curentIndex = 0;
StreamSubscription _streamSubscription;
static const int SHAKE_TIMEOUT = 500;
static const double SHAKE_THRESHOLD = 3.25;
var _lastTime = 0;
@override
void initState() {
super.initState();
_streamSubscription =
accelerometerEvents.listen((AccelerometerEvent event) {
var now = DateTime.now().millisecondsSinceEpoch;
if ((now - _lastTime) > SHAKE_TIMEOUT) {
//获得加速度的x,y,z值
var x = event.x;
var y = event.y;
var z = event.z;
double acce =
sqrt(x * x + y * y + z * z) - 9.8; //9.8是g,加速度公式,貌似是初中的物理,反正我是忘了
if (acce > SHAKE_THRESHOLD) {
print('摇一摇');
//手机晃动了
Vibration.vibrate();
_lastTime = now;
if (!mounted) return;
setState(() {
isShaked = true;
});
}
}
});
}
@override
void dispose() {
super.dispose();
_streamSubscription.cancel();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
title: Text(
'摇一摇',
style: TextStyle(color: Color(AppColors.APPBAR)),
),
iconTheme: IconThemeData(color: Color(AppColors.APPBAR)),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/images/shake.png',
width: 120.0,
height: 120.0,
),
SizedBox(
height: 10.0,
),
Text(isShaked ? '活动已结束!' : '摇一摇获取礼品'),
],
),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.folder), title: Text('礼品')),
BottomNavigationBarItem(
icon: Icon(Icons.assignment), title: Text('资讯'))
],
currentIndex: _curentIndex,
onTap: (index) {
if (!mounted) return;
setState(() {
_curentIndex = index;
isShaked = false;
});
},
),
);
}
}
下面咱们运行试一下:
![](https://i-blog.csdnimg.cn/blog_migrate/2a864bc15e5e39c0706f6163bd4a739a.gif)
图片中感知不到震动与摇动,具体自行体验下既可,当然摇一摇应该得去请求相应的接口的,这里由于木有相关接口就暂且忽略了。