自定义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);
}
}