LogoFlow 是極簡黑記帳本(SimpleBlackNote app)的起始 Activity,當初寫的時候並沒有 refactoring 以及 readable code 的概念。
剛好可以拿來練習重構。
以下為原始程式碼,可以看到其中充滿許多 bad smell。e.g. large class(過大的類別) ,duplicate code(重複的程式碼),long method(過長的函式) ,
commit(過多的註釋),大型的if-else條件式,不適當的封裝等等 。
package com.foxx.main; import java.util.Random; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.MotionEvent; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.ScaleAnimation; import android.widget.ImageView; import android.widget.LinearLayout; import com.google.ads.AdRequest; import com.google.ads.AdSize; import com.google.ads.AdView; import com.tlyerfoxx.sbcnote.R; @SuppressLint("NewApi") public class LogoFlow extends Activity implements Runnable{ private ImageView mLogo; private Handler mHandler; private float mLogoActIndex; private boolean mIsSleeping; private ValueAnimator mScaleX; private ValueAnimator mScaleY; private ValueAnimator mRotationY; private ValueAnimator mRotationX; private AnimatorSet mScalexy; public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.logoflow); // showAdmob(); mLogo = (ImageView)findViewById(R.id.imageView1); // useThreadFadeIn(); Random r = new Random(); useAnimfadein2(r.nextInt(4)); } public void run() { // TODO Auto-generated method stub while(!mIsSleeping){ try { Thread.sleep(30); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } Message m = new Message(); mHandler.sendMessage(m); } } private void useThreadFadeIn(){ mLogo.setAlpha(0.0f); mHandler = new Handler(){ public void handleMessage(Message msg) { Log.v("Trace Log", "revice maessage"); if(mLogo.getAlpha()<1.0f){ mLogoActIndex+=0.01f; mLogo.setAlpha(mLogoActIndex); Log.v("Trace Log", "test2"); } else if(mLogo.getAlpha()>=1.0f){ LogoFlow.this.finish(); // close activity Log.v("Trace Log", "test3"); mIsSleeping = true;// stop Thread Intent goAct = new Intent();// new a Intent goAct.setClass(LogoFlow.this, Table_Flow.class); // set another activity startActivity(goAct); // start another Activity System.exit(0);// stop program } Log.v("Trace Log", ""+mLogo.getAlpha()); super.handleMessage(msg); } }; Thread th = new Thread(this); th.start(); } void useAnimfadein(){ ValueAnimator fadein = ObjectAnimator.ofFloat(mLogo, "alpha", 0f, 1f); fadein.setDuration(2000); ValueAnimator standby = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 1f); standby.setDuration(1000); ValueAnimator fadeout = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 0f); fadeout.setDuration(2000); AnimatorSet bouncer = new AnimatorSet(); bouncer.playSequentially(fadein,standby,fadeout); bouncer.start(); bouncer.addListener(new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { // TODO Auto-generated method stub } @Override public void onAnimationRepeat(Animator animation) { // TODO Auto-generated method stub } @Override public void onAnimationEnd(Animator animation) { // TODO Auto-generated method stub LogoFlow.this.finish(); // close activity mIsSleeping = true;// stop Thread Intent goAct = new Intent();// new a Intent goAct.setClass(LogoFlow.this, CopyRightFlow.class); // set another activity startActivity(goAct); // start another Activity System.exit(0);// stop program } @Override public void onAnimationCancel(Animator animation) { // TODO Auto-generated method stub } }); } void useAnimfadein2(int type){ if(type==0){ mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 0.1f, 1f); mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 0.1f, 1f); mScaleX.setDuration(2500); mScaleY.setDuration(1250); mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f); mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f); mRotationY.setDuration(2500); mRotationX.setDuration(1250); mScalexy = new AnimatorSet(); mScalexy.playTogether(mScaleX,mScaleY,mRotationY,mRotationX); } else if(type==1){ mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 2.0f, 1f); mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 2.0f, 1f); mScaleX.setDuration(2500); mScaleY.setDuration(1250); mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f); mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f); mRotationY.setDuration(2500); mRotationX.setDuration(1250); mScalexy = new AnimatorSet(); mScalexy.playTogether(mScaleX,mScaleY,mRotationY,mRotationX); } else if(type==2){ mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f); mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f); mScaleX.setDuration(2500); mScaleY.setDuration(1250); mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 720f); mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 0f); mRotationY.setDuration(2500); mRotationX.setDuration(1250); mScalexy = new AnimatorSet(); mScalexy.playTogether(mScaleX,mScaleY,mRotationY,mRotationX); } else if(type==3){ mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f); mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f); mScaleX.setDuration(2500); mScaleY.setDuration(1250); mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 0f); mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 720f); mRotationY.setDuration(2500); mRotationX.setDuration(1250); mScalexy = new AnimatorSet(); mScalexy.playTogether(mScaleX,mScaleY,mRotationY,mRotationX); } ValueAnimator standby = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 1f); standby.setDuration(1000); ValueAnimator fadeout = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 0f); fadeout.setDuration(1000); AnimatorSet bouncer = new AnimatorSet(); bouncer.playSequentially(mScalexy,standby,fadeout); bouncer.start(); bouncer.addListener(new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { // TODO Auto-generated method stub } @Override public void onAnimationRepeat(Animator animation) { // TODO Auto-generated method stub } @Override public void onAnimationEnd(Animator animation) { // TODO Auto-generated method stub LogoFlow.this.finish(); // close activity mIsSleeping = true;// stop Thread Intent goAct = new Intent();// new a Intent goAct.setClass(LogoFlow.this, Table_Flow.class); // set another activity CopyRightFlow.class startActivity(goAct); // start another Activity overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);//�ϥιL��,��1�ӰѼƬO�U1�ӳ������i�J,��2�ӰѼƬO�������������} System.exit(0);// stop program } @Override public void onAnimationCancel(Animator animation) { // TODO Auto-generated method stub } }); } void showAdmob(){ AdView adView; // Create the adView adView = new AdView(this, AdSize.BANNER, "a150189a3abeb1a"); LinearLayout layout = (LinearLayout)findViewById(R.id.AdLayout); layout.addView(adView); adView.loadAd(new AdRequest()); } public boolean onTouchEvent(MotionEvent event){ // FP.p("in onTouchEvent"); LogoFlow.this.finish(); // close activity mIsSleeping = true;// stop Thread Intent goAct = new Intent();// new a Intent goAct.setClass(LogoFlow.this, Table_Flow.class); // set another activity CopyRightFlow.class startActivity(goAct); // start another Activity overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);//�ϥιL��,��1�ӰѼƬO�U1�ӳ������i�J,��2�ӰѼƬO�������������} System.exit(0);// stop program switch(event.getAction()){ case MotionEvent.ACTION_DOWN: // dx = (int)event.getX(); break; case MotionEvent.ACTION_UP: break; } return true; } }
以下將一步一步進行重構。
首先從適當的封裝開始,觀察類別屬性以及函式是否為useless,或是可以縮小存取權限以避免讓外界知道不必要的細節。
可以發現第77行的useThreadFadeIn,第119行的useAnimfadein,第282的showAdmob 這3個函式都沒有使用,沒有使用函式就是多餘的函式。
直接刪除吧。
刪完沒有使用的函式之後,logoActIndex 變數也沒有使用,另外刪掉多餘的 import 以及註解。
最後為類別屬性加上適當的重新命名之後。整個LogoFlow類別會從324行縮短到 175行。e.g.
package com.foxx.main; import java.util.Random; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.MotionEvent; import android.widget.ImageView; import com.tlyerfoxx.sbcnote.R; @SuppressLint("NewApi") public class LogoFlow extends Activity implements Runnable { private ImageView mLogo; private Handler mHandler; private boolean mIsSleeping; private ValueAnimator mScaleX; private ValueAnimator mScaleY; private ValueAnimator mRotationY; private ValueAnimator mRotationX; private AnimatorSet mScalexy; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.logoflow); mLogo = (ImageView) findViewById(R.id.imageView1); Random r = new Random(); useAnimfadein2(r.nextInt(4)); } public void run() { while (!mIsSleeping) { try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } Message m = new Message(); mHandler.sendMessage(m); } } void useAnimfadein2(int type) { if (type == 0) { mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 0.1f, 1f); mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 0.1f, 1f); mScaleX.setDuration(2500); mScaleY.setDuration(1250); mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f); mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f); mRotationY.setDuration(2500); mRotationX.setDuration(1250); mScalexy = new AnimatorSet(); mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX); } else if (type == 1) { mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 2.0f, 1f); mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 2.0f, 1f); mScaleX.setDuration(2500); mScaleY.setDuration(1250); mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f); mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f); mRotationY.setDuration(2500); mRotationX.setDuration(1250); mScalexy = new AnimatorSet(); mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX); } else if (type == 2) { mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f); mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f); mScaleX.setDuration(2500); mScaleY.setDuration(1250); mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 720f); mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 0f); mRotationY.setDuration(2500); mRotationX.setDuration(1250); mScalexy = new AnimatorSet(); mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX); } else if (type == 3) { mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f); mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f); mScaleX.setDuration(2500); mScaleY.setDuration(1250); mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 0f); mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 720f); mRotationY.setDuration(2500); mRotationX.setDuration(1250); mScalexy = new AnimatorSet(); mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX); } ValueAnimator standby = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 1f); standby.setDuration(1000); ValueAnimator fadeout = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 0f); fadeout.setDuration(1000); AnimatorSet bouncer = new AnimatorSet(); bouncer.playSequentially(mScalexy, standby, fadeout); bouncer.start(); bouncer.addListener(new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { LogoFlow.this.finish(); mIsSleeping = true;// stop Thread Intent goAct = new Intent(); goAct.setClass(LogoFlow.this, Table_Flow.class); startActivity(goAct); overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit); System.exit(0);// stop program } @Override public void onAnimationCancel(Animator animation) { } }); } public boolean onTouchEvent(MotionEvent event) { LogoFlow.this.finish(); mIsSleeping = true;// stop Thread Intent goAct = new Intent(); goAct.setClass(LogoFlow.this, Table_Flow.class); startActivity(goAct); overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit); System.exit(0);// stop program return true; } }
接著我們將目標轉向第61行的 useAnimfadein2 函式,因為它實在太長了,過長的函式通常不只做一件事,包含太多職責,讓函式的意圖不明。
先從名稱開始,useAnimfadein2大概可以猜出跟動畫淡入有關,而2是個數字命名法,是一個不好的命名習慣,因為你沒辦法從2得到訊息。
基本上也沒有人知道誰使用動畫淡入在誰身上,因此我們只能從函式內容看起。
從第64 到 125行應該是屬於動畫的參數設定,從參數上可以看出是mLogo使用動畫。
而第127行開始執行動畫,第129到159則是設定動畫的監聽器。
因此我們可以把 useAnimfadein2 更名為 runLogoAnimations 並拆分為3個函式 , setAnimationParameters , createAnimationListener。e.g.
private void runLogoAnimations(int animationType) { AnimatorSet animation = new AnimatorSet(); animation.playSequentially(setAnimationParameters(animationType)); animation.start(); animation.addListener(createAnimationListener()); } private List<Animator> setAnimationParameters(int type) { List<Animator> animators = new ArrayList<Animator>(); if (type == 0) { mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 0.1f, 1f); mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 0.1f, 1f); mScaleX.setDuration(2500); mScaleY.setDuration(1250); mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f); mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f); mRotationY.setDuration(2500); mRotationX.setDuration(1250); mScalexy = new AnimatorSet(); mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX); } else if (type == 1) { mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 2.0f, 1f); mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 2.0f, 1f); mScaleX.setDuration(2500); mScaleY.setDuration(1250); mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f); mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f); mRotationY.setDuration(2500); mRotationX.setDuration(1250); mScalexy = new AnimatorSet(); mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX); } else if (type == 2) { mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f); mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f); mScaleX.setDuration(2500); mScaleY.setDuration(1250); mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 720f); mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 0f); mRotationY.setDuration(2500); mRotationX.setDuration(1250); mScalexy = new AnimatorSet(); mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX); } else if (type == 3) { mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f); mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f); mScaleX.setDuration(2500); mScaleY.setDuration(1250); mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 0f); mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 720f); mRotationY.setDuration(2500); mRotationX.setDuration(1250); mScalexy = new AnimatorSet(); mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX); } ValueAnimator standby = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 1f); standby.setDuration(1000); ValueAnimator fadeout = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 0f); fadeout.setDuration(1000); animators.add(mScalexy); animators.add(standby.setDuration(1000)); animators.add(fadeout); return animators; } private AnimatorListener createAnimationListener() { return new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { LogoFlow.this.finish(); mIsSleeping = true;// stop Thread Intent goAct = new Intent(); goAct.setClass(LogoFlow.this, Table_Flow.class); startActivity(goAct); overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit); System.exit(0);// stop program } @Override public void onAnimationCancel(Animator animation) { } }; }
接著我們再從 setAnimationParameters 來看看,是否有可以改進的地方。
首先 setAnimationParameters 有個旗標參數,函式會根據旗標參數執行不同的行為。這也代表該方法做了不只一件事。
觀察 mSaleX , mScaleY,mRotationX , mRotationY , mScaleXY , 可以看到這四個類別屬性僅僅使用在setAnimationParameters函式內。
而不需要在類別的函式之間使用。因此可以將他們改為暫時變數,為了縮短生命週期。
private List<Animator> setAnimationParameters(int type) { ValueAnimator scaleX; ValueAnimator scaleY; ValueAnimator rotationY; ValueAnimator rotationX; AnimatorSet scaleXY = null; if (type == 0) { scaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 0.1f, 1f); scaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 0.1f, 1f); scaleX.setDuration(2500); scaleY.setDuration(1250); rotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f); rotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f); rotationY.setDuration(2500); rotationX.setDuration(1250); scaleXY = new AnimatorSet(); scaleXY.playTogether(scaleX, scaleY, rotationY, rotationX); } else if (type == 1) { scaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 2.0f, 1f); scaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 2.0f, 1f); scaleX.setDuration(2500); scaleY.setDuration(1250); rotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f); rotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f); rotationY.setDuration(2500); rotationX.setDuration(1250); scaleXY = new AnimatorSet(); scaleXY.playTogether(scaleX, scaleY, rotationY, rotationX); } else if (type == 2) { scaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f); scaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f); scaleX.setDuration(2500); scaleY.setDuration(1250); rotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 720f); rotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 0f); rotationY.setDuration(2500); rotationX.setDuration(1250); scaleXY = new AnimatorSet(); scaleXY.playTogether(scaleX, scaleY, rotationY, rotationX); } else if (type == 3) { scaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f); scaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f); scaleX.setDuration(2500); scaleY.setDuration(1250); rotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 0f); rotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 720f); rotationY.setDuration(2500); rotationX.setDuration(1250); scaleXY = new AnimatorSet(); scaleXY.playTogether(scaleX, scaleY, rotationY, rotationX); } ValueAnimator standby = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 1f); standby.setDuration(1000); ValueAnimator fadeout = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 0f); fadeout.setDuration(1000); List<Animator> animators = new ArrayList<Animator>(); animators.add(scaleXY); animators.add(standby.setDuration(1000)); animators.add(fadeout); return animators; }
觀察其中的條件式(第9~61),可以發現每個條件式的執行區塊,都做了2件事,首先是設定XY的縮放動畫,再來設定旋轉動畫。
但不管是設定縮放還是設定旋轉,都是使用類似的參數。為了減少重複的程式碼,我們可以另外建立通用的函式 setAnimation。
並把 if-else 條件式內容提取出來以增加可讀性。e.g.
private ValueAnimator setAnimation(float start, float end, String propertyName, int durationMS) { ValueAnimator animation; animation = ObjectAnimator.ofFloat(mLogo, propertyName, start, end); animation.setDuration(durationMS); return animation; } private AnimatorSet zoomInAndRotation360() { AnimatorSet animations; ValueAnimator scaleX = setAnimation(0.1f, 1f, "scaleX", 2500); ValueAnimator scaleY = setAnimation(0.1f, 1f, "scaleY", 1250); ValueAnimator rotationY = setAnimation(0f, 360f, "rotationY", 2500); ValueAnimator rotationX = setAnimation(0f, 360f, "rotationX", 1250); animations = new AnimatorSet(); animations.playTogether(scaleX, scaleY, rotationY, rotationX); return animations; } private AnimatorSet zoomOutAndRotation360() { AnimatorSet animations; ValueAnimator scaleX = setAnimation(2.0f, 1f, "scaleX", 2500); ValueAnimator scaleY = setAnimation(2.0f, 1f, "scaleY", 1250); ValueAnimator rotationY = setAnimation(0f, 360f, "rotationY", 2500); ValueAnimator rotationX = setAnimation(0f, 360f, "rotationX", 1250); animations = new AnimatorSet(); animations.playTogether(scaleX, scaleY, rotationY, rotationX); return animations; } private AnimatorSet rotationYAxis720() { AnimatorSet animations; ValueAnimator rotationY = setAnimation(0f, 720f, "rotationY", 2500); animations = new AnimatorSet(); animations.playTogether(rotationY); return animations; } private AnimatorSet rotationXAxis720() { AnimatorSet animations; ValueAnimator rotationX = setAnimation(0f, 720f, "rotationX", 2500); animations = new AnimatorSet(); animations.playTogether(rotationX); return animations; } private List<Animator> setAnimationParameters(int type) { AnimatorSet animations = null; if (type == 0) { animations = zoomInAndRotation360(); } else if (type == 1) { animations = zoomOutAndRotation360(); } else if (type == 2) { animations = rotationYAxis720(); } else if (type == 3) { animations = rotationXAxis720(); } ValueAnimator alphaNormal = setAnimation(1f, 1f, "alpha", 1000); ValueAnimator fadeOut = setAnimation(1f, 0f, "alpha", 1000); List<Animator> animators = new ArrayList<Animator>(); animators.add(animations); animators.add(alphaNormal); animators.add(fadeOut); return animators; }
OK~ 原先的setAnimationParameters,被我們拆分為一小段一小段的函式,小段的函式除了可以增加重用的機會以外,而明確的函式名稱完整表達了其意圖。
而且經過重構可以了解正確的程式碼意圖。為了提昇可讀性,第52~64的 if-else 決定引入列舉(AnimationType)來取代原本的int。e.g.
... private enum AnimationType { ZOOMIN_AND_ROTATION360, ZOOMOUT_AND_ROTATION360, ROTATION_Y_AXIS720, ROTATION_X_AXIS720 } private List<Animator> setAnimationParameters(int type) { AnimationType animationType = AnimationType.values()[type]; AnimatorSet animations = null; if (animationType == AnimationType.ZOOMIN_AND_ROTATION360) { animations = zoomInAndRotation360(); } else if (animationType == AnimationType.ZOOMOUT_AND_ROTATION360) { animations = zoomOutAndRotation360(); } else if (animationType == AnimationType.ROTATION_Y_AXIS720) { animations = rotationYAxis720(); } else if (animationType == AnimationType.ROTATION_X_AXIS720) { animations = rotationXAxis720(); } ValueAnimator alphaNormal = setAnimation(1f, 1f, "alpha", 1000); ValueAnimator fadeOut = setAnimation(1f, 0f, "alpha", 1000); List<Animator> animators = new ArrayList<Animator>(); animators.add(animations); animators.add(alphaNormal); animators.add(fadeOut); return animators; } ...
現在我們再來看看 mHandler 的用途,看起來它在 run 函式中呼叫了 sendMessage。讓 Logo 發生圖形的變化。
事實上讓 LogoFlow 繼承 Runnable interface 並不是一個好的作法。
簡單的說為了避免 ANR,一般的耗時任務都應該放到Worker Thread 而不可在 UI Thread 執行(詳細內容參考android worker thread)。
因此另外將圖形處理獨立到另一個 LogoAnimTask 來處理。e.g.
package com.foxx.main; import java.util.ArrayList; import java.util.List; import java.util.Random; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.os.AsyncTask; import com.foxx.main.LogoFlow.AnimationType; @SuppressLint("NewApi") public class LogoAnimTask extends AsyncTask<Void, Void, Void> { private ILogoFlow mLogoFlow; public LogoAnimTask(ILogoFlow logoFlow){ mLogoFlow = logoFlow; } @Override protected void onPreExecute() { Random randomAnimationType = new Random(); runLogoAnimations(randomAnimationType.nextInt(4)); } @Override protected Void doInBackground(Void... params) { return null; } private void runLogoAnimations(int animationType) { AnimatorSet animation = new AnimatorSet(); animation.playSequentially(setAnimationParameters(animationType)); animation.start(); animation.addListener(createAnimationListener()); } private ValueAnimator setAnimation(float start, float end, String propertyName, int durationMS) { ValueAnimator animation; animation = ObjectAnimator.ofFloat(mLogoFlow.getLogo(), propertyName, start, end); animation.setDuration(durationMS); return animation; } private AnimatorSet zoomInAndRotation360() { AnimatorSet animations; ValueAnimator scaleX = setAnimation(0.1f, 1f, "scaleX", 2500); ValueAnimator scaleY = setAnimation(0.1f, 1f, "scaleY", 1250); ValueAnimator rotationY = setAnimation(0f, 360f, "rotationY", 2500); ValueAnimator rotationX = setAnimation(0f, 360f, "rotationX", 1250); animations = new AnimatorSet(); animations.playTogether(scaleX, scaleY, rotationY, rotationX); return animations; } private AnimatorSet zoomOutAndRotation360() { AnimatorSet animations; ValueAnimator scaleX = setAnimation(2.0f, 1f, "scaleX", 2500); ValueAnimator scaleY = setAnimation(2.0f, 1f, "scaleY", 1250); ValueAnimator rotationY = setAnimation(0f, 360f, "rotationY", 2500); ValueAnimator rotationX = setAnimation(0f, 360f, "rotationX", 1250); animations = new AnimatorSet(); animations.playTogether(scaleX, scaleY, rotationY, rotationX); return animations; } private AnimatorSet rotationYAxis720() { AnimatorSet animations; ValueAnimator rotationY = setAnimation(0f, 720f, "rotationY", 2500); animations = new AnimatorSet(); animations.playTogether(rotationY); return animations; } private AnimatorSet rotationXAxis720() { AnimatorSet animations; ValueAnimator rotationX = setAnimation(0f, 720f, "rotationX", 2500); animations = new AnimatorSet(); animations.playTogether(rotationX); return animations; } private List<Animator> setAnimationParameters(int type) { AnimationType animationType = AnimationType.values()[type]; AnimatorSet animations = null; if (animationType == AnimationType.ZOOMIN_AND_ROTATION360) { animations = zoomInAndRotation360(); } else if (animationType == AnimationType.ZOOMOUT_AND_ROTATION360) { animations = zoomOutAndRotation360(); } else if (animationType == AnimationType.ROTATION_Y_AXIS720) { animations = rotationYAxis720(); } else if (animationType == AnimationType.ROTATION_X_AXIS720) { animations = rotationXAxis720(); } ValueAnimator alphaNormal = setAnimation(1f, 1f, "alpha", 1000); ValueAnimator fadeOut = setAnimation(1f, 0f, "alpha", 1000); List<Animator> animators = new ArrayList<Animator>(); animators.add(animations); animators.add(alphaNormal); animators.add(fadeOut); return animators; } private AnimatorListener createAnimationListener() { return new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { mLogoFlow.finishLogo(); } @Override public void onAnimationCancel(Animator animation) { } }; } }
另外建立ILogoFlow interface 並讓外界透過 interface 來交互,目的是讓模組透過抽象來相依。
public interface ILogoFlow { public View getLogo(); public void finishLogoFlow(); }
而原本的 LogoFlow 只剩下輕量化的內容。e.g.
package com.foxx.main; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.widget.ImageView; import com.tlyerfoxx.sbcnote.R; @SuppressLint("NewApi") public class LogoFlow extends Activity implements ILogoFlow { private ImageView mLogo; private LogoAnimTask mLogoAnimationtask; public enum AnimationType { ZOOMIN_AND_ROTATION360, ZOOMOUT_AND_ROTATION360, ROTATION_Y_AXIS720, ROTATION_X_AXIS720 } public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.logoflow); mLogo = (ImageView) findViewById(R.id.imageView1); mLogoAnimationtask = new LogoAnimTask(this); mLogoAnimationtask.execute(); } public boolean onTouchEvent(MotionEvent event) { finishLogoFlow(); return true; } @Override public View getLogo() { return mLogo; } @Override public void finishLogoFlow() { LogoFlow.this.finish(); Intent goAct = new Intent(); goAct.setClass(LogoFlow.this, Table_Flow.class); startActivity(goAct); overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit); System.exit(0); } }
最後重新命名 LogoFlow 為 LogoActivity,ILogoFlow 為 ILogo。