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。