高仿映客直播点亮功能,VectorDrawable+PropertyAnimation实战篇

转载请标明出处:
http://blog.csdn.net/iamzgx/article/details/51811430
本文出自:【iGoach的博客】

概括

最近项目中在做直播功能,其中有一个功能就是点亮功能,随心而动,类似映客直播的点亮功能,先来看看映客的右下角点亮直播功能效果。

这里写图片描述

看起来效果蛮炫的,怎么实现呢?肯定是动画实现啦,android动画里面有ViewAnimation、DrawableAnimation,PropertyAnimation。ViewAnimation的缩放、平移、旋转、透明度能实现吗?DrawableAnimation的逐帧播放能实现吗?就运动轨迹实现起来就麻烦了,而且还不能真正改变view的位置,万一运动的view有点击事件呢,那不就是坑了。所以还是用android里面更加强大的PropertyAnimation属性动画来实现。PropertyAnimation分两种ObjectAnimation和ValueAnimation,那么ObjectAnimtion能实现吗?两个都类似,只是根据当前动画的计算值,来改变动画的属性值,貌似ValueAnimation灵活性更高。既然能实现,那现在就来动手吧!

准备资源

仔细看下心形,实心部分颜色很多种,问ui拿九妹图,万一有很多颜色呢,那不是很多图。你又会说这个可以忽略,万一心形颜色要服务端给我们呢,那就叫服务端给图,确定他们给的图能适配所有手机。那怎么办?要知道android现在是支持矢量图的,记得前面写过一篇android矢量图之VectorDrawable ,自由又方便的填充色彩。写的不怎好,很多细节还是没有写到,这里就拿VectorDrawable来实现心形,实战下。

首先我们配置下使用的环境,添加下面配置

android {
  defaultConfig {
  vectorDrawables.useSupportLibrary = true
}
}
dependencies {
    compile 'com.android.support:appcompat-v7:24.0.0'
}

其中添加

vectorDrawables.useSupportLibrary = true

主要是要生成

这里写图片描述

这个jar兼容包

另外使用appcompat-v7:24以上才真正的实现了android5.0以下VectorDrawable的兼容,前面的版本各种坑,可以参考这篇博客

然后在项目drawable目录下创建love_drawable.xml,直接拿前面那篇的代码,再改下大小,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="32dp"
    android:width="32dp"
    android:viewportWidth="32"
    android:viewportHeight="32">
    <path android:fillColor="#bbb455"
        android:pathData="M20.5,9.5
                        c-1.955,0,-3.83,1.268,-4.5,3
                        c-0.67,-1.732,-2.547,-3,-4.5,-3
                        C8.957,9.5,7,11.432,7,14
                        c0,3.53,3.793,6.257,9,11.5
                        c5.207,-5.242,9,-7.97,9,-11.5
                        C25,11.432,23.043,9.5,20.5,9.5z" />
</vector>

效果如下

这里写图片描述

颜色可以用上面path的android:fillColor自己改,怎么用呢,来测试下。写个布局文件activity_main.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"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        app:srcCompat="@drawable/love_drawable"/>
</RelativeLayout>

注意上面app:srcCompat代替以前android:src设置背景,如果用android:src设置VectorDrawable会报一大堆错误,类似下面的错误

FATAL EXCEPTION: main
                                                              java.lang.IllegalStateException: Could not execute method for android:onClick
                                                              android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
//...                                                          com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:936)
//...
android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)

布局完后,activity里面不需要做任何操作,拿个棒棒糖(android5.0)以上的手机运行下,嗯,没问题,这样就能显示出来了。再拿个android4.2或者android4.4手机测试下,运行后,一样没问题。

如果上面布局里面的ImageView改为TextView,然后设置TextView的drawableTop。那么我们就要注意下面两点。

  • 布局代码设置drawableTop,和ImageView的android:src一样,不能直接引用VectorDrawable。我们要在VectorDrawable依附
StateListDrawable,InsetDrawable,LayerDrawable,LevelListDrawable,RotateDrawable

里面的其中一种。比如:

love_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/love_drawable"/>
</selector>

然后用drawableTop引用love_selector。

  • 仅仅依赖于上一点还不行,google对于不是ImageView的控件没有开启兼容功能,所以我们要在activity里面添加代码开启
static {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
    }

当我们设置完

setCompatVectorFromResourcesEnabled(true)

之后,按理说ImageView的setImageResource和TextView的setCompoundDrawablesWithIntrinsicBounds应该一样要依附

StateListDrawable,InsetDrawable,LayerDrawable,LevelListDrawable,RotateDrawable

里面的其中一种。测试发现,我们可以直接引用,比如

setImageDrawable(getResources().getDrawable(R.drawable.love_drawable));

//测试动态创建TextView直接设置VectorDrawable
TextView textView = new TextView(this);
textView.setText("我是代码创建的TextView");
        textView.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.love_drawable),null,null,null);
mRootView.addView(textView);

都没有问题的。

资源准备的差不多了,还有一个问题,就是上面的心形是不同颜色的,不可能让我们有多少种颜色就创建多少xml吧,而且万一颜色值来源于服务端那就完蛋了,所以我们还是要获取love_drawable,然后动态设置它的颜色。

