Android -- 自定义ViewGroup+贝塞尔+属性动画实现仿QQ点赞效果
1,昨天我们写了篇简单的贝塞尔曲线的应用,今天和大家一起写一个QQ名片上常用的给别人点赞的效果,实现效果图如下:
红心的图片比较丑,见谅见谅(哈哈哈哈哈哈)。。。。
2,实现的思路和原理
从上面的效果图我们可以看到,实现基本上可以分为两部分:
①点击红心的时候底部出现ImageView的颜色是随机的
②等生成ImageView之后,执行动画往上升,轨迹是一条曲线,且每一个Imageview的轨迹都是不相同的(这里主要用到随机贝塞尔曲线的知识)
ok,既然知道怎么做了,开撸开撸.......
- 创建类HeartStar类,继承自ViewGroup,绘制我们底部的ImageView
首先我们这里为什么选择ViewGroup而不选择View呢?因为考虑到我们的每次点击的都是生成新的ImageView,所以这里选择ViewGroup。
重写构造方法,在构造方法中初始化ImageView
public class LikeStar extends ViewGroup {
private List<Drawable> mStarDrawable;
private int mWidth; //整个控件的宽度
private int mHeight; //整个控件的高度
private Random random = new Random(); public LikeStar(Context context) {
this(context, null);
} public LikeStar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public LikeStar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
} private void init(final Context context) {
mStarDrawable = new ArrayList<>();
//初始化图片资源
mStarDrawable.add(getResources().getDrawable(R.mipmap.heart_red));
ImageView image_heard = new ImageView(context);
image_heard.setImageDrawable(mStarDrawable.get(0));
image_heard.setLayoutParams(newLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
addView(image_heard);
} }
重写OnSizeChange()方法,获取整个控件的宽高
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
}
重写onMeasure方法,测量子控件高度并保存
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec); //获取view的宽高测量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec); //保存测量高度
setMeasuredDimension(widthSize, heightSize);
}
重写onLayout方法,摆放圆心控件
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i("wangjitao", "l:" + l + ",t:" + t + ",r:" + r + ",b:" + b);
View child = getChildAt(0);
int childW = child.getMeasuredWidth();
int childH = child.getMeasuredHeight();
child.layout((mWidth - childW) / 2, (mHeight - childH), (mWidth - childW) / 2 + childW, mHeight); }
布局文件中引用
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.qianmo.beziertest.MainActivity"> <com.qianmo.beziertest.view.LikeStar
android:id="@+id/likestar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/> <Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="翻滚吧,贝塞尔"
android:visibility="gone"
/>
</RelativeLayout>
ok,看一下运行的效果
没问题,我们绘制的红心就在我们父控件的底部,这里可能有同学会有疑问,为什么我布局文件中设置的宽高是wrap_content,而我们自定义的ViewGroup却充满了屏幕,这个我以前写过原因,不理解的同学可以在这一篇去理解一下。
- 属性动画和插补器了解
由于这里要用到属性动画和插补器,后面打算自己写一下这个专题,这里就简单的举一个属性动画加自定义的TypeEvaluator一个简单的效果。
首先创建两个点,即我们的我们动画的其实点和结束点,然后自定义TypeEvaluator,重写evaluate方法,实时的返回我们ImageView移动的点,代码如下
............省略代码
//申明属性
private PointF mStartPoint, mEndPoint,
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh); mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight(); // 初始化各个点 //借用子view控件中的宽高
View child = getChildAt(0);
int childW = child.getMeasuredWidth();
int childH = child.getMeasuredHeight(); mStartPoint.x = (mWidth - childW) / 2;
mStartPoint.y = mHeight - childH;
mEndPoint.x = (mWidth - childW) / 2;
mEndPoint.y = 0 - childH;
} //自定义TypeEvaluator
public class BezierTypeEvaluator implements TypeEvaluator<PointF> { @Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
PointF pointCur = new PointF();
pointCur.x = mStartPoint.x + fraction * (endValue.x - mStartPoint.x);
pointCur.y = mStartPoint.y + fraction * (endValue.y - mStartPoint.y);
return pointCur;
}
} //向外部提供方法,用于点击事件触发动画发生
/**
* 开始动画
*/
public void startRunning() {
BezierTypeEvaluator bezierTypeEvaluator = new BezierTypeEvaluator();
ValueAnimator valueAnimator = ValueAnimator.ofObject(bezierTypeEvaluator, mStartPoint, mEndPoint);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointF = (PointF) animation.getAnimatedValue();
getChildAt(0).setX(pointF.x);
getChildAt(0).setY(pointF.y);
}
}); valueAnimator.setDuration(3000);
valueAnimator.start();
}
在Activity监听点击事件,并开启动画
package com.qianmo.beziertest; import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button; import com.qianmo.beziertest.view.LikeStar;
import com.qianmo.beziertest.view.MyView1;
import com.qianmo.beziertest.view.MyViewCircle; public class MainActivity extends AppCompatActivity {
private MyView1 myview;
private Button btn;
private MyViewCircle myViewCircle;
private LikeStar mLikeStar; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); //点赞效果
mLikeStar = (LikeStar) findViewById(R.id.likestar);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mLikeStar.startRunning();
}
});
}
}
效果图如下:
- 实现贝塞尔曲线上升
我们上面实现的只是一个简单的直线上升,但是我们最终的效果图是每个圆心都是做无规则的曲线上升,这里我们就要使用贝塞尔三阶曲线公式了,这里不了解的童鞋可以去这一篇弄懂,不然后面的计算你是看不懂怎么来的。
使用我们的三阶曲线我们知道需要两个数据点和两个控制点,数据点我们继续使用上面的mStartPoint、mEndPoint,然后在创建两个随机的控制点(但大方向不随机),最后在我们的evaluate()方法中套用贝塞尔三阶公式,公式、代码如下:
//初始化属性
private PointF mControllPointOne, mControllPointTwo;
private Random random = new Random(); //设置值
mControllPointOne.x = random.nextInt(mWidth / 2);
mControllPointOne.y = random.nextInt(mHeight / 2) + mHeight / 2; mControllPointTwo.x = random.nextInt(mWidth / 2) + mWidth / 2;
mControllPointTwo.y = random.nextInt(mHeight / 2);
OK,让我们重写写一下自定义TypeEvaluator的evaluate方法,代码如下:
public class BezierTypeEvaluator implements TypeEvaluator<PointF> {
private PointF mControllPoint1, mControllPoint2; public BezierTypeEvaluator(PointF mControllPointOne, PointF mControllPointTwo) {
mControllPoint1 = mControllPointOne;
mControllPoint2 = mControllPointTwo;
} @Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
PointF pointCur = new PointF();
pointCur.x = mStartPoint.x * (1 - fraction) * (1 - fraction) * (1 - fraction) + 3
* mControllPoint1.x * fraction * (1 - fraction) * (1 - fraction) + 3
* mControllPoint2.x * (1 - fraction) * fraction * fraction + endValue.x * fraction * fraction * fraction;// 实时计算最新的点X坐标
pointCur.y = mStartPoint.y * (1 - fraction) * (1 - fraction) * (1 - fraction) + 3
* mControllPoint1.y * fraction * (1 - fraction) * (1 - fraction) + 3
* mControllPoint2.y * (1 - fraction) * fraction * fraction + endValue.y * fraction * fraction * fraction;// 实时计算最新的点Y坐标
return pointCur;
}
} //动画中调用
/**
* 开始动画
*/
public void startRunning() {
BezierTypeEvaluator bezierTypeEvaluator = new BezierTypeEvaluator(mControllPointOne, mControllPointTwo);
ValueAnimator valueAnimator = ValueAnimator.ofObject(bezierTypeEvaluator, mStartPoint, mEndPoint);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointF = (PointF) animation.getAnimatedValue();
getChildAt(0).setX(pointF.x);
getChildAt(0).setY(pointF.y);
}
}); valueAnimator.setDuration(3000);
valueAnimator.start();
}
再看看运行效果
- 添加多个红心飞升
我们上面实现了一个红心的上飞,这里我们想点击红心,从而有多个红心飞上去,并且每次飞上来的圆心的颜色不一样,我这里实现的是每点击一次就往viewGroup中已添加一个view,并且开始动画,,每次产生的都是随机的颜色图片,主要代码如下:
private void init(final Context context) {
mStarDrawable = new ArrayList<>();
mInterpolators = new ArrayList<>();
mStartPoint = new PointF();
mEndPoint = new PointF();
mControllPointOne = new PointF();
mControllPointTwo = new PointF(); //初始化图片资源
mStarDrawable.add(getResources().getDrawable(R.mipmap.heart_red));
mStarDrawable.add(getResources().getDrawable(R.mipmap.heart_blue));
mStarDrawable.add(getResources().getDrawable(R.mipmap.heart_yellow));
mStarDrawable.add(getResources().getDrawable(R.mipmap.heart_green)); //初始化插补器
mInterpolators.add(new LinearInterpolator());
mInterpolators.add(new AccelerateDecelerateInterpolator());
mInterpolators.add(new AccelerateInterpolator());
mInterpolators.add(new DecelerateInterpolator()); ImageView image_heard = new ImageView(context);
image_heard.setImageDrawable(mStarDrawable.get(0)); image_heard.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
image_heard.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//点击之后开始动画,添加红心到布局文件并开始动画
final ImageView image_random = new ImageView(context);
image_random.setImageDrawable(mStarDrawable.get(random.nextInt(4))); image_random.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
addView(image_random); invalidate(); //开始做动画效果 BezierTypeEvaluator bezierTypeEvaluator = new BezierTypeEvaluator(mControllPointOne, mControllPointTwo); ValueAnimator valueAnimator = ValueAnimator.ofObject(bezierTypeEvaluator, mStartPoint, endPointRandom);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointF = (PointF) animation.getAnimatedValue();
image_random.setX(pointF.x);
image_random.setY(pointF.y);
}
}); valueAnimator.setDuration(2000);
valueAnimator.start(); }
});
addView(image_heard);
}
OK,这样就实现了多个不一样的红心上升了,效果如下:
- 实现每个红心上升的曲线路径不同
从上面的效果我们可以看到我们的红心都是按照一种曲线路径上升的,现在想变成随机上升的。所以这里要改变数据点中结束点的X坐标,所以代码修改为如下
image_heard.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//点击之后开始动画,添加红心到布局文件并开始动画
final ImageView image_random = new ImageView(context);
image_random.setImageDrawable(mStarDrawable.get(random.nextInt(4))); image_random.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
addView(image_random); invalidate(); //开始做动画效果
//这里修改了代码
PointF endPointRandom = new PointF(random.nextInt(mWidth), mEndPoint.y);
// BezierTypeEvaluator bezierTypeEvaluator = new BezierTypeEvaluator(mControllPointOne, mControllPointTwo);
BezierTypeEvaluator bezierTypeEvaluator = new BezierTypeEvaluator(mControllPointOne,mControllPointTwo);
ValueAnimator valueAnimator = ValueAnimator.ofObject(bezierTypeEvaluator, mStartPoint, endPointRandom);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointF = (PointF) animation.getAnimatedValue();
image_random.setX(pointF.x);
image_random.setY(pointF.y);
}
}); valueAnimator.setDuration(2000);
valueAnimator.start(); }
});
效果图如下:
- 修改控制点,实现每次开始的动画的方向不一样
从上面的效果可以看到我们的动画总是从左边开始往上升,这是应为我们之前说过的我们的两个控制点虽然是随机的,但是大体坐标是左右一个,现在我们想实现红心随机从左右两边开始动画上升,所以我们这里要把两个控制点的坐标完全随机,,只需要修改如下代码:
BezierTypeEvaluator bezierTypeEvaluator = new BezierTypeEvaluator(new PointF( random.nextInt(mWidth ),random.nextInt(mHeight)), new PointF( random.nextInt(mWidth),random.nextInt(mHeight)));
效果如下:
ok,这样我们就完全实现QQ名片点赞效果了,这是项目Github的源码地址,需要的同学可以去下载一下,See You Next Time !!!
Android -- 自定义ViewGroup+贝塞尔+属性动画实现仿QQ点赞效果的更多相关文章
- Android动画效果之自定义ViewGroup添加布局动画
前言: 前面几篇文章介绍了补间动画.逐帧动画.属性动画,大部分都是针对View来实现的动画,那么该如何为了一个ViewGroup添加动画呢?今天结合自定义ViewGroup来学习一下布局动画.本文将通 ...
- android自定义viewgroup之我也玩瀑布流
先看效果图吧, 继上一篇<android自定义viewgroup实现等分格子布局>中实现的布局效果,这里稍微有些区别,每个格子的高度不规则,就是传说的瀑布流布局,一般实现这种效果,要么用第 ...
- Android自定义开机和关机动画
Android自定义开机和关机动画 Android在开机的过程中,会经历三张图片,关于静态图的修改在我的这篇文章中有介绍到: Android开机图片替换 现在要介绍的是怎么用动画替换静态图片.开/关机 ...
- Android图文具体解释属性动画
Android中的动画分为视图动画(View Animation).属性动画(Property Animation)以及Drawable动画.从Android 3.0(API Level 11)開始. ...
- Flutter仿掘金点赞效果
老孟导读:今天分享一下如何实现掘金点赞效果,这不仅仅是一篇技术文章,还是一篇解决问题思路的文章,遇到一个需求时,如何拆分需求,然后一步一步实现,这个过程比单纯的技术(此文)更有含金量. 先来看一下掘金 ...
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]
http://blog.csdn.net/jj120522/article/details/8095852 示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这 ...
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这里我简单说明一下用自定义ViewGroup来实现. 实现方法:我们自定义一个ViewGroup实现左右滑动, ...
- android自定义viewgroup初步之一----抽屉菜单
转载请注明出处 http://blog.csdn.net/wingichoy/article/details/47832151 几天前在慕课网上看到鸿洋老师的 自定义卫星菜单,感觉很有意思,于是看完视 ...
- Android 自定义ViewGroup手把手教你实现ArcMenu
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907 逛eoe发现这样的UI效果,感觉很不错,后来知道github上有这 ...
随机推荐
- Codeforces Round #396.D
D. Mahmoud and a Dictionary time limit per test 4 seconds memory limit per test 256 megabytes input ...
- HTTP状态码理解
100-199 用于指定客户端应相应的某些动作. 200-299 用于表示请求成功. 300-399 用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息. 400-499 用于指出客户端的错 ...
- BZOJ 1096: [ZJOI2007]仓库建设(动态规划+斜率优化)
第一次写斜率优化,发现其实也没啥难的,没打过就随便找了一份代码借(chao)鉴(xi)下,不要介意= = 题解实在是懒得写了,贴代码吧= = CODE: #include<cstdio># ...
- centos 安装gcc->联网 问题解决
本篇部分摘抄至TD_时缔 VMware虚拟机下安装centosmini版本,安装后第一件事就是yum update 但是有错:cannot find a valid baseurl for repo ...
- Gulp实现css、js、图片的压缩以及css、js文件的MD5命名
目前做代码压缩合并的工具有很多,诸如gulp,webpack,grunt等等,可以说这些项目构建工具的功能非常之强大:图片压缩.图片转base64.css和js的压缩以及合并,文件的md5重命名 -- ...
- java(jdk1.7) IO系列01之InputStream和OutputStream解析
1.InputStream和OutputStream简介 在java中InputStream和OutputStream分别代表字节输入流和字节输出流,表示以字节的方式来实现进程或者程序的通信,Inpu ...
- liunx常用命令
查看系统信息常用命令 uname -m /arch 显示机器的处理架构 uname -r 显示正在使用的内核版本 cat/proc/cpuinfo 显 ...
- 每天一个Linux命令(07)--mv命令
mv命令是move的缩写,可以用来移动文件或者将文件改名,这也是个常用命令,经常用来备份文件或者目录. 1.命令格式: mv [选项] 源文件或目录 目标文件或目录 2.命令功能: 视mv命令中第 ...
- mysql view视图的简单使用....
为什么使用视图 1.查询性能提高. 2.安全 3.有灵活性的功能需求后,需要改动表的结构而导致工作量比较大.那么可以使用虚拟表的形式达到少修改的效果 4.复杂的查询需求.可以进行问题分解,然后将创建多 ...
- 第27篇 重复造轮子---模拟IIS服务器
在写程序的时候,重复造轮子是程序员的一个大忌,很多人对重复造轮子持有反对的态度,但是我觉得这个造轮子的过程,是对于现有的知识的一个深入的探索的过程,虽然我们不可能把轮子造的那么的完善,对于现在有的东西 ...