QQ聊天气泡的动画动态效果实现

前言

气泡框作为社交聊天软件的基本功能,想必大家都了解怎么实现了。但是像国内巨头腾讯旗下的QQ气泡(尤其是动态的)我想应该是没有几个。这里就为大家提供一种类似QQ动态气泡的解决方案(还不完善)。

首先用到了一个第三方Lottie, airbnb做的一个专门做动画的东西。专门研究过动画的应该都知道或了解。简单讲就是通过插件将AE中画的动画转换成json文件,然后按照特定规则转换到页面上通过CABaseAnimation来实现。

效果预览

效果图,注意小人头顶鬼火

明确需求

  • 基本的聊天气泡,随文本大小变更气泡大小并保证边界圆角等形状不变
  • 动态气泡,气泡内容会能动态展示,并根据文本大小,部分动画(如位移)会有区别

lottie分析

一下是一段lottie的json文件,部分删减,源文件可在demo中查看,这里标注上重要字段

{
    "v": "5.1.10",
    "fr": 25,
    "ip": 0,
    "op": 750,
    "w": 128,//画布宽
    "h": 72,//画布高
    "nm": "合成 1",
    "ddd": 0,
    "assets": [//使用的图片资源
        {
            "id": "image_0",
            "w": 26,
            "h": 19,
            "u": "images/",
            "p": "data:image/png;base64,"//图片内容,愿框架不支持网络地址,仅能检索本地文件与base64编码后的内容
        }
    ],
    "layers": [//layer层
        {
            "ddd": 0,
            "ind": 1,
            "ty": 2,
            "nm": "星星.png",
            "cl": "png",
            "refId": "image_0",//使用图片资源id
            "sr": 1,
            "ks": {//位置,大小,缩放等信息
                "o": {
                    "a": 0,
                    "k": 100,
                    "ix": 11
                },
                "r": {
                    "a": 0,
                    "k": 0,
                    "ix": 10
                },
                "p": {
                    "a": 1,
                    "k": [
                        {//数组表示核心桢时的位置
                            "i": {
                                "x": 0.833,
                                "y": 0.833
                            },
                            "o": {
                                "x": 0.167,
                                "y": 0.167
                            },
                            "n": "0p833_0p833_0p167_0p167",
                            "t": 0,
                            "s": [//大小
                                21.375,
                                49.125,
                                0
                            ],
                            "e": [//点位
                                20.5,
                                48.375,
                                0
                            ],
                            "to": [//点位
                                -0.14583332836628,
                                -0.125,
                                0
                            ],
                            "ti": [
                                0.14583332836628,
                                0.25,
                                0
                            ]
                        },
                        {
                            "i": {
                                "x": 0.833,
                                "y": 0.833
                            },
                            "o": {
                                "x": 0.167,
                                "y": 0.167
                            },
                            "n": "0p833_0p833_0p167_0p167",
                            "t": 8,
                            "s": [
                                20.5,
                                48.375,
                                0
                            ],
                            "e": [
                                20.5,
                                47.625,
                                0
                            ],
                            "to": [
                                -0.14583332836628,
                                -0.25,
                                0
                            ],
                            "ti": [
                                0,
                                0.14583332836628,
                                0
                            ]
                        },
                        {
                            "t": 185
                        }
                    ],
                    "ix": 2
                },
                "a": {
                    "a": 0,
                    "k": [
                        13,
                        9.5,
                        0
                    ],
                    "ix": 1
                },
                "s": {
                    "a": 1,
                    "k": [
                        {//数组表示核心桢时的大小
                            "i": {
                                "x": [
                                    0.833,
                                    0.833,
                                    0.833
                                ],
                                "y": [
                                    0.833,
                                    0.833,
                                    0.833
                                ]
                            },
                            "o": {
                                "x": [
                                    0.167,
                                    0.167,
                                    0.167
                                ],
                                "y": [
                                    0.167,
                                    0.167,
                                    0.167
                                ]
                            },
                            "n": [
                                "0p833_0p833_0p167_0p167",
                                "0p833_0p833_0p167_0p167",
                                "0p833_0p833_0p167_0p167"
                            ],
                            "t": 19,
                            "s": [
                                71.154,
                                78.947,
                                100
                            ],
                            "e": [
                                61.538,
                                76.316,
                                100
                            ]
                        },
                        {
                            "i": {
                                "x": [
                                    0.833,
                                    0.833,
                                    0.833
                                ],
                                "y": [
                                    0.833,
                                    0.833,
                                    0.833
                                ]
                            },
                            "o": {
                                "x": [
                                    0.167,
                                    0.167,
                                    0.167
                                ],
                                "y": [
                                    0.167,
                                    0.167,
                                    0.167
                                ]
                            },
                            "n": [
                                "0p833_0p833_0p167_0p167",
                                "0p833_0p833_0p167_0p167",
                                "0p833_0p833_0p167_0p167"
                            ],
                            "t": 42,
                            "s": [
                                61.538,
                                76.316,
                                100
                            ],
                            "e": [
                                79.808,
                                78.947,
                                100
                            ]
                        },
                        {
                            "t": 66
                        }
                    ],
                    "ix": 6
                }
            },
            "ao": 0,
            "ip": 0,
            "op": 750,
            "st": 0,
            "bm": 0
        },
         {
            "ddd": 0,
            "ind": 4,
            "ty": 4,
            "nm": "右下角",
            "sr": 1,
            "ks": {
                "o": {
                    "a": 0,
                    "k": 100,
                    "ix": 11
                },
                "r": {
                    "a": 0,
                    "k": 0,
                    "ix": 10
                },
                "p": {//图层位置
                    "a": 0,
                    "k": [
                        141.25,
                        55.875,
                        0
                    ],
                    "ix": 2
                },
                "a": {
                    "a": 0,
                    "k": [
                        0,
                        0,
                        0
                    ],
                    "ix": 1
                },
                "s": {
                    "a": 0,
                    "k": [
                        100,
                        100,
                        100
                    ],
                    "ix": 6
                }
            },
            "ao": 0,
            "shapes": [//子层数组
                {
                    "ty": "gr",
                    "it": [
                        {
                            "ty": "rc",
                            "d": 1,
                            "s": {//大小
                                "a": 0,
                                "k": [
                                    24.875,
                                    16.125
                                ],
                                "ix": 2
                            },
                            "p": {//位置
                                "a": 0,
                                "k": [
                                    0,
                                    0
                                ],
                                "ix": 3
                            },
                            "r": {
                                "a": 0,
                                "k": 20,
                                "ix": 4
                            },
                            "nm": "矩形路径 1",
                            "mn": "ADBE Vector Shape - Rect",
                            "hd": false
                        },
                        {
                            "ty": "st",
                            "c": {//颜色
                                "a": 0,
                                "k": [
                                    0.901176542394,
                                    0.699030438591,
                                    0.699030438591,
                                    1
                                ],
                                "ix": 3
                            },
                            "o": {
                                "a": 0,
                                "k": 100,
                                "ix": 4
                            },
                            "w": {
                                "a": 0,
                                "k": 0,
                                "ix": 5
                            },
                            "lc": 1,
                            "lj": 1,
                            "ml": 4,
                            "nm": "描边 1",
                            "mn": "ADBE Vector Graphic - Stroke",
                            "hd": false
                        },
                        {
                            "ty": "fl",
                            "c": {
                                "a": 0,
                                "k": [
                                    0.878431432387,
                                    0.760784373564,
                                    0.301960784314,
                                    1
                                ],
                                "ix": 4
                            },
                            "o": {
                                "a": 0,
                                "k": 100,
                                "ix": 5
                            },
                            "r": 1,
                            "nm": "填充 1",
                            "mn": "ADBE Vector Graphic - Fill",
                            "hd": false
                        },
                        {
                            "ty": "tr",
                            "p": {
                                "a": 0,
                                "k": [
                                    -38.562,
                                    -9.438
                                ],
                                "ix": 2
                            },
                            "a": {
                                "a": 0,
                                "k": [
                                    0,
                                    0
                                ],
                                "ix": 1
                            },
                            "s": {
                                "a": 0,
                                "k": [
                                    100,
                                    100
                                ],
                                "ix": 3
                            },
                            "r": {
                                "a": 0,
                                "k": 0,
                                "ix": 6
                            },
                            "o": {
                                "a": 0,
                                "k": 100,
                                "ix": 7
                            },
                            "sk": {
                                "a": 0,
                                "k": 0,
                                "ix": 4
                            },
                            "sa": {
                                "a": 0,
                                "k": 0,
                                "ix": 5
                            },
                            "nm": "变换"
                        }
                    ],
                    "nm": "矩形 1",
                    "np": 3,
                    "cix": 2,
                    "ix": 1,
                    "mn": "ADBE Vector Group",
                    "hd": false
                }
            ],
            "ip": 0,
            "op": 750,
            "st": 0,
            "bm": 0
        }
        ],
    "markers": []//暂时没用到,也不推荐使用
}

