美文网首页androidAndroid程序员
Android 利用三阶贝塞尔曲线绘制运动轨迹

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

作者: 涉水者 | 来源:发表于2018-03-07 16:33 被阅读214次

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

    效果图:


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

    参考文章:https://www.jianshu.com/p/c0d7ad796cee

    相关文章

      网友评论

      本文标题:Android 利用三阶贝塞尔曲线绘制运动轨迹

      本文链接:https://www.haomeiwen.com/subject/mjojfftx.html