时间:2020-12-16 09:46:57 | 栏目:Android代码 | 点击:次
本篇文章主要介绍了Android 利用三阶贝塞尔曲线绘制运动轨迹的示例,分享给大家,具体如下:
实现点赞效果,自定义起始点以及运动轨迹
效果图:
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; } }