这里写链接内容仿映客送小礼物的特效,顺便复习一下属性动画,话不多说先看效果图。
需求分析
可以看到整个动画有几部分组成,那我们就把每个部分拆分出来各个击破。
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"><RelativeLayoutandroid: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"><ImageViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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" /><ImageViewandroid: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><ImageViewandroid: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.StrokeTextViewandroid: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方法先绘制外层,在绘制内层。
@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animation) {anim_gift.setVisibility(View.VISIBLE);}@Overridepublic void onAnimationEnd(Animator animation) {GiftAnimationUtil.startAnimationDrawable(anim_light);anim_num.setVisibility(View.VISIBLE);}});//数量增加ObjectAnimator scaleGiftNum = GiftAnimationUtil.scaleGiftNum(anim_num, repeatCount);scaleGiftNum.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationRepeat(Animator animation) {anim_num.setText("x " + (++starNum));}});//向上渐变消失ObjectAnimator fadeAnimator = GiftAnimationUtil.createFadeAnimator(GiftFrameLayout.this, 0, -100, 300, 400);fadeAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic 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() {@Overridepublic 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>();@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic 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);}}}});}@Overridepublic 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;}@Overridepublic 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 SimplifiableIfStatementif (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}}
其中关于缓存区的策略大家可以根据实际需求进行定制。
以上所述是小编给大家介绍的Android开发仿映客送礼物效果,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对武林网网站的支持!
新闻热点
疑难解答