編輯:關於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 android:layout_height="wrap_content" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"></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; ListgiftSendModelList = 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); } }
其中關於緩存區的策略大家可以根據實際需求進行定制。
感覺RxJava最近風生水起,不學習一下都不好意思了,灑家也是初學RxJava,也是感覺代碼好像更復雜更難懂了,看了一篇外文感同身受,簡單翻譯一下。本文簡單介紹使用Rx
前言之前,我們介紹了下拉刷新上拉加載RecyclerView的使用,那麼現在,我們就來說一下這個下拉刷新是怎麼實現的。在開發過程中,我想了兩種方案。一是使用LinearL
這裡寫鏈接內容仿映客送小禮物的特效,順便復習一下屬性動畫,話不多說先看效果圖。需求分析可以看到整個動畫有幾部分組成,那我們就把每個部分拆分出來各個擊破。1.要顯示那些內容
在實際開發中,Activity需要啟動但界面又不能顯示出來,這時就需要後台啟動,但又不是finish(),這時就要用到Activity中的moveTaskToBack函數