简单讲,通常p之下的k表示位置,s下表示大小,c表示颜色等,另外位置信息一般表示图形中心点相对父层的位置而不是相对原始画布的位置,这一点要特别注意。大小有两种,使用图片的一般使用缩放比来表示大小,而普通图形则为具体尺寸

首先如果不修改原始画布大小,则lottie会根据展示区域的大小自动缩放整个图形,那么相应的所有元素都会被缩放,所以我们想要的聊天气泡一定是要先修改画布大小的,修改完大小后内部的各个相对点位也都要做相应调整。

我们把气泡划分几个区域来分析

区域划分

划分成如上9块区域,规定横向为x轴,纵向为y轴。其中左上角内容不需要做任何变更,即可保持原始状态,右上角区域需要修改x轴位置坐标即可,上边区域需要修改x轴位置及x轴大小,下面几块区域依此类推。我们需要制定以下规则来达成修改对应值的目的(a表示原始值,dif表示差值,rat表示比例值)。

  1. 差值相加 a=a+dif
  2. 差值相减 a=a-dif
  3. 比例相乘 a=a*rat
  4. 半差相加 a=a+\frac{dif}{2}
  5. 半差相减 a=a-\frac{dif}{2}
  6. 图片比例s的修改 a=a+\frac{100*dif}{b} b为图片原始尺寸,由于比例值本身是乘以100的,所以这里计算也要乘100