动态设置VectorDrawable的fillColor和strokeColor

查看源码我们会发现VectorDrawableCompat这个类,它有一个

public void setTint(int tint)

方法让我们去设置fillColor,或者我们也可以通过

DrawableCompat.setTint(a,colors[round]);

来设置fillColor,随便一种方式都行。

这样我们就可以定义几种颜色值,然后随机选取一种

 private int[] colors = new int[]{Color.YELLOW,Color.BLACK,Color.BLUE,Color.RED,Color.GREEN};

然后我们把颜色值设置进去,得到一个继承Drawable的VectorDrawableCompat 对象

 VectorDrawableCompat a = VectorDrawableCompat.create(getResources(), R.drawable.love_drawable,
                getResources().newTheme());
        Random random = new Random();
        int round =  random.nextInt(5);
        a.setTint(colors[round]);

如果你不需要映客直播心形的边界包裹颜色,那只要

setImageDrawable(a);

就可以了。

如果需要边界值,那VectorDrawableCompat 有没有设置strokeColor的边界颜色的呢,查找源码,发现设置strokeColor在一个VFullPath静态内部类。而且是unused的。

@SuppressWarnings("unused")
 void setStrokeColor(int strokeColor) {
     mStrokeColor = strokeColor;
 }

所以我们根本没法使用,那怎么办?放弃吗?不,这里说一个实现方法,上面我们不是说过VectorDrawable要用一层Drawable包裹吗,那我们也可以通过LayerDrawable来实现呀,里面一层实心的心形,外面一层空心的心形。这样我们就可以实现了。所以,我们再定义一个空心的drawable。代码和love_drawable差不多,

border_drawable.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="32dp"
    android:width="32dp"
    android:viewportWidth="32"
    android:viewportHeight="32">
    <path
        android:strokeWidth="2"
        android:strokeColor="#aaaaaa"
        android:pathData="M20.5,9.5
                        c-1.955,0,-3.83,1.268,-4.5,3
                        c-0.67,-1.732,-2.547,-3,-4.5,-3
                        C8.957,9.5,7,11.432,7,14
                        c0,3.53,3.793,6.257,9,11.5
                        c5.207,-5.242,9,-7.97,9,-11.5
                        C25,11.432,23.043,9.5,20.5,9.5z" />
</vector>

效果如下

这里写图片描述

不设置fillColor,设置strokeColor,这样内部就是透明的了。然后我们再两层包裹起来

//layerDrawable的第一层
   VectorDrawableCompat a = VectorDrawableCompat.create(getResources(), R.drawable.love_drawable,getResources().newTheme());
   Random random = new Random();
   int round =  random.nextInt(5);
   a.setTint(colors[round]);
   //第二种实现设置fillcolor的方式
   //DrawableCompat.setTint(a,colors[round]);
   //layerDrawable的第二层
   VectorDrawableCompat boardVdc = VectorDrawableCompat.create(getResources(), R.drawable.border_drawable,getResources().newTheme());
   Drawable[] drawable = new Drawable[2];
   drawable[0] = a;
   drawable[1] = boardVdc;
   LayerDrawable layerDrawable = new LayerDrawable(drawable);
   setImageDrawable(layerDrawable);

这样资源的使用我们可以了。

PropertyAnimation的使用

资源使用好了。那么我们就来通过PropertyAnimation的ValueAnimation来实现动画,在这个动画里面我们要知道每时每刻的运动位置,然后来决定它的运动轨迹,于是我们就要使用ValueAnimation的TypeEvaluator。那怎么去获取它每时每刻的运动位置呢。仔细看,我们会发现,这个动画使用的运动轨迹是使用二次贝塞尔曲线实现的。
什么叫二次贝塞尔曲线?
先来看一张图
这里写图片描述

定义AD/AB = BE/BC = DF/DE ,这样F就是在贝塞尔曲线上面的其中的一个点,这样主成的所有点就是一条贝塞尔曲线。详情原理请看博客 Iwfu-贝塞尔曲线。自定义轨迹可以查看工具。起点A和终点C和B点我们可以自己控制,那么求F点我们只是要用到一个公式

(1 - t)^2 P0 + 2 t (1 - t) P1 + t^2 P2;

知道这个公式了,我们就可以自定义TypeEvaluator,点的坐标用Point来记录,代码如下:

public class LoveEvaluator implements TypeEvaluator<Point> {
    private Point dirPoint;
    public LoveEvaluator(Point dirPoint){
        this.dirPoint = dirPoint ;
    }
    @Override
    public Point evaluate(float t, Point startValue, Point endValue) {
        //(1 - t)^2 P0 + 2 t (1 - t) P1 + t^2 P2;
        int x = (int)(Math.pow((1-t),2)*startValue.x+2*t*(1-t)*dirPoint.x+Math.pow(t,2)*endValue.x);
        int y = (int)(Math.pow((1-t),2)*startValue.y+2*t*(1-t)*dirPoint.y+Math.pow(t,2)*endValue.y);
        return new Point(x,y);
    }
}

