最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android Flutter實(shí)現(xiàn)點(diǎn)贊效果的示例代碼

 更新時(shí)間:2022年04月22日 08:24:44   作者:老李code  
點(diǎn)贊這個(gè)動(dòng)作不得不說在社交、短視頻等App中實(shí)在是太常見了。本文將利用Flutter制作出一個(gè)點(diǎn)贊動(dòng)畫效果,感興趣的小伙伴可以學(xué)習(xí)一下

前言

點(diǎn)贊這個(gè)動(dòng)作不得不說在社交、短視頻等App中實(shí)在是太常見了,當(dāng)用戶手指按下去的那一刻,給用戶一個(gè)好的反饋效果也是非常重要的,這樣用戶點(diǎn)起贊來才會(huì)有一種強(qiáng)烈的我點(diǎn)了贊的效果,那么今天我們就用Flutter實(shí)現(xiàn)一個(gè)掘金App上的點(diǎn)贊效果。

首先我們看下掘金App的點(diǎn)贊組成部分,有一個(gè)小手,點(diǎn)贊數(shù)字、點(diǎn)贊氣泡效果,還有一個(gè)震動(dòng)反饋,接下來我們一步一步實(shí)現(xiàn)。

知識(shí)點(diǎn):繪制、動(dòng)畫、震動(dòng)反饋

繪制小手

這里我們使用Flutter的Icon圖標(biāo)中的點(diǎn)贊小手,Icons圖標(biāo)庫為我們提供了很多App常見的小圖標(biāo),如果使用蘋果蘋果風(fēng)格的小圖標(biāo)可以使用cupertino_icons: ^1.0.2插件,圖標(biāo)并不是圖片,本質(zhì)上和emoji圖標(biāo)一樣,可以添加到文本中使用,所以圖標(biāo)才可以設(shè)置不同的顏色屬性,對(duì)比使用png格式圖標(biāo)可以節(jié)省不少的內(nèi)存。

接下來我們就將這兩個(gè)圖標(biāo)繪制出來,首先我們從上圖可以看到真正的圖標(biāo)數(shù)據(jù)其實(shí)是IconData類,里面有一個(gè)codePoint屬性可以獲取到Unicode統(tǒng)一碼,通過String.fromCharCode(int charCode)可以返回一個(gè)代碼單元,在Text文本中支持顯示。

class IconData{
/// The Unicode code point at which this icon is stored in the icon font.
/// 獲取此圖標(biāo)的Unicode代碼點(diǎn)
final int codePoint;
}

class String{
/// 如果[charCode]可以用一個(gè)UTF-16編碼單元表示,則新的字符串包含一個(gè)代碼單元
external factory String.fromCharCode(int charCode);
}

接下來我們就可以把圖標(biāo)以繪制文本的形式繪制出來了

關(guān)鍵代碼:

 // 贊圖標(biāo)
  final icon = Icons.thumb_up_alt_outlined;
// 通過TextPainter可以獲取圖標(biāo)的尺寸
  TextPainter textPainter = TextPainter(
      text: TextSpan(
          text: String.fromCharCode(icon.codePoint),
          style: TextStyle(
              fontSize: 30,
              fontFamily: icon.fontFamily,// 字體形象家族,這個(gè)字段一定要設(shè)置,不然顯示不出來
              color: Colors.black)),
      textAlign: TextAlign.center,
      textDirection: TextDirection.ltr);
  textPainter.layout(); // 進(jìn)行布局
  Size size2 = textPainter.size; // 尺寸必須在布局后獲取
  //將圖標(biāo)偏移到畫布中央
  textPainter.paint(canvas, Offset(-size2.width / 2, -size2.height / 2));

通過上方代碼我們就實(shí)現(xiàn)了將圖標(biāo)繪制到畫板當(dāng)中

接下來繼續(xù)繪制點(diǎn)贊數(shù)量

代碼:

TextPainter textPainter2 = TextPainter(
    text: TextSpan(
        text: "點(diǎn)贊",// 點(diǎn)贊數(shù)量
        style: TextStyle(
            fontSize: 9, fontWeight: FontWeight.w500, color: Colors.black)),
    textAlign: TextAlign.center,
    textDirection: TextDirection.ltr);
textPainter2.layout(); // 進(jìn)行布局
// 向右上進(jìn)行偏移在小手上面
textPainter2.paint(canvas, Offset(size.width / 9, -size.height / 2 + 5));

然后圖標(biāo)就變成了這樣樣子

我們看到,掘金App點(diǎn)贊的過程中,周圍還有一些小氣泡的效果,這里提供一個(gè)思路,將這些氣泡的坐標(biāo)點(diǎn)放到一個(gè)圓的外環(huán)上面,通過動(dòng)畫改變圓的半徑達(dá)到小圓點(diǎn)由內(nèi)向外發(fā)散,發(fā)散的同時(shí)改變小圓點(diǎn)的大小,從而達(dá)到氣泡的效果, 關(guān)鍵代碼:

var r = size.width / 2 - 15; // 半徑
var d = 4; // 偏移量 氣泡的移動(dòng)距離

