Android 利用三阶贝塞尔曲线绘制运动轨迹

实现点赞效果,自定义起始点以及运动轨迹

效果图:


heart.jpg

xml布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/rl_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="stone.wshh.com.touch.MainActivity">
    <stone.wshh.com.touch.MyLoveLayout
        android:layout_marginBottom="100dp"
        android:layout_marginRight="15dp"
        android:id="@+id/love_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </stone.wshh.com.touch.MyLoveLayout>
    <Button
        android:id="@+id/bt_bottom"
        android:text="begin"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="20dp"
        android:layout_width="100dp"
        android:layout_height="50dp" />
</RelativeLayout>

MainActivity类:

public class MainActivity extends Activity implements View.OnClickListener{
    private Button btBottom;
//    private WaitNoticeDialog dialog;
//    public Handler handler;
    private MyLoveLayout love;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btBottom = (Button) findViewById(R.id.bt_bottom);
        love = (MyLoveLayout) findViewById(R.id.love_layout);
        btBottom.setOnClickListener(this);
//        handler=new IHandler(this);
//        dialog = new WaitNoticeDialog(this);
    }

    static class IHandler extends Handler {
        private WeakReference<MainActivity> ui;
        IHandler(MainActivity ui) {
            this.ui = new WeakReference<MainActivity>(ui);
        }
        @Override
        public void handleMessage(Message msg) {
            if(ui!=null&&ui.get()!=null){
                ui.get().handleMsg(msg);
            }
        }
    }

    /**
     * 线程消息处理
     * @param msg
     */
    public void handleMsg(Message msg){
        switch (msg.what) {
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.bt_bottom:
                love.addHeart();
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
//        handler.removeCallbacksAndMessages(null);
    }
}

自定义view:MyLoveLayout

public class MyLoveLayout extends RelativeLayout {
    private Drawable[] drawables;
    private Interpolator[] mInterpolators;
    private int dWidth, mWidth;
    private int dHeight, mHeight;
    private LayoutParams lp;
    private Random random = new Random();

    public MyLoveLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        //imageView位置是相对于MyLoveLayout
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //得到本布局的宽高
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
    }

    private void init() {

        // 初始化显示的图片
        drawables = new Drawable[7];
        drawables[0] = getResources().getDrawable(R.drawable.heart_1);
        drawables[1] = getResources().getDrawable(R.drawable.heart_2);
        drawables[2] = getResources().getDrawable(R.drawable.heart_3);
        drawables[3] = getResources().getDrawable(R.drawable.heart_4);
        drawables[4] = getResources().getDrawable(R.drawable.heart_5);
        drawables[5] = getResources().getDrawable(R.drawable.heart_6);
        drawables[6] = getResources().getDrawable(R.drawable.heart_7);

        // 初始化插补器
        mInterpolators = new Interpolator[4];
        mInterpolators[0] = new LinearInterpolator();// 线性
        mInterpolators[1] = new AccelerateInterpolator();// 加速
        mInterpolators[2] = new DecelerateInterpolator();// 减速
        mInterpolators[3] = new AccelerateDecelerateInterpolator();// 先加速后减速

        // 获取图片宽高
//        dWidth = drawables[0].getIntrinsicWidth();
//        dHeight = drawables[0].getIntrinsicHeight();
        //手动设置宽高
        dWidth = dip2px(getContext(), 40);
        dHeight = dip2px(getContext(), 40);
        lp = new LayoutParams(dWidth, dHeight);
        //设置view控件的起始位置
//        lp.addRule(CENTER_HORIZONTAL, TRUE);// 这里的TRUE 要注意 不是true
        lp.addRule(ALIGN_PARENT_RIGHT, TRUE);
        lp.addRule(ALIGN_PARENT_BOTTOM, TRUE);

    }

    /**
     * dp转px值
     */
    private int dip2px(Context context, float dpValue) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 进场动画,三种同时播放
     * alpha透明度 (80%-0%)
     * scaleX 宽度 target(20%-100%)
     * scaleY 高度
     * @param target
     * @return
     */
    private AnimatorSet getEnterAnimator(final View target) {
        ObjectAnimator alpha = ObjectAnimator.ofFloat(target, View.ALPHA, 0.2f, 1f);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, 0.2f, 1f);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, 0.2f, 1f);
        AnimatorSet enter = new AnimatorSet();
        enter.setTarget(target);
        enter.setInterpolator(new LinearInterpolator());
        enter.setDuration(500).playTogether(alpha, scaleX, scaleY);
        return enter;
    }

    private ValueAnimator getBezierValueAnimator(final View target) {
        // 初始化贝塞尔估值器
        //随机产生两个点,以确定一条3阶贝塞尔曲线
        BezierEvaluator evaluator = new BezierEvaluator(getPointF(2), getPointF(1));
        // 起点在底部中心位置,终点在底部随机一个位置,改变new PointF()中值来改变起始位置
//        ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((mWidth - dWidth) /
//                2, mHeight - dHeight), new PointF(random.nextInt(getWidth()), 0));
        // 起点在右下角位置,终点在左上角位置
        ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF(mWidth - dWidth, mHeight - dHeight), new PointF(0, 0));
        animator.setTarget(target);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                // 这里获取到贝塞尔曲线计算出来的的x y值 赋值给view 这样就能让爱心随着曲线走啦
                PointF pointF = (PointF) valueAnimator.getAnimatedValue();
                target.setX(pointF.x);
                target.setY(pointF.y);
                // alpha动画,根据运动距离改变透明度
