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

作者 : 开心源码 本文共5699个字,预计阅读时间需要15分钟 发布时间: 2022-05-12 共242人阅读

当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

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

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 高仿马蜂窝旅游头像泡泡动画

发表回复