当前位置: 移动技术网 > 移动技术>移动开发>Android > Android自定义View实现直播点赞特效

Android自定义View实现直播点赞特效

2020年07月30日  | 移动技术网移动技术  | 我要评论

由于开发的需要,需要开发类似直播点赞特效的需求,于是自定义view来实现这种效果

案例图:

自定义view

import android.animation.animator;
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.view;
import android.view.animation.acceleratedecelerateinterpolator;
import android.view.animation.accelerateinterpolator;
import android.view.animation.decelerateinterpolator;
import android.view.animation.interpolator;
import android.view.animation.linearinterpolator;
import android.widget.imageview;
import android.widget.relativelayout;
import com.xinrui.ndkapp.r;
import java.util.random;

/**
 * created by liuyong
 * data: 2017/8/8
 * github:https://github.com/mrallright
 * 直播点赞view
 */

public class givepraiseview extends relativelayout {
  private relativelayout.layoutparams layoutparams;//图片布局参数
  private pointf mpointf0, mpointf1, mpointf2, mpointf3;//通过3阶贝塞尔曲线控制图片的移动轨迹
  private int mscreenwidth, mscreenheight;//屏幕宽高
  private drawable[] mimagedrawables;//加载点赞红心图片,红黄蓝
  private int mdrawablewidth, mdrawableheight;//图片的宽高
  private random mrandom = new random();
  private int count = 0;
  private interpolator[] interpolators = new interpolator[4];

  public givepraiseview(context context) {
    super(context);
    init();
  }

  public givepraiseview(context context, attributeset attrs, int defstyleattr) {
    super(context, attrs, defstyleattr);
    init();
  }

  public givepraiseview(context context, attributeset attrs) {
    super(context, attrs);
    init();
  }

  @override
  protected void onsizechanged(int w, int h, int oldw, int oldh) {
    super.onsizechanged(w, h, oldw, oldh);
    mscreenheight = h;
    mscreenwidth = w;
  }

  //初始化drawable,layoutparams
  private void init() {
    mimagedrawables = new drawable[4];
    mimagedrawables[0] = getresources().getdrawable(r.drawable.pl_blue);
    mimagedrawables[1] = getresources().getdrawable(r.drawable.pl_red);
    mimagedrawables[2] = getresources().getdrawable(r.drawable.pl_yellow);
    mimagedrawables[3] = getresources().getdrawable(r.drawable.pl_red);
    // 插值器
    interpolators[0] = new acceleratedecelerateinterpolator(); // 在动画开始与结束的地方速率改变比较慢,在中间的时候加速
    interpolators[1] = new accelerateinterpolator(); // 在动画开始的地方速率改变比较慢,然后开始加速
    interpolators[2] = new decelerateinterpolator(); // 在动画开始的地方快然后慢
    interpolators[3] = new linearinterpolator(); // 以常量速率改变
    mdrawablewidth = mimagedrawables[0].getintrinsicwidth();
    mdrawableheight = mimagedrawables[0].getintrinsicheight();
    layoutparams = new layoutparams(50, 50);
    layoutparams.addrule(align_parent_bottom, true);
    layoutparams.addrule(align_parent_right, true);
    layoutparams.setmargins(0, 0, 60, 60);//放置在屏幕的右下角
    //这里为了演示我们现在布局初始化的时候,放置一个imageview,颜色随机,设置点击屏幕出现点赞效果
    imageview iv = new imageview(getcontext());
    iv.setlayoutparams(layoutparams);
    iv.setimagedrawable(mimagedrawables[0]);
    addview(iv);
    this.setonclicklistener(new onclicklistener() {
      @override
      public void onclick(view v) {
        for(int i=0;i<10;i++) {
          addgivepraiseimg(count);
          count++;
          if (count == 4) count = 0;
        }
      }
    });
  }

  //点击图片是添加imageview到布局中,并添加动画
  private void addgivepraiseimg(int count) {
    final imageview givepraiseimg = new imageview(getcontext());
    givepraiseimg.setlayoutparams(layoutparams);
    givepraiseimg.setimagedrawable(mimagedrawables[count]);
    addview(givepraiseimg);
    addanimator(givepraiseimg);//添加动画效果,动画分两部分,第一部分是产生图片时缩放和透明度,第二部是移动图片再进行透明度变化
  }

  private void addanimator(final imageview imageview) {
    //点击的时候,让图片经过放大,缩放效果,之后再开始沿着贝塞尔曲线的轨迹移动
    objectanimator alpha = objectanimator.offloat(imageview, "alpha", 0.3f, 1f);
    objectanimator scalex = objectanimator.offloat(imageview, "scalex", 0.2f, 1f);
    objectanimator scaley = objectanimator.offloat(imageview, "scaley", 0.2f, 1f);
    animatorset set = new animatorset();
    set.setduration(100);
    set.playtogether(alpha, scalex, scaley);
    set.settarget(imageview);
    set.addlistener(new animator.animatorlistener() {
      @override
      public void onanimationstart(animator animation) {

      }

      @override
      public void onanimationend(animator animation) {
        //设置贝塞尔曲线移动效果
        valueanimator va = getbzieranimator(imageview);//第二部分动画
        va.start();
      }

      @override
      public void onanimationcancel(animator animation) {

      }

      @override
      public void onanimationrepeat(animator animation) {

      }
    });
    set.start();
  }

  //初始化贝塞尔曲线的4个点
  private void initpointf() {
    mpointf0 = new pointf(mscreenwidth - 60 - 50, mscreenheight - 60 - 50);//起点是初始化时的点
    mpointf1 = new pointf(mrandom.nextint(mscreenwidth), mrandom.nextint((int) mpointf0.y));//第一个控制点必须要在起始点的上方
    mpointf2 = new pointf(mrandom.nextint(mscreenwidth), mrandom.nextint((int) mpointf1.y));//第二个控制点必须在第一个点的上方
    mpointf3 = new pointf(mrandom.nextint(mscreenwidth), -50);//终点在屏幕的最顶部0-图片的高度
  }


  /**
   *  自定义估值器计算图片移动的轨迹
   *  计算公式参考贝塞尔曲线3阶计算公式
   *  自定义估值器的方法可百度搜索
   *  其中估值器定义返回的结果为pointf
   */
  public class bezierevaluator implements typeevaluator<pointf> {
    private pointf pointf1, pointf2;

    public bezierevaluator(pointf p1, pointf p2) {
      this.pointf1 = p1;
      this.pointf2 = p2;
    }

    @override
    public pointf evaluate(float t, pointf p0, pointf p3) {
      pointf point = new pointf();
      point.x = p0.x * (1 - t) * (1 - t) * (1 - t) //
          + 3 * pointf1.x * t * (1 - t) * (1 - t)//
          + 3 * pointf2.x * t * t * (1 - t)//
          + p3.x * t * t * t;//

      point.y = p0.y * (1 - t) * (1 - t) * (1 - t) //
          + 3 * pointf1.y * t * (1 - t) * (1 - t)//
          + 3 * pointf2.y * t * t * (1 - t)//
          + p3.y * t * t * t;//
      return point;
    }
  }

  private valueanimator getvalueanimator(final imageview imageview) {
    initpointf();
    bezierevaluator bezierevaluator = new bezierevaluator(mpointf1, mpointf2);
    valueanimator valueanimator = valueanimator.ofobject(bezierevaluator, mpointf0, mpointf3);
    valueanimator.setduration(3000);
    valueanimator.settarget(imageview);
    valueanimator.addupdatelistener(new valueanimator.animatorupdatelistener() {
      @override
      public void onanimationupdate(valueanimator animation) {
        //改变imageview位置实现移动效果
        pointf point = (pointf) animation.getanimatedvalue();
        imageview.setx(point.x);
        imageview.sety(point.y);
        imageview.setalpha(1 - animation.getanimatedfraction());
        //动画结束移除imageview
        if (animation.getanimatedfraction() >= 1) {
          removeview(imageview);
        }
      }
    });
    return valueanimator;
  }

  /**
   * 贝塞尔动画
   * */
  private valueanimator getbzieranimator(final imageview iv) {
    // todo auto-generated method stub
    pointf[] pointfs = getpointfs(iv); // 4个点的坐标
    bezierevaluator evaluator = new bezierevaluator(pointfs[1], pointfs[2]);
    valueanimator valueanim = valueanimator.ofobject(evaluator, pointfs[0], pointfs[3]);
    valueanim.addupdatelistener(new valueanimator.animatorupdatelistener() {

      @override
      public void onanimationupdate(valueanimator animation) {
        // todo auto-generated method stub
        pointf p = (pointf) animation.getanimatedvalue();
        iv.setx(p.x);
        iv.sety(p.y);
        iv.setalpha(1- animation.getanimatedfraction()); // 透明度
        //动画结束移除imageview
        if (animation.getanimatedfraction() >= 1) {
          removeview(iv);
        }
      }
    });
    valueanim.settarget(iv);
    valueanim.setduration(3000);
    valueanim.setinterpolator(interpolators[new random().nextint(4)]);
    return valueanim;
  }

  private pointf[] getpointfs(imageview iv) {
    // todo auto-generated method stub
    pointf[] pointfs = new pointf[4];
    pointfs[0] = new pointf(); // p0
    pointfs[0].x = (mscreenwidth- layoutparams.width)/ 2;
    pointfs[0].y = mscreenheight - layoutparams.height;

    pointfs[1] = new pointf(); // p1
    pointfs[1].x = new random().nextint(mscreenwidth);
    pointfs[1].y = new random().nextint(mscreenheight /2) + mscreenheight / 2 + layoutparams.height;

    pointfs[2] = new pointf(); // p2
    pointfs[2].x = new random().nextint(mscreenwidth);
    pointfs[2].y = new random().nextint(mscreenheight /2);

    pointfs[3] = new pointf(); // p3
    pointfs[3].x = new random().nextint(mscreenwidth);
    pointfs[3].y = 0;
    return pointfs;
  }
}

2.givepraise_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@android:color/darker_gray">
  <com.xinrui.ndkapp.view.givepraiseview
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
  <!--<com.xinrui.ndkapp.view.lovelayout-->
    <!--android:layout_width="match_parent"-->
    <!--android:layout_height="match_parent"/>-->
</relativelayout>

3.activity 部分代码

import android.app.activity;
import android.os.bundle;

public class givepraiseactivity extends activity {
  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.givepraise_layout);
  }
}

4.估值器的运算

p0坐标:x坐标((布局的宽-心形图片宽)除以2),y坐标(布局的高 -心形图片高),这样获得的是顶部部水平中心点的坐标。
p1坐标:x坐标(横坐标中的随机位置),y坐标(布局一半的高度 加上 0到一半高度范围内的随机坐标+心形的高度的一半)。这样取到的横坐标是在布局宽度之内的随机坐标,纵坐标为整个路径高度中部以上的随机坐标。
p2坐标:与p1类似,横坐标是在布局宽度之内的随机坐标,纵坐标为整个路径高度中部以下的随机坐标。
p3坐标:控件底部中心点
知道4个坐标了,那么就可以开始计算路径

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网