接下来我们就可以使用ValueAnimation.ofObject来实现动画效果了,下面我们结合上面的VectorDrawable来自定义一个ImageView,代码如下:

//实现了ValueAnimator.AnimatorUpdateListener接口,主要是用来设置View的移动位置以及透明度
public class LoveAnimView extends ImageView implements ValueAnimator.AnimatorUpdateListener{
    //起点坐标
    private Point mStartPoint;
    //终点坐标
    private Point mEndPoint;
    //定义的一组颜色
    private int[] colors = new int[]{Color.YELLOW,Color.BLACK,Color.BLUE,Color.RED,Color.GREEN};
    public LoveAnimView(Context context) {
        this(context,null);
    }
    public LoveAnimView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public LoveAnimView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //引用资源,获取心形资源
        VectorDrawableCompat a = VectorDrawableCompat.create(getResources(), R.drawable.love_drawable,
                getResources().newTheme());
        Random random = new Random();
        int round =  random.nextInt(5);
        a.setTint(colors[round]);
        //DrawableCompat.setTint(a,colors[round]);
        VectorDrawableCompat boardVdc = VectorDrawableCompat.create(getResources(), R.drawable.border_drawable,
                getResources().newTheme());
        Drawable[] drawable = new Drawable[2];
        drawable[0] = a;
        drawable[1] = boardVdc;
        LayerDrawable layerDrawable = new LayerDrawable(drawable);
        //把LayerDrawable做为当前view的背景
        setImageDrawable(layerDrawable);
    }
    //设置起点的位置
    public void setStartPosition(Point startPosition) {
        this.mStartPoint = startPosition;
    }
    //设置终点的位置
    public void setEndPosition(Point endPosition) {
        this.mEndPoint = endPosition;
    }
    //设置动画
    public void startLoveAnimation(){
        if(mStartPoint==null||mEndPoint==null)
            throw new IllegalArgumentException("mStartPoint is not null or mEndPoint is not null");
    //中间的指向点的坐标,这里我们x轴坐标为在0-330之内生成随机数,可自己结合上面工具调节生成想要的效果
        int dirPointX = (int)(Math.random()*330);
   //中间的指向点的坐标,这里我们y轴坐标取起点和终点的中间值,可自己调节生成想要的效果
        int dirPointY = (mStartPoint.y+mEndPoint.y)/2;
        Point dirPoint = new Point(dirPointX,dirPointY);
        //创建自定义的TypeEvaluator
        LoveEvaluator loveEvaluator = new LoveEvaluator(dirPoint);
        //然后获取ValueAnimator对象
        ValueAnimator animator = ValueAnimator.ofObject(loveEvaluator,mStartPoint,mEndPoint);
        //实现onAnimationUpdate方法监听每时每刻的坐标位置,然后设置view的坐标
        animator.addUpdateListener(this);
        //设置动画时间为2s
        animator.setDuration(2000);
        //动画结束要移除view,同时设置alpha为透明
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                ViewGroup viewGroup = (ViewGroup) getParent();
                setAlpha(0f);
                viewGroup.removeView(LoveAnimView.this);
            }
        });
        //常素执行动画
        animator.setInterpolator(new LinearInterpolator());
        //启动动画
        animator.start();
    }
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {
        Point point = (Point) valueAnimator.getAnimatedValue();
        //设置当前view的坐标
        setX(point.x);
        setY(point.y);
        float value = point.y*1.0f/mStartPoint.y;
        //设置透明度慢慢变透明
        setAlpha(value);
        invalidate();
    }
}

再来看下MainActivity

public class MainActivity extends AppCompatActivity {
    static {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
    }
    private RelativeLayout mRootView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRootView = (RelativeLayout) findViewById(R.id.id_root_view);

        //测试动态创建TextView直接设置VectorDrawable
        TextView textView = new TextView(this);
        textView.setText("我是代码创建的TextView");
        textView.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.love_drawable),null,null,null);
        mRootView.addView(textView);
    }
    public void startAnim(View view){
    //点击按钮生成一个心形状态并执行动画
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
            LoveAnimView loveAnimView = new LoveAnimView(this);
            loveAnimView.setLayoutParams(params);
            //坐标位置可自己手动设置结合上面工具调节,这里以映客效果为参考点
            loveAnimView.setStartPosition(new Point(530,712));
            loveAnimView.setEndPosition(new Point(530-(int)(Math.random()*200),712-((int)(Math.random()*500)+200)));
            //开始动画
            loveAnimView.startLoveAnimation();
            //把view加入到根布局里面,生成动画
            mRootView.addView(loveAnimView);
    }
}

最后的布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/id_root_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center"
        android:layout_centerInParent="true">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="我是布局里面的TextView"
            android:drawableLeft="@drawable/love_selector"/>
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/love_drawable"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="随心而动"
            android:onClick="startAnim"/>
    </LinearLayout>
</RelativeLayout>

最后来看下动画效果

这里写图片描述

效果就是点击一次按钮就会生成一个心,映客的就是点击屏幕生成心,一样的。使用贝塞尔曲线可以实现我们很多动画效果。这里仅仅只是一种,以后像这种的动画,我们也可以同样的这样来实现。

下载代码

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值