热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

Android开发仿映客送礼物效果

这篇文章主要介绍了Android开发仿映客送礼物效果的实现代码,本文介绍的非常详细,具有参考借鉴价值,感兴趣的朋友一起学习吧

这里写链接内容仿映客送小礼物的特效,顺便复习一下属性动画,话不多说先看效果图。

效果图

需求分析

可以看到整个动画有几部分组成,那我们就把每个部分拆分出来各个击破。

1.要显示那些内容以及内容间的位置关系?

可以看到我们要显示用户头像,昵称,礼物图标以及数量。所以这里我选择用FrameLayout来作为根布局。

2.需要哪些动画以及动画的执行顺序?

a.首先是整体从左到右飞入并有一个回弹(translationX + OvershootInterpolator)

b.然后是礼物从左到右飞入而且是一个带减速效果的(translationX + DecelerateInterpolator)

c.礼物数量依次累加同时伴随着缩放(scale+repeat)

d.后面的粒子效果(帧动画)

e.整体向上平移并且逐渐消失(translationY + alpha)

3.送礼的区域有两块(A,B),如何分配?

因为用户送礼的数量不固定,所以动画持续的时间也不一定。但是我们希望这两块区域能得到充分的使用,即我们需要一个队列存放这些礼物实例,A和B谁空闲,就分配给谁处理。

4.以上所有内容是否使用原生的空间就能实现?

正如上面的分析,我们有时操作整体,有时操作局部。这时我们最好能自定义一个布局继承FrameLayout,其实也就是封装一层,这样我们就可以很好的控制整个布局。除此之外,还有我们注意到礼物数量是带描边的,貌似需要我们自定义实现了。

功能实现

需求分析完了,接下来我们说说功能的实现。

首先来打我们的整体布局。


这里比较简单不多说了,重点看下StrokeTextView,带描边的textview,其实就是重写了ondraw方法先绘制外层,在绘制内层。

@Override
protected void onDraw(Canvas canvas) {
if (m_bDrawSideLine) {
// 描外层
setTextColorUseReflection(mOuterColor);
m_TextPaint.setStrokeWidth(5);
m_TextPaint.setStyle(Paint.Style.FILL_AND_STROKE);
super.onDraw(canvas);
// 描内层,恢复原先的画笔
setTextColorUseReflection(mInnerColor);
m_TextPaint.setStrokeWidth(0);
m_TextPaint.setStyle(Paint.Style.FILL_AND_STROKE);
}
super.onDraw(canvas);
}
/**
* 使用反射的方法进行字体颜色的设置
* @param color
*/
private void setTextColorUseReflection(int color) {
Field textColorField;
try {
textColorField = TextView.class.getDeclaredField("mCurTextColor");
textColorField.setAccessible(true);
textColorField.set(this, color);
textColorField.setAccessible(false);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
m_TextPaint.setColor(color);
}

定义礼物的实体类

public class GiftSendModel {
private int giftCount;
private String userAvatarRes;
private String nickname;
private String sig;
private int giftRes;
private String gift_id;
private int star;
public GiftSendModel(int giftCount) {
this.giftCount = giftCount;
}
public int getGiftCount() {
return giftCount;
}
public void setGiftCount(int giftCount) {
this.giftCount = giftCount;
}
......

封装整体布局

public class GiftFrameLayout extends FrameLayout {
private LayoutInflater mInflater;
RelativeLayout anim_rl;
ImageView anim_gift, anim_light, anim_header;
TextView anim_nickname, anim_sign;
StrokeTextView anim_num;
/**
* 礼物数量的起始值
*/
int starNum = 1;
int repeatCount = 0;
private boolean isShowing = false;
public GiftFrameLayout(Context context) {
this(context, null);
}
public GiftFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mInflater = LayoutInflater.from(context);
initView();
}
private void initView() {
View view = mInflater.inflate(R.layout.animation, this, false);
anim_rl = (RelativeLayout) view.findViewById(R.id.animation_person_rl);
anim_gift = (ImageView) view.findViewById(R.id.animation_gift);
anim_light = (ImageView) view.findViewById(R.id.animation_light);
anim_num = (StrokeTextView) view.findViewById(R.id.animation_num);
anim_header = (ImageView) view.findViewById(R.id.gift_userheader_iv);
anim_nickname = (TextView) view.findViewById(R.id.gift_usernickname_tv);
anim_sign = (TextView) view.findViewById(R.id.gift_usersign_tv);
this.addView(view);
}
public void hideView() {
anim_gift.setVisibility(INVISIBLE);
anim_light.setVisibility(INVISIBLE);
anim_num.setVisibility(INVISIBLE);
}
public void setModel(GiftSendModel model){
if (0!=model.getGiftCount()) {
this.repeatCount = model.getGiftCount();
}
if (!TextUtils.isEmpty(model.getNickname())) {
anim_nickname.setText(model.getNickname());
}
if (!TextUtils.isEmpty(model.getSig())) {
anim_sign.setText(model.getSig());
}
}
public boolean isShowing(){
return isShowing;
}
public AnimatorSet startAnimation( final int repeatCount) {
hideView();
//布局飞入
ObjectAnimator flyFromLtoR = GiftAnimationUtil.createFlyFromLtoR(anim_rl, -getWidth(), 0, 400,new OvershootInterpolator());
flyFromLtoR.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
GiftFrameLayout.this.setVisibility(View.VISIBLE);
GiftFrameLayout.this.setAlpha(1f);
isShowing = true;
anim_num.setText("x " + 1);
Log.i("TAG", "flyFromLtoR A start");
}
});
//礼物飞入
ObjectAnimator flyFromLtoR2 = GiftAnimationUtil.createFlyFromLtoR(anim_gift, -getWidth(), 0, 400,new DecelerateInterpolator());
flyFromLtoR2.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
anim_gift.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animation) {
GiftAnimationUtil.startAnimationDrawable(anim_light);
anim_num.setVisibility(View.VISIBLE);
}
});
//数量增加
ObjectAnimator scaleGiftNum = GiftAnimationUtil.scaleGiftNum(anim_num, repeatCount);
scaleGiftNum.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
anim_num.setText("x " + (++starNum));
}
});
//向上渐变消失
ObjectAnimator fadeAnimator = GiftAnimationUtil.createFadeAnimator(GiftFrameLayout.this, 0, -100, 300, 400);
fadeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
GiftFrameLayout.this.setVisibility(View.INVISIBLE);
}
});
// 复原
ObjectAnimator fadeAnimator2 = GiftAnimationUtil.createFadeAnimator(GiftFrameLayout.this, 100, 0, 20, 0);
AnimatorSet animatorSet = GiftAnimationUtil.startAnimation(flyFromLtoR, flyFromLtoR2, scaleGiftNum, fadeAnimator, fadeAnimator2);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
starNum = 1;
isShowing = false;
}
});
return animatorSet;

我们将所有的动画方法都写到了GiftAnimationUtil中便于管理

public class GiftAnimationUtil {
/**
* @param target
* @param star 动画起始坐标
* @param end 动画终止坐标
* @param duration 持续时间
* @return
* 创建一个从左到右的飞入动画
* 礼物飞入动画
*/
public static ObjectAnimator createFlyFromLtoR(final View target, float star, float end, int duration, TimeInterpolator interpolator) {
//1.个人信息先飞出来
ObjectAnimator anim1 = ObjectAnimator.ofFloat(target, "translationX",
star, end);
anim1.setInterpolator(interpolator);
anim1.setDuration(duration);
return anim1;
}
/**
* @param target
* @return
* 播放帧动画
*/
public static AnimationDrawable startAnimationDrawable(ImageView target){
AnimationDrawable animatiOnDrawable= (AnimationDrawable) target.getDrawable();
if(animationDrawable!=null) {
target.setVisibility(View.VISIBLE);
animationDrawable.start();
}
return animationDrawable;
}
/**
* @param target
* @param drawable
* 设置帧动画
*/
public static void setAnimationDrawable(ImageView target, AnimationDrawable drawable){
target.setBackground(drawable);
}
/**
* @param target
* @param num
* @return
* 送礼数字变化
*/
public static ObjectAnimator scaleGiftNum(final TextView target , int num){
PropertyValuesHolder anim4 = PropertyValuesHolder.ofFloat("scaleX",
1.7f, 0.8f,1f);
PropertyValuesHolder anim5 = PropertyValuesHolder.ofFloat("scaleY",
1.7f, 0.8f,1f);
PropertyValuesHolder anim6 = PropertyValuesHolder.ofFloat("alpha",
1.0f, 0f,1f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(target, anim4, anim5, anim6).setDuration(480);
animator.setRepeatCount(num);
return animator;
}
/**
* @param target
* @param star
* @param end
* @param duration
* @param startDelay
* @return
* 向上飞 淡出
*/
public static ObjectAnimator createFadeAnimator(final View target, float star, float end, int duration, int startDelay){
PropertyValuesHolder translatiOnY= PropertyValuesHolder.ofFloat("translationY", star,end);
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f,0f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(target, translationY, alpha);
animator.setStartDelay(startDelay);
animator.setDuration(duration);
return animator;
}
/**
* @param animators
* @return
* 按顺序播放动画
*/
public static AnimatorSet startAnimation(ObjectAnimator animator1, ObjectAnimator animator2, ObjectAnimator animator3, ObjectAnimator animator4, ObjectAnimator animator5){
AnimatorSet animSet = new AnimatorSet();
// animSet.playSequentially(animators);
animSet.play(animator1).before(animator2);
animSet.play(animator3).after(animator2);
animSet.play(animator4).after(animator3);
animSet.play(animator5).after(animator4);
animSet.start();
return animSet;
}
}

所有的动画效果均是用属性动画完成,其中不仅有单个的动画,还有组合动画。属性动画用起来方面而且功能十分强大!

最后看下MainActivity中的实现

public class MainActivity extends AppCompatActivity {
private GiftFrameLayout giftFrameLayout1;
private GiftFrameLayout giftFrameLayout2;
List giftSendModelList = new ArrayList();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
giftFrameLayout1 = (GiftFrameLayout) findViewById(R.id.gift_layout1);
giftFrameLayout2 = (GiftFrameLayout) findViewById(R.id.gift_layout2);
findViewById(R.id.action).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
starGiftAnimation(createGiftSendModel());
}
});
}
private GiftSendModel createGiftSendModel(){
return new GiftSendModel((int)(Math.random()*10));
}
private void starGiftAnimation(GiftSendModel model){
if (!giftFrameLayout1.isShowing()) {
sendGiftAnimation(giftFrameLayout1,model);
}else if(!giftFrameLayout2.isShowing()){
sendGiftAnimation(giftFrameLayout2,model);
}else{
giftSendModelList.add(model);
}
}
private void sendGiftAnimation(final GiftFrameLayout view, GiftSendModel model){
view.setModel(model);
AnimatorSet animatorSet = view.startAnimation(model.getGiftCount());
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
synchronized (giftSendModelList) {
if (giftSendModelList.size() > 0) {
view.startAnimation(giftSendModelList.get(giftSendModelList.size() - 1).getGiftCount());
giftSendModelList.remove(giftSendModelList.size() - 1);
}
}
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}

其中关于缓存区的策略大家可以根据实际需求进行定制。

以上所述是小编给大家介绍的Android开发仿映客送礼物效果,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!


推荐阅读
  • 在使用SSH框架进行项目开发时,经常会遇到一些常见的问题。例如,在Spring配置文件中配置AOP事务声明后,进行单元测试时可能会出现“No Hibernate Session bound to thread”的错误。本文将详细探讨这一问题的原因,并提供有效的解决方案,帮助开发者顺利解决此类问题。 ... [详细]
  • 本文探讨了资源访问的学习路径与方法,旨在帮助学习者更高效地获取和利用各类资源。通过分析不同资源的特点和应用场景,提出了多种实用的学习策略和技术手段,为学习者提供了系统的指导和建议。 ... [详细]
  • 本文探讨了Android系统中支持的图像格式及其在不同版本中的兼容性问题,重点涵盖了存储、HTTP传输、相机功能以及SparseArray的应用。文章详细分析了从Android 10 (API 29) 到Android 11 的存储规范变化,并讨论了这些变化对图像处理的影响。此外,还介绍了如何通过系统升级和代码优化来解决版本兼容性问题,以确保应用程序在不同Android版本中稳定运行。 ... [详细]
  • 利用树莓派畅享落网电台音乐体验
    最近重新拾起了闲置已久的树莓派,这台小巧的开发板已经沉寂了半年多。上个月闲暇时间较多,我决定将其重新启用。恰逢落网电台进行了改版,回忆起之前在树莓派论坛上看到有人用它来播放豆瓣音乐,便萌生了同样的想法。通过一番调试,终于实现了在树莓派上流畅播放落网电台音乐的功能,带来了全新的音乐享受体验。 ... [详细]
  • 在处理遗留数据库的映射时,反向工程是一个重要的初始步骤。由于实体模式已经在数据库系统中存在,Hibernate 提供了自动化工具来简化这一过程,帮助开发人员快速生成持久化类和映射文件。通过反向工程,可以显著提高开发效率并减少手动配置的错误。此外,该工具还支持对现有数据库结构进行分析,自动生成符合 Hibernate 规范的配置文件,从而加速项目的启动和开发周期。 ... [详细]
  • 在尝试为 Unity 编译一个简单的 Java 库时,运行 `ant jar` 命令后遇到了 Java I/O 异常。具体错误信息为“无法启动程序 ${aAPT},错误代码 2”,这通常表示指定的文件或目录不存在。此问题可能是由于环境配置不正确或路径设置有误导致的。建议检查相关路径和环境变量,确保所有依赖项都已正确安装和配置。 ... [详细]
  • 基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析
    基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析 ... [详细]
  • 当遇到“Warning: Default Activity not found”这一警告时,通常是由于上次强制退出导致 Android Studio 的配置文件损坏所致。解决方法如下:1. 关闭 Android Studio;2. 导航至 Android Studio 的安装目录,找到并删除 `.idea` 和 `build` 文件夹;3. 重新启动 Android Studio 并同步项目。这些步骤有助于恢复项目的正常配置,确保应用能够顺利运行。 ... [详细]
  • TCP三次握手过程详解与图示解析
    本文详细解析了TCP三次握手的过程,并通过图示清晰展示了各个状态的变化。同时,文章还介绍了四次挥手的图解,解释了在TIME_WAIT状态中,客户端最后一次发送的ACK包的作用和重要性。 ... [详细]
  • 动态壁纸 LiveWallPaper:让您的桌面栩栩如生(第二篇)
    在本文中,我们将继续探讨如何开发动态壁纸 LiveWallPaper,使您的桌面更加生动有趣。作为 2010 年 Google 暑期大学生博客分享大赛 Android 篇的一部分,我们将详细介绍 Ed Burnette 的《Hello, Android》第三版中的相关内容,并分享一些实用的开发技巧和经验。通过本篇文章,您将了解到如何利用 Android SDK 创建引人入胜的动态壁纸,提升用户体验。 ... [详细]
  • 在Ubuntu系统中配置Python环境变量是确保项目顺利运行的关键步骤。本文介绍了如何将Windows上的Django项目迁移到Ubuntu,并解决因虚拟环境导致的模块缺失问题。通过详细的操作指南,帮助读者正确配置虚拟环境,确保所有第三方库都能被正确识别和使用。此外,还提供了一些实用的技巧,如如何检查环境变量配置是否正确,以及如何在多个虚拟环境之间切换。 ... [详细]
  • 理解和应用HTTP请求中的转发与重定向机制
    在HTTP请求处理过程中,客户端发送请求(通常简称为req),服务器进行相应处理后返回响应(通常简称为res)。理解和应用客户端的转发与重定向机制是前端开发的重要内容。这两种机制在Web开发中具有关键作用,能够有效管理和优化用户请求的处理流程。转发机制允许服务器内部将请求传递给另一个资源,而重定向则指示客户端向新的URL发起新的请求,从而实现页面跳转或资源更新。掌握这些技术有助于提升应用的性能和用户体验。 ... [详细]
  • 本文探讨了在形状类族中应用纯虚函数的设计模式及其解析方法。通过定义一个基类 `Shape`,其中包含一个纯虚函数 `area()`,实现了多态性和代码的灵活性。该设计使得派生类能够根据具体的形状计算面积,从而提高了代码的可扩展性和复用性。示例代码展示了如何利用纯虚函数实现这一机制。 ... [详细]
  • 在GitHub上克隆vue-element-admin项目时遇到依赖安装错误
    在 GitHub 上克隆 vue-element-admin 项目后,使用 `npm install` 安装依赖时遇到了未知的 Git 错误。具体错误信息为 `npm ERR! code 128`,提示命令执行失败。这可能是由于网络问题、Git 配置不正确或某些依赖包的仓库地址无效导致的。建议检查网络连接、更新 Git 版本并确保所有依赖项的 URL 正确无误。 ... [详细]
  • CentOS 7环境下Jenkins的安装与前后端应用部署详解
    CentOS 7环境下Jenkins的安装与前后端应用部署详解 ... [详细]
author-avatar
安琪儿
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有