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。