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

高仿映客直播点亮功能,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,直接拿前面那篇的代码,再改下大小,代码如下:

android:height="32dp"

android:width="32dp"

android:viewportWidth="32"

android:viewportHeight="32">

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" />

效果如下

颜色可以用上面path的android:fillColor自己改,怎么用呢,来测试下。写个布局文件activity_main.xml

android:layout_width="match_parent"

android:layout_height="match_parent"

xmlns:app="http://schemas.android.com/apk/res-auto">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerInParent="true"

app:srcCompat="@drawable/love_drawable"/>

注意上面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

然后用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

android:height="32dp"

android:width="32dp"

android:viewportWidth="32"

android:viewportHeight="32">

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" />

效果如下

不设置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 {

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);

}

}

最后的布局文件

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">

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical"

android:gravity="center"

android:layout_centerInParent="true">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="我是布局里面的TextView"

android:drawableLeft="@drawable/love_selector"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

app:srcCompat="@drawable/love_drawable"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginTop="10dp"

android:text="随心而动"

android:onClick="startAnim"/>

最后来看下动画效果

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

下载代码

相关推荐

Help:如何输入间隔号
bet体育365官网安全吗

Help:如何输入间隔号

📅 09-25 👀 5631
怎么移动文件夹 Mac上移动文件的三种方法
365bet体育滚球

怎么移动文件夹 Mac上移动文件的三种方法

📅 07-12 👀 4016
QQ怎么保存闪图
365wm完美体育

QQ怎么保存闪图

📅 06-27 👀 4099