高仿马蜂窝旅游头像泡泡动画

当pm制定完下一版本需求打开马蜂窝旅游app准备出去嗨一圈的时候 看到了马蜂窝旅游app的一个用户头像动画后。。。(=@__@=) 先看看效果图

demo_01

效果分析:

demo_03

  1. 涉及到有多个view在做动画操作 这里需要继承FrameLayout来左父布局 供图片做动画操作
  2. 每个子view的动画路径类似于S型 我这里采用的是三阶贝塞尔曲线和PathMeasure来完成动画运动路径的封装
  3. 每个子view动画执行完后 是移除添加新的view进来 还是回收重新利用 本案例是直接移除再添加新的(回收重新利用还没来得及去考虑该怎么写)
  4. 动画是循环不停的播放 我采用的是RxJava timer()操作符 不断的发送随机延迟消息去通知动画的执行
  5. 最后就剩下一些停止动画操作的开关设定

实现步骤

1.一些基本的初始化工作

public class HeadBubbleView extends FrameLayout {
    //这个position很重要 不断的取出图片资源 靠它累加完成的
    private int position = 0;
    public HeadBubbleView(@NonNull Context context) {
        this(context,null);
    }
    public HeadBubbleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        setFocusable(false);
        //三阶贝塞尔曲线控制点一
        controlPointOne = new Point();
        //三阶贝塞尔曲线控制点二
        controlPointTwo = new Point();
        //每个子view的宽高是固定的
        viewWidth = viewHeight = SizeUtils.dp2px(context, 22);
        marginLeft = SizeUtils.dp2px(context, 15);
        marginBot = SizeUtils.dp2px(context, 21);
        //父View的高度也是固定的
        height = SizeUtils.dp2px(context, 130);
        //用于从PathMeasure 中不断的取出 曲线的路径值
        pos = new float[2];
        tan = new float[2];
        initView();
    }

2.初始化的时候数据的加载状态

private void initView() {
        //这个ImageView将不执行动画 用于底部不断切换图片展示
        tempImageView = getImageView();
        textView = getTextView();
        initData(tempImageView);
    } 
//创建执行动画的具体角色    
private ImageView getImageView() {
        LayoutParams layoutParams = new LayoutParams(viewWidth, viewHeight);
        ImageView roundedImageView = new ImageView(getContext());
        roundedImageView.setScaleType(ImageView.ScaleType.FIT_XY);
        layoutParams.gravity = Gravity.BOTTOM | Gravity.END;
        layoutParams.setMargins(0, 0, marginLeft, marginBot);
        addView(roundedImageView, layoutParams);
        return roundedImageView;
    }
//创建用于显示坐标xx来过的TextView    
private TextView getTextView() {
        int bottom = SizeUtils.dp2px(mContext, 23);
        int right = SizeUtils.dp2px(mContext, 41);
        LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams.gravity = Gravity.END | Gravity.BOTTOM;
        layoutParams.setMargins(0, 0, right, bottom);

        TextView tv_name = new TextView(mContext);
        tv_name.setTextSize(12);
        tv_name.setTextColor(Color.WHITE);
        addView(tv_name, layoutParams);
        return tv_name;
    }
//第一次加载数据
private void initData(ImageView roundedImageView) {
        if (null != browseEntities && browseEntities.size() > 0) {
            //第一次去第0个数据
            BrowseEntity browseEntity = browseEntities.get(position);
            if (null != browseEntity) {
                roundedImageView.setBackgroundResource(browseEntity.drawableId);
                String username = browseEntity.name;
                if (!TextUtils.isEmpty(username)) {
                    textView.setText(username + "来过");
                }
            }
        }
    }

由上面的操作就完成基础显示


demo_02

3.接下来完成第一阶段动画 由最小缩放到最大

private boolean createAnimView() {
        if (!isStop) {
            return true;
        }
        ImageView imageView = getImageView();
        //创建好后 设置缩放到最小
        imageView.setScaleX(0);
        imageView.setScaleY(0);
        initData(imageView);
        startScaleAnim(imageView);
        return false;
    }