参考lottie源码,该部分内容均由LOTKeyframe实现,而s和p等在json中是同级的,并且x,y,z轴是分开的,因此我们制定如下规则,采用6位16进制数表示(json文件中应转换为对应的十进制数值存储),前三位表示大小的修改(s),后三位表示未知的修改(p),三位分别对应 z y x。数值定义如下

16进制 前三位大小、后三位位置 zyx,运算方法与数值如上边规则所示。eg:0x012045->69649表示宽度(大小x轴)需要加上原始画布与结果画布的宽度差,高度(大小y轴)需要减去原始画布与结果画布的高度差,x轴位置需要加上原始画布与结果画布的宽度差的一般,y轴位置需要减去原始画布与结果画布的高度差的一般。

使用说明

由于lottie不支持网络地址图片加载,需要调用内部方法等问题,所以对lottie原始框架内做了部分更改,其他均为扩展或子类实现。建议将demo中的LottieExtension(自定内容)和lottie-ios(原始框架)直接放入项目中使用。使用时只需使用RAAnimationView替换气泡图层即可。

修改lottie文件摘要如下:

  • LOTAnimationView.h

由于lottie对AE插件导出后的图片资源是不在json中的,所以做了一个简易的mac程序来自动识别并导入图片数据(默认为base64方式)参见changeBubbleJson

注意,这个mac程序并不能实现自动添加计算方法的参数,因此需要用户自行修改json,在合适的位置添加pty以实现对应图层的位置大小修改,在最外层添加raty以保证修改画布原始尺寸。

本文由博客群发一文多发等运营工具平台 OpenWrite 发布

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容