Android进阶之路Android开发经验谈Android技术知识

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

2020-06-08  本文已影响0人  像程序一样思考

/ 前言 /

一名优秀的Android开发,需要一份完备的 知识体系,在这里,让我们一起成长为自己所想的那样~。

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

效果分析:

  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 + "来过");
                }
            }
        }
    }

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

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();
    } 

image.gif

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. 三阶赛贝尔曲线的计算下面以左边的为例

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

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;
    } 
最后连贯起来看看效

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();
        }
    } 

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

上一篇下一篇

猜你喜欢

热点阅读