您的位置:首页 > 编程语言

200行代码打造直播间点赞、飘心效果

2017-09-01 17:02 183 查看

先上效果图:



 用到了二阶贝塞尔曲线,先来学习学习。
  



  




 原理: 由 P0 至 P1 的连续点 Q0,描述一条线段。 
          由 P1 至 P2 的连续点 Q1,描述一条线段。 
          由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线。


 按照我自己的理解就是,P0和P2分别为起点和终点,P1为控制点,来控制曲线的轨迹。

 因为是很多个心,所以我用HasMap来装,因为在动画结束之后我们要把心在界面移除,所以用key来删除当前的心。因为是自定义View,所以我把曲线和动画及更新的位置都放在了内部类里面,让自行操作。了解了二阶贝塞尔曲线的原理之后,我把贝塞尔曲线的控制点放在了下图中圈中的位置,控制点是随机的。起点是最下面的中点,终点是最上面x轴随机的位置。



 下面上代码,基本都有注释。

 
classBezierEvaluatorimplementsTypeEvaluator<Point>,ValueAnimator.AnimatorUpdateListener{

privatePointrandomPoint;
c4e1
//随机控制点

privateintx;//每次更新的曲线的x坐标

privateinty;//每次更新的曲线的y坐标

publicinti;//随机选择的图片样式

publicStringkey;//用来删除动画结束的心

privateBitmapbitmap;//要画心的bitmap

publicPaintpaint;//画笔

publicBezierEvaluator(Stringkey){
this.key=key;
paint=newPaint();
paint.setAntiAlias(true);

i=newRandom().nextInt(5);
getImg(i);
//获得随机的bitmap
randomPoint=newPoint(newRandom().nextInt(mWidth/2)+mWidth/4,newRandom().nextInt(mHeight/2));
//获得随机控制点
PointstartP=newPoint(mWidth/2,mHeight-50);
//设置起点位置,向上偏移50
PointendP=newPoint(newRandom().nextInt(mWidth/2)+mWidth/4,0);
//设置终点位置,让终点在上图的随机位置

ValueAnimatoranim=ValueAnimator.ofObject(this,startP,endP);
//设置动画
anim.addUpdateListener(this);
anim.setDuration(5000);
anim.addListener(newAnimatorListenerAdapter(){
@Override
publicvoidonAnimationEnd(Animatoranimation){
super.onAnimationEnd(animation);
hashMap.remove(BezierEvaluator.this.key);
//在hashmap中把当前删除
randomIs.remove(BezierEvaluator.this.key);
//删除当前key值
bitmap.recycle();
//回收bitmap
bitmap=null;
//滞空bitmap
}
});
anim.setInterpolator(newLinearInterpolator());
//为动画加入插值器
anim.start();
}

privatevoidgetImg(inti){
switch(i){
case0:
bitmap=BitmapFactory.decodeResource(getResources(),
R.mipmap.heart1).copy(Bitmap.Config.ARGB_8888,true);
break;
case1:
bitmap=BitmapFactory.decodeResource(getResources(),
R.mipmap.heart2).copy(Bitmap.Config.ARGB_8888,true);
break;
case2:
bitmap=BitmapFactory.decodeResource(getResources(),
R.mipmap.heart3).copy(Bitmap.Config.ARGB_8888,true);
break;
case3:
bitmap=BitmapFactory.decodeResource(getResources(),
R.mipmap.heart4).copy(Bitmap.Config.ARGB_8888,true);
break;
case4:
bitmap=BitmapFactory.decodeResource(getResources(),
R.mipmap.heart5).copy(Bitmap.Config.ARGB_8888,true);
break;
}
}

@Override
publicPointevaluate(floatt,PointstartValue,PointendValue){
intx=(int)((1-t)*(1-t)*startValue.x+2*t*(1-t)*randomPoint.x+t*t*endValue.x);
inty=(int)((1-t)*(1-t)*startValue.y+2*t*(1-t)*randomPoint.y+t*t*endValue.y);
//根据二阶贝塞尔曲线公式获得在屏幕运动中的x,y坐标
returnnewPoint(x,y);
}

@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
//根据动画的更新来获得坐标来重绘onDraw
Pointpoint=(Point)animation.getAnimatedValue();
this.x=point.x;
this.y=point.y;
intalpha=point.y/4;
paint.setAlpha(alpha);
//为画笔设置透明度
invalidate();
}
}

 下面是自定义view中点击事件添加心以及对外开放添加心的方法。
  
@Override
publicvoidonClick(Viewv){
keys.add(String.valueOf(key));
hashMap.put(String.valueOf(key),newBezierEvaluator(String.valueOf(key)));
key++;
}

publicvoidaddHeart(intsize){
addSize=size;
if(size>0){
newHandler().postDelayed(newRunnable(){
@Override
publicvoidrun(){
keys.add(String.valueOf(key));
hashMap.put(String.valueOf(key),newBezierEvaluator(String.valueOf(key)));
key++;
addSize--;
addHeart(addSize);
}
},100);
}else{
return;
}
}
 使用:
  
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<com.yogee.mycustom.view.HeartView
android:id="@+id/heartView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

</LinearLayout>


privateHeartViewheartView;

@Override
protectedvoidonCreate(@NullableBundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_six);
heartView=(HeartView)findViewById(R.id.heartView);
heartView.addHeart(500);
}


完整版代码:
packagecom.yogee.mycustom.view;

importandroid.animation.Animator;
importandroid.animation.AnimatorListenerAdapter;
importandroid.animation.TypeEvaluator;
importandroid.animation.ValueAnimator;
importandroid.content.Context;
importandroid.graphics.Bitmap;
importandroid.graphics.BitmapFactory;
importandroid.graphics.Canvas;
importandroid.graphics.Paint;
importandroid.graphics.Point;
importandroid.os.Handler;
importandroid.support.annotation.Nullable;
importandroid.util.AttributeSet;
importandroid.view.View;
importandroid.view.animation.LinearInterpolator;