//执行缩放动画    
private void startScaleAnim(final ImageView imageView) {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
        valueAnimator.setDuration(800);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float animatedValue = (float) animation.getAnimatedValue();
                imageView.setScaleX(0.1f + animatedValue);
                imageView.setScaleY(0.1f + animatedValue);
            }
        });
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (position == browseEntities.size() - 1) {
                    position = 0;
                } else {
                    position++;
                }
          BrowseEntity browseEntity = browseEntities.get(position);
        //动画执行完后要立马取出下一个图片 把底部的图片显示更新
        tempImageView.setBackgroundResource(browseEntity.drawableId);
        //动画执行完执行平移动画       
        startTranslationAnimator(imageView);
            }
        });
        valueAnimator.start();
    }
demo_04

4.第二阶段的曲线运动缩小动画

private void startTranslationAnimator(final ImageView imageView) {
        Path path;
        int seed = (int) (Math.random() * 100);
        //根据随机数来确定是走左边曲线还是右边曲线
        if (seed % 2 == 0) {
            //曲线路径的封装
            path = createRightPath();
        } else {
            //曲线路径的封装
            path = createLeftPath();
        }
        //通过PathMeasure 和ValueAnimator结合 在不同的阶段取出运动路径的x,y值
        final PathMeasure pathMeasure = new PathMeasure(path, false);
        final ValueAnimator valueAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
        valueAnimator.setDuration(riseDuration);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float animatedValue = (float) animation.getAnimatedValue();
                int length = (int) (pathMeasure.getLength() * animatedValue);
               //在不同的阶段取出运动路径的x,y值
                pathMeasure.getPosTan(length, pos, tan);
                imageView.setTranslationX(pos[0]);
                imageView.setTranslationY(pos[1]);
                //同时做透明度动画
                imageView.setAlpha(animatedValue);
                if (animatedValue >= 0.5f) {
                    imageView.setScaleX(0.2f + animatedValue);
                    imageView.setScaleY(0.2f + animatedValue);
                }
            }
        });
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                //动画执行完就移除View
                removeView(imageView);
            }
        });
        valueAnimator.start();
    }

5.三阶赛贝尔曲线的计算 下面以左边的为例

这里我也没有更好的办法去计算 是通过不断预估尝试出来的 如果有大佬在这里有很好的计算方法 请务必告知下

dome_08
private Path createLeftPath() {
        Path path = new Path();
        float nextFloat = new Random().nextFloat();
        path.moveTo(nextFloat, -height * 1.0f / 1.8f);
        //曲线控制点一
        controlPointOne.x = -(viewWidth);
        controlPointOne.y = -height / 5;
        //曲线控制点二
        controlPointTwo.x = -(viewWidth + marginLeft / 2);
        controlPointTwo.y = (int) (-height * 0.15);
        //生成三阶贝塞尔曲线
        path.cubicTo(controlPointOne.x, controlPointOne.y, controlPointTwo.x, controlPointTwo.y, 0, 0);
        return path;
    }

最后连贯起来看看效果


demo_07

6.最后使用RxJava 的timer()操作符 发延迟消息来让整个动画循环执行起来

这里也可以用handler 来发消息处理

public void startAnimation(int innerDelay) {
        subscribe = Observable.timer(innerDelay, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long aLong) throws Exception {
                        if (createAnimView()) return;
                        
                        int duration = (int) (1500 * Math.random());
                        if (duration < 500) {
                            duration = 500;
                        }
                        //循环调用
                        startAnimation(500 + duration);
                    }
                });
    }
    
//动画执行的一些开关操作  
public void stopAnimator() {
        isStop = false;
        if (null != subscribe) {
            subscribe.dispose();
        }
    }   
demo_08

到这里整个动画流程到这里就结束了,当然在内存的管理上还没有做到极致 大家可以去自由发挥, 希望这篇水文能帮助到那些有类似需求的同学,我们应该把时间拿去做一些更有用的事情,不过截止到目前 马蜂窝最新版 已经没有该头像的泡泡动画,想必他们也改了吧!

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 1 CALayer IOS SDK详解之CALayer(一) http://doc.okbase.net/Hell...
    Kevin_Junbaozi阅读 4,981评论 3 23
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,001评论 5 13
  • 如果想让事情变得顺利,只有靠自己--夏尔·纪尧姆 上一章介绍了隐式动画的概念。隐式动画是在iOS平台创建动态用户界...
    夜空下最亮的亮点阅读 1,839评论 0 1
  • 【Android 动画】 动画分类补间动画(Tween动画)帧动画(Frame 动画)属性动画(Property ...
    Rtia阅读 5,954评论 1 38