实现点赞效果, 自定义起始点以及运动轨迹
效果图:
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
来源: http://www.jianshu.com/p/f3e25468475a