importcom.yogee.mycustom.R;

importjava.util.ArrayList;
importjava.util.HashMap;
importjava.util.List;
importjava.util.Random;

publicclassHeartViewextendsViewimplementsView.OnClickListener{
privateintmWidth;

privateintmHeight;

privateintkey=0;

privateList<String>keys=newArrayList<>();//key值集合

privateHashMap<String,BezierEvaluator>hashMap=newHashMap<>();

privateintaddSize;

publicHeartView(Contextcontext){
super(context);
setOnClickListener(this);
}

publicHeartView(Contextcontext,@NullableAttributeSetattrs){
super(context,attrs);
setOnClickListener(this);
}

publicHeartView(Contextcontext,@NullableAttributeSetattrs,intdefStyleAttr){
super(context,attrs,defStyleAttr);
setOnClickListener(this);
}

@Override
protectedvoidonDraw(Canvascanvas){
super.onDraw(canvas);

if(hashMap.size()>0)
for(Stringi:keys){
canvas.drawBitmap(hashMap.get(i).bitmap,hashMap.get(i).x,hashMap.get(i).y,hashMap.get(i).paint);
}
}

@Override
protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
super.onMeasure(widthMeasureSpec,heightMeasureSpec);

mWidth=MeasureSpec.getSize(widthMeasureSpec);
mHeight=MeasureSpec.getSize(heightMeasureSpec);

setMeasuredDimension(mWidth,mHeight);
}

@Override
publicvoidonClick(Viewv){
keys.add(String.valueOf(key));
hashMap.put(String.valueOf(key),newBezierEvaluator(String.valueOf(key)));
key++;
}

publicvoidaddHeart(intsize){
addSize=size;
if(size>0){
newHandler().postDelayed(newRunnable(){
@Override
publicvoidrun(){
keys.add(String.valueOf(key));
hashMap.put(String.valueOf(key),newBezierEvaluator(String.valueOf(key)));
key++;
addSize--;
addHeart(addSize);
}
},100);
}else{
return;
}
}

classBezierEvaluatorimplementsTypeEvaluator<Point>,ValueAnimator.AnimatorUpdateListener{

privatePointrandomPoint;//随机控制点

privateintx;//每次更新的曲线的x坐标

privateinty;//每次更新的曲线的y坐标

publicinti;//随机选择的图片样式

publicStringkey;//用来删除动画结束的心

privateBitmapbitmap;//要画心的bitmap

publicPaintpaint;//画笔

publicBezierEvaluator(Stringkey){
this.key=key;
paint=newPaint();
paint.setAntiAlias(true);

i=newRandom().nextInt(5);
getImg(i);
//获得随机的bitmap
randomPoint=newPoint(newRandom().nextInt(mWidth/2)+mWidth/4,newRandom().nextInt(mHeight/2));
//获得随机控制点
PointstartP=newPoint(mWidth/2,mHeight-50);
//设置起点位置,向上偏移50
PointendP=newPoint(newRandom().nextInt(mWidth/2)+mWidth/4,0);
//设置终点位置,让终点在上图的随机位置

ValueAnimatoranim=ValueAnimator.ofObject(this,startP,endP);
//设置动画
anim.addUpdateListener(this);
anim.setDuration(5000);
anim.addListener(newAnimatorListenerAdapter(){
@Override
publicvoidonAnimationEnd(Animatoranimation){
super.onAnimationEnd(animation);
hashMap.remove(BezierEvaluator.this.key);
//在hashmap中把当前删除
keys.remove(BezierEvaluator.this.key);
//删除当前key值
bitmap.recycle();
//回收bitmap
bitmap=null;
//滞空bitmap
}
});
anim.setInterpolator(newLinearInterpolator());
//为动画加入插值器
anim.start();
}

privatevoidgetImg(inti){
switch(i){
case0:
bitmap=BitmapFactory.decodeResource(getResources(),
R.mipmap.heart1).copy(Bitmap.Config.ARGB_8888,true);
break;
case1:
bitmap=BitmapFactory.decodeResource(getResources(),
R.mipmap.heart2).copy(Bitmap.Config.ARGB_8888,true);
break;
case2:
bitmap=BitmapFactory.decodeResource(getResources(),
R.mipmap.heart3).copy(Bitmap.Config.ARGB_8888,true);
break;
case3:
bitmap=BitmapFactory.decodeResource(getResources(),
R.mipmap.heart4).copy(Bitmap.Config.ARGB_8888,true);
break;
case4:
bitmap=BitmapFactory.decodeResource(getResources(),
R.mipmap.heart5).copy(Bitmap.Config.ARGB_8888,true);
break;
}
}

@Override
publicPointevaluate(floatt,PointstartValue,PointendValue){
intx=(int)((1-t)*(1-t)*startValue.x+2*t*(1-t)*randomPoint.x+t*t*endValue.x);
inty=(int)((1-t)*(1-t)*startValue.y+2*t*(1-t)*randomPoint.y+t*t*endValue.y);
//根据二阶贝塞尔曲线公式获得在屏幕运动中的x,y坐标
returnnewPoint(x,y);
}

@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
//根据动画的更新来获得坐标来重绘onDraw
Pointpoint=(Point)animation.getAnimatedValue();
this.x=point.x;
this.y=point.y;
intalpha=point.y/4;
paint.setAlpha(alpha);
//为画笔设置透明度
invalidate();
}
}
}


 完成。

  
  





                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: