Android开发仿映客送礼物效果

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

需求分析

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

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

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

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

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

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

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

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

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

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

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

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

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

功能实现

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

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

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/animation_person_rl"
android:layout_width="wrap_content"
android:layout_height="39dp"
android:layout_gravity="left"
android:layout_marginTop="22dp"
android:background="@drawable/bg_giftlayout">
<ImageView
android:id="@+id/gift_userheader_iv"
android:layout_width="39dp"
android:layout_height="39dp"
android:layout_margin="3dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:src="@mipmap/ember" />
<TextView
android:id="@+id/gift_usernickname_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="6dp"
android:layout_marginTop="4dp"
android:layout_toRightOf="@id/gift_userheader_iv"
android:text="库日天"
android:textColor="#ffffff"
android:textSize="12sp" />
<TextView
android:id="@+id/gift_usersign_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/gift_usernickname_tv"
android:layout_below="@id/gift_usernickname_tv"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:text="送一个超级无敌"
android:textColor="#ffea79"
android:textSize="11sp" />
<ImageView
android:id="@+id/animation_gift"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/gift_usersign_tv"
android:background="@mipmap/diamond2x" />
</RelativeLayout>
<ImageView
android:id="@+id/animation_light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="120dp"
android:src="@drawable/light_star_anim" />
<com.example.work.animationdemo.StrokeTextView
android:id="@+id/animation_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="185dp"
android:layout_marginTop="12dp"
android:text="x 1"
android:textColor="#0076ff"
android:textSize="24sp"
app:innnerColor="#ffffff"
app:outerColor="#0076ff" />
</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<GiftSendModel> giftSendModelList = new ArrayList<GiftSendModel>();
@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开发仿映客送礼物效果,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Android开发仿扫一扫实现拍摄框内的照片功能

    就是仿照现在扫一扫的形式,周围是半透明的遮挡,然后中间是全透明的,拍摄后只截取框内的内容 查了很多博客,实现起来真的太复杂了,本人比较怕麻烦所以在很多地方偷懒了 先上效果图: 第一步:设置照相机预览以及拍照 这是所有步骤的前提,没有预览,用户怎么知道自己拍的什么呢.预览用的是SurfaceView 这篇博文写得已经十分详细了,打开照相机,然后拍照,而且十分简洁!不想别的博客一下就几百行代码不知所云.这篇代码可以复制下去当相机模版使用. 这里遇到一个问题,就是预览的效果是左转90度的,拍出来也是左

  • Android设计模式系列之工厂方法模式

    工厂方法模式,往往是设计模式初学者入门的模式,的确,有人称之为最为典型最具启发效果的模式. android中用到了太多的工厂类,其中有用工厂方法模式的,当然也有很多工厂并不是使用工厂方法模式的,只是工具管理类. 今天以ThreadFactory举例说明一下简单工厂模式和工厂方法模式. 工厂方法模式,Factory Method,简单的方式,不简单的应用. 1.意图 定义一个用于创建对象的接口,让子类决定实例化哪个类.工厂方式模式使一个类的实例化延迟到其子类. 热门词汇:虚构造器 延迟 创建对象

  • Android Splash界面白屏、黑屏问题的解决方法

    前言 我相信很多Android开发同学都遇到过这样的需求: 1.实现一个Splash界面,界面上有应用相关的背景图片和一个开始按钮.  2.点击按钮之后进入主页,以后用户再打开应用就不显示这个Splash界面了. 也相信很多同学都遇到了这样的困惑:  •第二次进入应用,尽管你在Splash界面已经直接跳转到首页了,但是还是有个白屏或者黑屏或者带ActionBar的白屏闪现一下. 如果你也遇到这个问题,那就继续阅读这篇文章,我带大家去分析和解决这个问题. 解决方案 这里我们先给出解决方案,然后再具

  • Android Rsa数据加解密的介绍与使用示例

    Rsa加密 RSA是目前最有影响力的公钥加密算法,RSA也是第一个既能用于数据加密也能用于数字签名的算法.该算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但那时想要对其乘积进行因式分解却极其困 难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥.公钥是可发布的供任何人使用,私钥则为自己所有,供解密之用. RSA算法原理 1.随机选择两个大质数p和q,p不等于q,计算N=pq: 2.选择一个大于1小于N的自然数e,e必须与(p-1)(q-1)互素. 3.用公式计算出d:

  • Android app启动时黑屏或者白屏的原因及解决办法

    1.产生原因 其实显示黑屏或者白屏实属正常,这是因为还没加载到布局文件,就已经显示了window窗口背景,黑屏白屏就是window窗口背景. 示例: 2.解决办法 通过设置设置Style (1)设置背景图Theme 通过设置一张背景图. 当程序启动时,首先显示这张背景图,避免出现黑屏 <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name=&qu

  • 深入理解Android MD5数据加密

    MD5加密 MD5是由MD2.MD3.MD4演变过来的,虽然MD5加密算法现在有些人已经将其解开了,但是它的加密机制依然很强大,我想绝大对数还是不会解开的.MD5加密算法是单向加密,是不可逆的一种的加密方式,只能用你的密码才能解开,要不就是会解密算法,否则想都别想解开. MD5加密的特点 压缩性:任意长度的数据,算出的MD5值长度都是固定的. 容易计算:从原数据计算出MD5值很容易. 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别. 强抗碰撞:已知原数据和其MD

  • Android 判断SIM卡是中国移动\中国联通\中国电信(移动运营商)

    第一种方法: 获取手机的IMSI码,并判断是中国移动\中国联通\中国电信 TelephonyManager telManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); /** 获取SIM卡的IMSI码 * SIM卡唯一标识:IMSI 国际移动用户识别码(IMSI:International Mobile Subscriber Identification Number)是区别移动用户的标志, * 储存在

  • 设计简单的Android图片加载框架

    目前Android 发展至今优秀的图片加载框架太多,例如: Volley ,Picasso,Imageloader,Glide等等.但是作为程序猿,懂得其中的实现原理还是相当重要的,只有懂得才能更好地使用.于是乎,今天我就简单设计一个网络加载图片框架.主要就是熟悉图片的网络加载机制. 一般来说,一个优秀的 图片加载框架(ImageLoader) 应该具备如下功能: 图片压缩 内存缓存 磁盘缓存 图片的同步加载 图片的异步加载 网络拉取 那我们就从以上几个方面进行介绍: 1.图片压缩(有效的降低O

  • Android编程中activity启动时出现白屏、黑屏问题的解决方法

    本文实例讲述了Android编程中activity启动时出现白屏.黑屏问题的解决方法.分享给大家供大家参考,具体如下: 默认情况下 activity 启动的时候先把屏幕刷成白色,再绘制界面,绘制界面或多或少有点延迟,这段时间中你看到的就是白屏,显然影响用户体验,怎么消除呢? 在 Activity theme 设置style 即可 <style name="AppTheme" parent="android:Theme.Light.NoTitleBar">

  • Android仿知乎客户端关注和取消关注的按钮点击特效实现思路详解

    先说明一下,项目代码已上传至github,不想看长篇大论的也可以先去下代码,对照代码,哪里不懂点哪里. 代码在这https://github.com/zgzczzw/ZHFollowButton 前几天发现知乎关注的点击效果确实赞,查了一下实现方式,刚好看到这个问题,花了一天时间终于把这个效果实现了,现在来回答一下,很不幸,楼上各位的答案都不全对,且听我一一道来. 首先,我先详细观察了一些知乎的效果,其中有一个很神奇的地方,如图: 注意看第二张图,这个圆形在扩散的时候,圆形底下的字还在,而且新的

随机推荐