//                target.setAlpha(1 - valueAnimator.getAnimatedFraction());
                target.setAlpha(1 - valueAnimator.getAnimatedFraction() + 0.3f);
            }
        });

        animator.setDuration(3000);
        return animator;
    }

    private PointF getPointF(int i) {
        PointF pointF = new PointF();
        //pointF.x,pointF.y都是随机,因此可以产生n多种轨迹
        pointF.x = random.nextInt(mWidth);//0~loveLayout.Width

        //为了美观,建议尽量保证P2在P1上面,那怎么做呢??
        //只需要将该布局的高度分为上下两部分,让p1只能在下面部分范围内变化(1/2height~height),让p2只能在上面部分范围内变化(0~1/2height),因为坐标系是倒着的;

        //0~loveLayout.Height/2
        if (i == 1) {
            pointF.y = random.nextInt(mHeight / 2) + mHeight / 2;//P1点Y轴坐标变化
        } else if (i == 2) {//P2点Y轴坐标变化
            pointF.y = random.nextInt(mHeight / 2);
        }
//       写死的一条轨迹
//        if (i == 1) {
//            pointF.x=mWidth-dWidth*2;
//            pointF.y = 3*dHeight;
//        } else if (i == 2) {
//            pointF.x=dWidth*2;
//            pointF.y = mHeight -dHeight;
//        }

        return pointF;
    }

    public void addHeart() {

        final ImageView imageView = new ImageView(getContext());
        // 随机选一个爱心
        imageView.setImageDrawable(drawables[random.nextInt(6)]);
        imageView.setLayoutParams(lp);
        addView(imageView);

        AnimatorSet finalSet = new AnimatorSet();

        AnimatorSet enterAnimatorSet = getEnterAnimator(imageView);//入场动画
        ValueAnimator bezierValueAnimator = getBezierValueAnimator(imageView);//贝塞尔曲线路径动画

        finalSet.playSequentially(enterAnimatorSet, bezierValueAnimator);
//        finalSet.playSequentially(bezierValueAnimator);
        finalSet.setInterpolator(mInterpolators[random.nextInt(4)]);
        finalSet.setTarget(imageView);

        finalSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                removeView((imageView));//删除爱心
            }
        });
        finalSet.start();

    }


}

贝塞尔估值器:BezierEvaluator

public class BezierEvaluator implements TypeEvaluator<PointF> {
    private PointF mControlP1;
    private PointF mControlP2;



    public BezierEvaluator(PointF controlP1, PointF controlP2) {
        this.mControlP1 = controlP1;
        this.mControlP2 = controlP2;
    }

    @Override
    public PointF evaluate(float time, PointF start, PointF end) {

        float timeLeft = 1.0f - time;
        PointF point = new PointF();

        point.x = timeLeft * timeLeft * timeLeft * (start.x) + 3 * timeLeft * timeLeft * time *
                (mControlP1.x) + 3 * timeLeft * time *
                time * (mControlP2.x) + time * time * time * (end.x);

        point.y = timeLeft * timeLeft * timeLeft * (start.y) + 3 * timeLeft * timeLeft * time *
                (mControlP1.y) + 3 * timeLeft * time *
                time * (mControlP2.y) + time * time * time * (end.y);
        return point;
    }

}

参考文章://www.greatytc.com/p/c0d7ad796cee

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

推荐阅读更多精彩内容