// 繪制小圓點(diǎn) 一共4個(gè) 掘金也是4個(gè) 角度可以自由發(fā)揮 這里根據(jù)掘金App的發(fā)散角度定義的
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);

得到現(xiàn)在的圖形, 發(fā)散前

發(fā)散后

接下來繼續(xù)我們來添加交互效果,添加動(dòng)畫,如果有看上一篇吃豆人,相信這里就很so easy了,首先創(chuàng)建兩個(gè)動(dòng)畫類,控制小手和氣泡,再創(chuàng)建兩個(gè)變量,是否點(diǎn)贊和點(diǎn)贊數(shù)量,代碼:

late Animation<double> animation; // 贊
late Animation<double> animation2; // 小圓點(diǎn)
ValueNotifier<bool> isZan = ValueNotifier(false); // 記錄點(diǎn)贊狀態(tài) 默認(rèn)沒點(diǎn)贊
ValueNotifier<int> zanNum = ValueNotifier(0); // 記錄點(diǎn)贊數(shù)量 默認(rèn)0點(diǎn)贊

這里我們需要使用動(dòng)畫曲線CurvedAnimation這個(gè)類,這個(gè)類可以實(shí)現(xiàn)不同的0-1的運(yùn)動(dòng)曲線,根據(jù)掘金的點(diǎn)贊效果,比較符合這個(gè)曲線規(guī)則,快速放大,然后回歸正常大小,這個(gè)類幫我們實(shí)現(xiàn)了很多好玩的運(yùn)動(dòng)曲線,有興趣的小伙伴可以嘗試下其他運(yùn)動(dòng)曲線。

小手運(yùn)動(dòng)曲線

氣泡運(yùn)動(dòng)曲線:

有了運(yùn)動(dòng)曲線之后,接下來我們只需將屬性賦值給小手手和小圓點(diǎn)就好了

完整源碼

封裝一下,對(duì)外暴露大小,就是一個(gè)點(diǎn)贊組件了。

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; // 小圓點(diǎn)
  ValueNotifier<bool> isZan = ValueNotifier(false); // 記錄點(diǎn)贊狀態(tài) 默認(rèn)沒點(diǎn)贊
  ValueNotifier<int> zanNum = ValueNotifier(0); // 記錄點(diǎn)贊數(shù)量 默認(rèn)0點(diǎn)贊

  late AnimationController _controller; // 控制器
  late AnimationController _controller2; // 小圓點(diǎn)控制器
  late CurvedAnimation cure; // 動(dòng)畫運(yùn)行的速度軌跡 速度的變化
  late CurvedAnimation cure2; // 動(dòng)畫運(yùn)行的速度軌跡 速度的變化

  int time = 0;// 防止快速點(diǎn)兩次贊導(dǎo)致取消贊

  @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可以獲取圖標(biāo)的尺寸
    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(); // 進(jìn)行布局
    Size size2 = textPainter.size; // 尺寸必須在布局后獲取
    //將圖標(biāo)偏移到畫布中央
    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 ? "點(diǎn)贊" : zanNum.value.toString(),
            style: TextStyle(
                fontSize: 9, fontWeight: FontWeight.w500, color: Colors.black)),
        textAlign: TextAlign.center,
        textDirection: TextDirection.ltr);
    textPainter2.layout(); // 進(jìn)行布局
    // 向右上進(jìn)行偏移在小手上面
    textPainter2.paint(canvas, Offset(size.width / 9, -size.height / 2 + 5));
  }

  @override
  bool shouldRepaint(covariant _ZanPainter oldDelegate) {
    return oldDelegate.listenable != listenable;
  }
}

到這里發(fā)現(xiàn)是不是少了點(diǎn)什么,不錯(cuò),還少了震動(dòng)的效果,這里我們引入flutter_vibrate: ^1.3.0這個(gè)插件,這個(gè)插件是用來管理設(shè)備震動(dòng)效果的,Andoroid端記得加入震動(dòng)權(quán)限

<uses-permission android:name="android.permission.VIBRATE"/>使用方法也很簡單,這個(gè)插件封裝了一些常見的提示震動(dòng),比如操作成功、操作警告、操作失敗等,其實(shí)就是震動(dòng)時(shí)間的長短,這里我們就在點(diǎn)贊時(shí)候調(diào)用Vibrate.feedback(FeedbackType.success);有一個(gè)點(diǎn)擊成功的震動(dòng)就好了。

最后來看下最終效果圖吧:

是不是和掘金App的效果一樣,不信你點(diǎn)個(gè)贊看看~~

以上就是Android Flutter實(shí)現(xiàn)點(diǎn)贊效果的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Flutter點(diǎn)贊的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

呼图壁县| 宜丰县| 武川县| 郑州市| 天全县| 普兰县| 全州县| 灵璧县| 江安县| 长葛市| 聂荣县| 民丰县| 香港| 赫章县| 荥阳市| 江源县| 错那县| 谢通门县| 杂多县| 文安县| 革吉县| 南通市| 景谷| 大埔区| 曲松县| 河南省| 乡宁县| 通渭县| 辽中县| 旌德县| 江城| 赣榆县| 贵港市| 赣榆县| 阳东县| 武强县| 正蓝旗| 定州市| 北川| 凤台县| 枝江市|