<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
點贊這個動作不得不說在社交、短視訊等App中實在是太常見了,當用戶手指按下去的那一刻,給使用者一個好的反饋效果也是非常重要的,這樣使用者點起贊來才會有一種強烈的我點了讚的效果,那麼今天我們就用Flutter實現一個掘金App上的點贊效果。
首先我們看下掘金App的點贊組成部分,有一個小手,點贊數位、點贊氣泡效果,還有一個震動反饋,接下來我們一步一步實現。
知識點:繪製、動畫、震動反饋
這裡我們使用Flutter的Icon圖示中的點贊小手,Icons圖示庫為我們提供了很多App常見的小圖示,如果使用蘋果蘋果風格的小圖示可以使用cupertino_icons: ^1.0.2
外掛,圖示並不是圖片,本質上和emoji
圖示一樣,可以新增到文字中使用,所以圖示才可以設定不同的顏色屬性,對比使用png格式圖示可以節省不少的記憶體。
接下來我們就將這兩個圖示繪製出來,首先我們從上圖可以看到真正的圖示資料其實是IconData
類,裡面有一個codePoint
屬性可以獲取到Unicode
統一碼,通過String.fromCharCode(int charCode)
可以返回一個程式碼單元,在Text文字中支援顯示。
class IconData{ /// The Unicode code point at which this icon is stored in the icon font. /// 獲取此圖示的Unicode程式碼點 final int codePoint; } class String{ /// 如果[charCode]可以用一個UTF-16編碼單元表示,則新的字串包含一個程式碼單元 external factory String.fromCharCode(int charCode); }
接下來我們就可以把圖示以繪製文字的形式繪製出來了
關鍵程式碼:
// 贊圖示 final icon = Icons.thumb_up_alt_outlined; // 通過TextPainter可以獲取圖示的尺寸 TextPainter textPainter = TextPainter( text: TextSpan( text: String.fromCharCode(icon.codePoint), style: TextStyle( fontSize: 30, fontFamily: icon.fontFamily,// 字型形象家族,這個欄位一定要設定,不然顯示不出來 color: Colors.black)), textAlign: TextAlign.center, textDirection: TextDirection.ltr); textPainter.layout(); // 進行佈局 Size size2 = textPainter.size; // 尺寸必須在佈局後獲取 //將圖示偏移到畫布中央 textPainter.paint(canvas, Offset(-size2.width / 2, -size2.height / 2));
通過上方程式碼我們就實現了將圖示繪製到畫板當中
接下來繼續繪製點贊數量
程式碼:
TextPainter textPainter2 = TextPainter( text: TextSpan( text: "點贊",// 點贊數量 style: TextStyle( fontSize: 9, fontWeight: FontWeight.w500, color: Colors.black)), textAlign: TextAlign.center, textDirection: TextDirection.ltr); textPainter2.layout(); // 進行佈局 // 向右上進行偏移在小手上面 textPainter2.paint(canvas, Offset(size.width / 9, -size.height / 2 + 5));
然後圖示就變成了這樣樣子
我們看到,掘金App點讚的過程中,周圍還有一些小氣泡的效果,這裡提供一個思路,將這些氣泡的座標點放到一個圓的外環上面,通過動畫改變圓的半徑達到小圓點由內向外發散,發散的同時改變小圓點的大小,從而達到氣泡的效果, 關鍵程式碼:
var r = size.width / 2 - 15; // 半徑 var d = 4; // 偏移量 氣泡的移動距離 // 繪製小圓點 一共4個 掘金也是4個 角度可以自由發揮 這裡根據掘金App的發散角度定義的 canvas.drawPoints( ui.PointMode.points, [ Offset((r + d * animation2.value) * cos(pi - pi / 18 * 2), (r + d * animation2.value) * sin(pi - pi / 18 * 2)), Offset((r + d * animation2.value) * cos(pi + pi / 18 * 2), (r + d * animation2.value) * sin(pi + pi / 18 * 2)), Offset((r + d * animation2.value) * cos(pi * 1.5 - pi / 18), (r + d * animation2.value) * sin(pi * 1.5 - pi / 18)), Offset((r + d * animation2.value) * cos(pi * 1.5 + pi / 18 * 5), (r + d * animation2.value) * sin(pi * 1.5 + pi / 18 * 5)), ], _paint ..strokeWidth = 5 ..color = Colors.blue ..strokeCap = StrokeCap.round);
得到現在的圖形, 發散前
發散後
接下來繼續我們來新增互動效果,新增動畫,如果有看上一篇吃豆人,相信這裡就很so easy了,首先建立兩個動畫類,控制小手和氣泡,再建立兩個變數,是否點贊和點贊數量,程式碼:
late Animation<double> animation; // 贊 late Animation<double> animation2; // 小圓點 ValueNotifier<bool> isZan = ValueNotifier(false); // 記錄點贊狀態 預設沒點贊 ValueNotifier<int> zanNum = ValueNotifier(0); // 記錄點贊數量 預設0點贊
這裡我們需要使用動畫曲線CurvedAnimation
這個類,這個類可以實現不同的0-1的運動曲線,根據掘金的點贊效果,比較符合這個曲線規則,快速放大,然後迴歸正常大小,這個類幫我們實現了很多好玩的運動曲線,有興趣的小夥伴可以嘗試下其他運動曲線。
小手運動曲線:
氣泡運動曲線:
有了運動曲線之後,接下來我們只需將屬性賦值給小手手和小圓點就好了
封裝一下,對外暴露大小,就是一個點贊元件了。
class ZanDemo extends StatefulWidget { const ZanDemo({Key? key}) : super(key: key); @override _ZanDemoState createState() => _ZanDemoState(); } class _ZanDemoState extends State<ZanDemo> with TickerProviderStateMixin { late Animation<double> animation; // 贊 late Animation<double> animation2; // 小圓點 ValueNotifier<bool> isZan = ValueNotifier(false); // 記錄點贊狀態 預設沒點贊 ValueNotifier<int> zanNum = ValueNotifier(0); // 記錄點贊數量 預設0點贊 late AnimationController _controller; // 控制器 late AnimationController _controller2; // 小圓點控制器 late CurvedAnimation cure; // 動畫執行的速度軌跡 速度的變化 late CurvedAnimation cure2; // 動畫執行的速度軌跡 速度的變化 int time = 0;// 防止快速點兩次贊導致取消贊 @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 500)); //500ms _controller2 = AnimationController( vsync: this, duration: const Duration(milliseconds: 500)); //500ms cure = CurvedAnimation(parent: _controller, curve: Curves.easeInOutBack); cure2 = CurvedAnimation(parent: _controller2, curve: Curves.easeOutQuint); animation = Tween(begin: 0.0, end: 1.0).animate(cure); animation2 = Tween(begin: 0.0, end: 1.0).animate(_controller2); } @override Widget build(BuildContext context) { return InkWell( child: Center( child: CustomPaint( size: Size(50, 50), painter: _ZanPainter(animation, animation2, isZan, zanNum, Listenable.merge([animation, animation2, isZan, zanNum])), ), ), onTap: () { if (!isZan.value && !_isDoubleClick()) { _controller.forward(from: 0); // 延遲300ms彈窗氣泡 Timer(Duration(milliseconds: 300), () { isZan.value = true; _controller2.forward(from: 0); }); Vibrate.feedback(FeedbackType.success); zanNum.value++; } else if (isZan.value) { Vibrate.feedback(FeedbackType.success); isZan.value = false; zanNum.value--; } }, ); } bool _isDoubleClick() { if (time == 0) { time = DateTime.now().microsecondsSinceEpoch; return false; } else { if (DateTime.now().microsecondsSinceEpoch - time < 800 * 1000) { return true; } else { time = DateTime.now().microsecondsSinceEpoch; return false; } } } } class _ZanPainter extends CustomPainter { Animation<double> animation; Animation<double> animation2; ValueNotifier<bool> isZan; ValueNotifier<int> zanNum; Listenable listenable; _ZanPainter( this.animation, this.animation2, this.isZan, this.zanNum, this.listenable) : super(repaint: listenable); Paint _paint = Paint()..color = Colors.blue; List<Offset> points = []; @override void paint(Canvas canvas, Size size) { canvas.clipRect(Offset.zero & size); canvas.translate(size.width / 2, size.height / 2); // 贊 final icon = isZan.value ? Icons.thumb_up_alt_rounded : Icons.thumb_up_alt_outlined; // 通過TextPainter可以獲取圖示的尺寸 TextPainter textPainter = TextPainter( text: TextSpan( text: String.fromCharCode(icon.codePoint), style: TextStyle( fontSize: animation.value < 0 ? 0 : animation.value * 30, fontFamily: icon.fontFamily, color: isZan.value ? Colors.blue : Colors.black)), textAlign: TextAlign.center, textDirection: TextDirection.ltr); textPainter.layout(); // 進行佈局 Size size2 = textPainter.size; // 尺寸必須在佈局後獲取 //將圖示偏移到畫布中央 textPainter.paint(canvas, Offset(-size2.width / 2, -size2.height / 2)); var r = size.width / 2 - 15; // 半徑 var d = 4; // 偏移量 canvas.drawPoints( ui.PointMode.points, [ Offset((r + d * animation2.value) * cos(pi - pi / 18 * 2), (r + d * animation2.value) * sin(pi - pi / 18 * 2)), Offset((r + d * animation2.value) * cos(pi + pi / 18 * 2), (r + d * animation2.value) * sin(pi + pi / 18 * 2)), Offset((r + d * animation2.value) * cos(pi * 1.5 - pi / 18 * 1), (r + d * animation2.value) * sin(pi * 1.5 - pi / 18 * 1)), Offset((r + d * animation2.value) * cos(pi * 1.5 + pi / 18 * 5), (r + d * animation2.value) * sin(pi * 1.5 + pi / 18 * 5)), ], _paint ..strokeWidth = animation2.value < 1 ? 5 * animation2.value : 0 ..color = Colors.blue ..strokeCap = StrokeCap.round); TextPainter textPainter2 = TextPainter( text: TextSpan( text: zanNum.value == 0 ? "點贊" : zanNum.value.toString(), style: TextStyle( fontSize: 9, fontWeight: FontWeight.w500, color: Colors.black)), textAlign: TextAlign.center, textDirection: TextDirection.ltr); textPainter2.layout(); // 進行佈局 // 向右上進行偏移在小手上面 textPainter2.paint(canvas, Offset(size.width / 9, -size.height / 2 + 5)); } @override bool shouldRepaint(covariant _ZanPainter oldDelegate) { return oldDelegate.listenable != listenable; } }
到這裡發現是不是少了點什麼,不錯,還少了震動的效果,這裡我們引入flutter_vibrate: ^1.3.0
這個外掛,這個外掛是用來管理裝置震動效果的,Andoroid端記得加入震動許可權
<uses-permission android:name="android.permission.VIBRATE"/>
使用方法也很簡單,這個外掛封裝了一些常見的提示震動,比如操作成功、操作警告、操作失敗等,其實就是震動時間的長短,這裡我們就在點贊時候呼叫Vibrate.feedback(FeedbackType.success);
有一個點選成功的震動就好了。
最後來看下最終效果圖吧:
是不是和掘金App的效果一樣,不信你點個贊看看~~
以上就是Android Flutter實現點贊效果的範例程式碼的詳細內容,更多關於Flutter點讚的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45