自定义View:仿抖音直播点赞效果
目录
源码(主要是增加了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);
}
}