仿映客送礼特效

来源:互联网 发布:基恩士视觉软件 编辑:程序博客网 时间:2024/04/29 22:29

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

需求分析

可以看到整个动画有几部分组成,那我们就把每个部分拆分出来各个击破。
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);    }}

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

Demo地址

8 0
原创粉丝点击