自定义View:仿抖音直播点赞效果

目录

一、效果图

        1、第一版本:在屏幕底部开始显示 

        2、第二版本:点击任意位置都可以显示

        3、第三版本:给任意控件添加点赞效果

 二、代码

        1、第一版本代码    

        源码:

        示例:

2、第二版本代码

        源码(主要是增加了ontouchEvent的处理还有修改贝塞尔四个点的坐标范围):

        示例:

3、第三版本代码(主要是增加了ontouchlistener)

        源码:

        示例:


一、效果图

        1、第一版本:在屏幕底部开始显示 

        2、第二版本:点击任意位置都可以显示

        3、第三版本:给任意控件添加点赞效果

 二、代码

        1、第一版本代码    

        源码:

package com.example.lovelayout;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import java.util.Random;

import androidx.core.content.ContextCompat;

public class MyLoveLayout extends RelativeLayout {
    private int[] imgResId;
    private Random random;
    private int mWidth;
    private int mHeight;
    private int drawableWidth;
    private int drawableHeight;

    public MyLoveLayout(Context context) {
        this(context, null);
    }

    public MyLoveLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyLoveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        imgResId = new int[]{R.drawable.off, R.drawable.on};
        random = new Random();
        Drawable drawable = ContextCompat.getDrawable(context, R.drawable.on);
        drawableWidth = drawable.getIntrinsicWidth();
        drawableHeight = drawable.getIntrinsicHeight();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
    }

    //添加图片
    public void addView() {
        ImageView loveImg = new ImageView(getContext());
        loveImg.setImageResource(imgResId[random.nextInt(imgResId.length)]);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        params.addRule(CENTER_HORIZONTAL);
        params.addRule(ALIGN_PARENT_BOTTOM);
        loveImg.setLayoutParams(params);
        addView(loveImg);
        //开始动画
        startAni(loveImg);
    }

    private void startAni(ImageView loveImg) {
        //透明度、缩放动画
        ObjectAnimator alphaAni = ObjectAnimator.ofFloat(loveImg, "Alpha", 0f, 1f);
        ObjectAnimator scaleXAni = ObjectAnimator.ofFloat(loveImg, "ScaleX", 0.2f, 1f);
        alphaAni.setDuration(500);
        scaleXAni.setDuration(500);

        //贝塞尔路径动画
        ValueAnimator bizerAni = getBizerPathAni(loveImg);

        AnimatorSet aniSet = new AnimatorSet();
        aniSet.play(alphaAni).with(scaleXAni).before(bizerAni);
        aniSet.start();
    }

    private ValueAnimator getBizerPathAni(final ImageView loveImg) {
        //point0 point3 起始点和终点 point1 point2 控制点
        PointF point0 = new PointF(mWidth / 2 - drawableWidth / 2, mHeight - drawableHeight);//左上角
        PointF point1 = new PointF(random.nextInt(mWidth - drawableWidth), random.nextInt(mHeight / 2)+ mHeight / 2);
        PointF point2 = new PointF(random.nextInt(mWidth - drawableWidth), random.nextInt(mHeight / 2) );
        PointF point3 = new PointF(random.nextInt(mWidth - drawableWidth), 0);

        final ValueAnimator bizerPathAni = ValueAnimator.ofObject(new MyEvalutator(point0, point3), point1, point2);
        bizerPathAni.setDuration(3000);
        bizerPathAni.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF bizerPoint = (PointF) animation.getAnimatedValue();
                //改变imageview的坐标
                loveImg.setX(bizerPoint.x);
                loveImg.setY(bizerPoint.y);
                float t = animation.getAnimatedFraction();
                //设置透明度,慢慢消失
                loveImg.setAlpha(1.3f - t);
            }
        });
        bizerPathAni.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                removeView(loveImg);
            }
        });
        return bizerPathAni;
    }

    private class MyEvalutator implements TypeEvaluator<PointF> {
        private PointF point0;//起点
        private PointF point3;//终点
        private PointF pointBizer;

        public MyEvalutator(PointF point0, PointF point3) {
            this.point0 = point0;
            this.point3 = point3;
            pointBizer = new PointF();
        }

        @Override
        public PointF evaluate(float t, PointF point1, PointF point2) {
            //三次贝塞尔曲线
            pointBizer.x = (float) (point0.x * Math.pow(1 - t, 3) + 3 * point1.x * t * Math.pow(1 - t, 2)
                    + 3 * point2.x * t * t * (1 - t) + point3.x * Math.pow(t, 3));

            pointBizer.y = (float) (point0.y * Math.pow(1 - t, 3) + 3 * point1.y * t * Math.pow(1 - t, 2)
                    + 3 * point2.y * t * t * (1 - t) + point3.y * Math.pow(t, 3));
            return pointBizer;
        }
    }


}

        示例:

<?xml version="1.0" encoding="utf-8"?>
<com.example.lovelayout.MyLoveLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/loveLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="点击" />

</com.example.lovelayout.MyLoveLayout>
package com.example.lovelayout;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    MyLoveLayout loveLayout;
    Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loveLayout = (MyLoveLayout) findViewById(R.id.loveLayout);
        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                loveLayout.addView();
            }
        });
    }
}

2、第二版本代码

        源码(主要是增加了ontouchEvent的处理还有修改贝塞尔四个点的坐标范围):

package com.example.lovelayout;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import java.util.Random;

import androidx.core.content.ContextCompat;

public class MyLoveLayout extends RelativeLayout {
    private int[] imgResId;
    private Random random;
    private int mWidth;
    private int mHeight;
    private int drawableWidth;
    private int drawableHeight;
    private PointF downPoint;//手指按下的点

    public MyLoveLayout(Context context) {
        this(context, null);
    }

    public MyLoveLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyLoveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        imgResId = new int[]{R.drawable.off, R.drawable.on};
        random = new Random();
        Drawable drawable = ContextCompat.getDrawable(context, R.drawable.on);
        drawableWidth = drawable.getIntrinsicWidth();
        drawableHeight = drawable.getIntrinsicHeight();
        downPoint=new PointF();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
    }

    //添加图片
    private void addView() {
        ImageView loveImg = new ImageView(getContext());
        loveImg.setImageResource(imgResId[random.nextInt(imgResId.length)]);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        loveImg.setLayoutParams(params);
        loveImg.setX(downPoint.x-drawableWidth/2);
        loveImg.setY(downPoint.y-drawableHeight/2);
        addView(loveImg);
        //开始动画
        startAni(loveImg);
    }

    private void startAni(ImageView loveImg) {
        //透明度、缩放动画
        ObjectAnimator alphaAni = ObjectAnimator.ofFloat(loveImg, "Alpha", 0f, 1f);
        ObjectAnimator scaleXAni = ObjectAnimator.ofFloat(loveImg, "ScaleX", 0.2f, 1f);
        alphaAni.setDuration(100);
        scaleXAni.setDuration(100);

        //贝塞尔路径动画
        ValueAnimator bizerAni = getBizerPathAni(loveImg);

        AnimatorSet aniSet = new AnimatorSet();
        aniSet.play(alphaAni).with(scaleXAni).before(bizerAni);
        aniSet.start();
    }

    private ValueAnimator getBizerPathAni(final ImageView loveImg) {
        //point0 point3 起始点和终点 point1 point2 控制点
        PointF point0 = new PointF(downPoint.x-drawableWidth/2, downPoint.y - drawableHeight/2);//左上角
        PointF point1 = new PointF(random.nextInt(mWidth - drawableWidth),random.nextInt((int) ((mHeight-downPoint.y) / 2)) +(mHeight-downPoint.y) / 2 );
        PointF point2 = new PointF(random.nextInt(mWidth - drawableWidth), random.nextInt((int) ((mHeight-downPoint.y) / 2)));
        PointF point3 = new PointF(random.nextInt(mWidth - drawableWidth), 0);

        final ValueAnimator bizerPathAni = ValueAnimator.ofObject(new MyEvalutator(point0, point3), point1, point2);
        bizerPathAni.setDuration(3000);
        bizerPathAni.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF bizerPoint = (PointF) animation.getAnimatedValue();
                //改变imageview的坐标
                loveImg.setX(bizerPoint.x);
                loveImg.setY(bizerPoint.y);
                float t = animation.getAnimatedFraction();
                //设置透明度,慢慢消失
                loveImg.setAlpha(1.3f - t);
            }
        });
        bizerPathAni.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                removeView(loveImg);
            }
        });
        return bizerPathAni;
    }

    private class MyEvalutator implements TypeEvaluator<PointF> {
        private PointF point0;//起点
        private PointF point3;//终点
        private PointF pointBizer;

        public MyEvalutator(PointF point0, PointF point3) {
            this.point0 = point0;
            this.point3 = point3;
            pointBizer = new PointF();
        }

        @Override
        public PointF evaluate(float t, PointF point1, PointF point2) {
            //三次贝塞尔曲线
            pointBizer.x = (float) (point0.x * Math.pow(1 - t, 3) + 3 * point1.x * t * Math.pow(1 - t, 2)
                    + 3 * point2.x * t * t * (1 - t) + point3.x * Math.pow(t, 3));

            pointBizer.y = (float) (point0.y * Math.pow(1 - t, 3) + 3 * point1.y * t * Math.pow(1 - t, 2)
                    + 3 * point2.y * t * t * (1 - t) + point3.y * Math.pow(t, 3));
            return pointBizer;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downPoint.x = event.getX();
                downPoint.y = event.getY();
                addView();
                break;
        }
        return super.onTouchEvent(event);
    }
}

        示例:

<?xml version="1.0" encoding="utf-8"?>
<com.example.lovelayout.MyLoveLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/loveLayout"
    tools:context=".MainActivity">


</com.example.lovelayout.MyLoveLayout>
package com.example.lovelayout;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    MyLoveLayout loveLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loveLayout = (MyLoveLayout) findViewById(R.id.loveLayout);
    }
}

3、第三版本代码(主要是增加了ontouchlistener)

        源码:

package com.example.lovelayout;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import java.util.Random;

import androidx.core.content.ContextCompat;

public class MyLoveLayout extends RelativeLayout {
    private static final String TAG="MyLoveLayout";
    private int[] imgResId;
    private Random random;
    private int mWidth;
    private int mHeight;
    private int drawableWidth;
    private int drawableHeight;
    private PointF downPoint;//手指按下的点

    public MyLoveLayout(Context context) {
        this(context, null);
    }

    public MyLoveLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyLoveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        imgResId = new int[]{R.drawable.off, R.drawable.on};
        random = new Random();
        Drawable drawable = ContextCompat.getDrawable(context, R.drawable.on);
        drawableWidth = drawable.getIntrinsicWidth();
        drawableHeight = drawable.getIntrinsicHeight();
        downPoint=new PointF();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
    }

    //添加图片
    private void addView() {
        ImageView loveImg = new ImageView(getContext());
        loveImg.setImageResource(imgResId[random.nextInt(imgResId.length)]);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        loveImg.setLayoutParams(params);
        loveImg.setX(downPoint.x-drawableWidth/2);
        loveImg.setY(downPoint.y-drawableHeight/2);
        addView(loveImg);
        //开始动画
        startAni(loveImg);
    }

    private void startAni(ImageView loveImg) {
        //透明度、缩放动画
        ObjectAnimator alphaAni = ObjectAnimator.ofFloat(loveImg, "Alpha", 0f, 1f);
        ObjectAnimator scaleXAni = ObjectAnimator.ofFloat(loveImg, "ScaleX", 0.2f, 1f);
        alphaAni.setDuration(100);
        scaleXAni.setDuration(100);

        //贝塞尔路径动画
        ValueAnimator bizerAni = getBizerPathAni(loveImg);

        AnimatorSet aniSet = new AnimatorSet();
        aniSet.play(alphaAni).with(scaleXAni).before(bizerAni);
        aniSet.start();
    }

    private ValueAnimator getBizerPathAni(final ImageView loveImg) {
        //point0 point3 起始点和终点 point1 point2 控制点
        PointF point0 = new PointF(downPoint.x-drawableWidth/2, downPoint.y - drawableHeight/2);//左上角
        PointF point1 = new PointF(random.nextInt(mWidth - drawableWidth),random.nextInt((int) ((mHeight-downPoint.y) / 2)) +(mHeight-downPoint.y) / 2 );
        PointF point2 = new PointF(random.nextInt(mWidth - drawableWidth), random.nextInt((int) ((mHeight-downPoint.y) / 2)));
        PointF point3 = new PointF(random.nextInt(mWidth - drawableWidth), 0);

        final ValueAnimator bizerPathAni = ValueAnimator.ofObject(new MyEvalutator(point0, point3), point1, point2);
        bizerPathAni.setDuration(3000);
        bizerPathAni.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF bizerPoint = (PointF) animation.getAnimatedValue();
                //改变imageview的坐标
                loveImg.setX(bizerPoint.x);
                loveImg.setY(bizerPoint.y);
                float t = animation.getAnimatedFraction();
                //设置透明度,慢慢消失
                loveImg.setAlpha(1.3f - t);
            }
        });
        bizerPathAni.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                removeView(loveImg);
            }
        });
        return bizerPathAni;
    }

    private class MyEvalutator implements TypeEvaluator<PointF> {
        private PointF point0;//起点
        private PointF point3;//终点
        private PointF pointBizer;

        public MyEvalutator(PointF point0, PointF point3) {
            this.point0 = point0;
            this.point3 = point3;
            pointBizer = new PointF();
        }

        @Override
        public PointF evaluate(float t, PointF point1, PointF point2) {
            //三次贝塞尔曲线
            pointBizer.x = (float) (point0.x * Math.pow(1 - t, 3) + 3 * point1.x * t * Math.pow(1 - t, 2)
                    + 3 * point2.x * t * t * (1 - t) + point3.x * Math.pow(t, 3));

            pointBizer.y = (float) (point0.y * Math.pow(1 - t, 3) + 3 * point1.y * t * Math.pow(1 - t, 2)
                    + 3 * point2.y * t * t * (1 - t) + point3.y * Math.pow(t, 3));
            return pointBizer;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
//        switch (event.getAction()) {
//            case MotionEvent.ACTION_DOWN:
//                downPoint.x = event.getX();
//                downPoint.y = event.getY();
//                addView();
//                break;
//        }
        return super.onTouchEvent(event);
    }

    //增加view的触摸监听事件
    public void attachView(View view) {
        view.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        downPoint.x = event.getRawX();
                        //这里要减去状态栏的高度,否则高度不对,如果有ActionBar,这里也要记得减掉
                        downPoint.y = event.getRawY()-getStatusBarHeight();
                        addView();
                        break;
                }
                return true;
            }
        });
    }

    //获取状态栏高度
    private int getStatusBarHeight() {
        int height=0;
        Resources resources=getContext().getResources();
        int resId = resources.getIdentifier("status_bar_height", "dimen", "android");
        if (resId > 0) {
            height = resources.getDimensionPixelOffset(resId);
        }
        height=(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,height,resources.getDisplayMetrics());
        return height;
    }
}

        示例:

<?xml version="1.0" encoding="utf-8"?>
<com.example.lovelayout.MyLoveLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/loveLayout"
    tools:context=".MainActivity">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginTop="500dp"
        android:background="@drawable/on"
        android:id="@+id/img" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button"
        android:layout_centerInParent="true"
        android:text="点我" />

</com.example.lovelayout.MyLoveLayout>
package com.example.lovelayout;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {
    MyLoveLayout loveLayout;
    ImageView img;
    Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loveLayout = (MyLoveLayout) findViewById(R.id.loveLayout);
        img = (ImageView) findViewById(R.id.img);
        button = (Button) findViewById(R.id.button);
        loveLayout.attachView(button);
        loveLayout.attachView(img);

    }
}