2.3 Activity的使用技巧彙總

點此進入:從零快速構建APP系列目錄導圖
點此進入:UI編程系列目錄導圖
點此進入:四大組件系列目錄導圖
點此進入:數據網絡和線程系列目錄導圖

一、橫豎屏切換與狀態保存的問題

前面的文章說到了App橫豎屏切換的時候會銷燬當前的Activity然後重新創建一個,我們可以自行在生命週期的每個方法裏都添加打印Log的語句,以此來進行判斷。又或者設一個按鈕一個TextView點擊按鈕後,修改TextView 文本,然後橫豎屏切換,這時我們就會發現TextView文本變回之前的內容了。

總之,橫豎屏切換時Activity走下述生命週期:onPause-> onStop-> onDestory-> onCreate->onStart->onResume。也就是說Activity銷燬後又重新建立了一個,下面我們說一下關於橫豎屏切換可能遇到的問題以及解決方法。

1、禁止屏幕橫豎屏自動切換

解決方法很簡單,我們在AndroidManifest.xml中爲Act添加一個android:screenOrientation屬性就可以了。

android:screenOrientation有下述可選值:
  • unspecified:默認值,由系統來判斷顯示方向,判定的策略是和設備相關的,所以不同的設備會有不同的顯示方向。
  • landscape:橫屏顯示(寬比高要長)。
  • portrait:豎屏顯示(高比寬要長)。
  • user:用戶當前首選的方向。
  • behind:和該Activity下面的那個Activity的方向一致(在Activity堆棧中的)。
  • sensor:由物理的感應器來決定,如果用戶旋轉設備這屏幕會橫豎屏切換。
  • nosensor:忽略物理感應器,這樣就不會隨着用戶旋轉設備而更改了(”unspecified”設置除外)。

2、橫豎屏時想加載不同的佈局

(1)方法一:準備兩套不同的佈局

Android會自己根據橫豎屏加載不同佈局,方法是創建兩個佈局文件夾:layout-land橫屏、layout-port豎屏,然後把這兩套佈局文件丟這兩文件夾裏,文件名一樣,Android就會自行判斷,然後加載相應佈局了!

(1)方法二:自己在代碼中進行判斷

這樣的話我們就能自己想加載什麼就加載什麼,我們一般是在onCreate()方法中加載佈局文件的,我們可以在這裏對橫豎屏的狀態做下判斷,關鍵代碼如下:

if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){  
     setContentView(R.layout.橫屏);
}  

else if (this.getResources().getConfiguration().orientation ==Configuration.ORIENTATION_PORTRAIT) {  
    setContentView(R.layout.豎屏);
}

3、狀態保存問題

這個其實我們之前說過了,通過一個Bundle savedInstanceState參數即可完成,三個核心方法是:

onCreate(Bundle savedInstanceState);
onSaveInstanceState(Bundle outState);
onRestoreInstanceState(Bundle savedInstanceState);

然後重寫onSaveInstanceState()方法,往這個bundle中寫入數據,比如:

outState.putInt("num",1);

然後你在onCreate或者onRestoreInstanceState中就可以拿出裏面存儲的數據,不過拿之前要判斷下是否爲null:

savedInstanceState.getInt("num");

二、知曉當前是哪個Activity

這個技巧將教會你,如何根據程序當前的界面就能判斷出這是哪一個Activity。可能你會覺得挺納悶的,我自己寫的代碼怎麼會不知道這是哪一個Activity呢?很不幸的是,在你真正進入到企業之後,更有可能的是接手一份別人寫的代碼,因爲你剛進公司就正好有一個新項目啓動的概率並不高。閱讀別人的代碼時有一個很頭疼的問題,就是你需要在某個界面上修改一些非常簡單的東西,但是你半天找不到這個界面對應的活動是哪一個。學會了本節的技巧之後,這對你來說就再也不是難題了。

首先需要新建一個 BaseActivity 繼承自Activity,然後在 BaseActivity 中重寫 onCreate()方法,如下所示:

public class BaseActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, getClass().getSimpleName());
    }
}

這樣我們就在 onCreate() 方法中獲取了當前實例的類名,並通過 Log 打印了出來。

接下來我們需要讓 BaseActivity 成爲工程項目中所有具有共同屬性的Activity的父類,這樣雖然項目中的Activity不再直接繼承自 Activity 了,但是它們仍然完全繼承了 Activity 中的所有特性,並且加入了我們最新的功能。

三、隨時隨地完全退出App以及退出指定Activity的方法

1、完全退出App

(1)完全退出Activity

有時我們可能會打開了很多個Activity,突然來個這樣的需求,在某個頁面可以關掉所有的Activity並退出程序。別以爲這不會是真的,比如如果目前你手機的界面還停留在 SecondActivity,你會發現當前想退出程序是非常不方便的,需要連按兩次 Back 鍵纔行。有可能你會說:按Home鍵就行了呀。但是按 Home 鍵只是把程序掛起,並沒有退出程序。所以這個問題就足以引起我們的思考,如果我們的程序需要一個註銷或者退出的功能該怎麼辦呢?必須要有一個隨時隨地都能退出程序的方案纔行。

好吧,下面提供一個關閉所有Activity的方法, 就是用一個list集合來管理所有Activity,然後就方便後續的退出操作了。

新建一個 ActivityCollector 類作爲活動管理器,代碼如下所示:
public class ActivityCollector {
    public static List<Activity> activities = new ArrayList<Activity>();
    public static void addActivity(Activity activity) {
        activities.add(activity);
    }
    public static void removeActivity(Activity activity) {
        activities.remove(activity);
    }
    public static void finishAll() {
        for (Activity activity : activities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
    }
}

在Activity管理器中,我們通過一個 List 來暫存Activity,然後提供了一個 addActivity() 方法用
於向 List 中添加一個Activity,提供了一個 removeActivity() 方法用於從 List 中移除Activity,最後提供了一個 finishAll() 方法用於將 List 中存儲的Activity全部都銷燬掉,這樣我們就可以輕鬆實現我們想要的功能了,比如我們可以這麼使用:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());
        ActivityCollector.addActivity(this);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }

在 onCreate() 方法中調用了 ActivityCollector 的 addActivity()方法,表明將當前正在創建的Activity添加到活動管理器裏。然後在 onDestroy() 方法中調用了 ActivityCollector 的 removeActivity() 方法,表明將一個馬上要銷燬的Activity從活動管理器裏移除。

從此以後,不管我麼想在什麼地方退出程序,只需要調用 ActivityCollector.finishAll() 方法就可以了。

(2)殺死整個App

上面說的是關閉所有Activity的,但是有些時候我們可能想殺死整個App,連後臺任務都殺死 殺得一乾二淨的話,可以使用搭配着下述代碼使用:

    /**
     * 退出應用程序
     */
    public void AppExit(Context context) {
        try {
            ActivityCollector.finishAll();
            ActivityManager activityMgr = (ActivityManager) context
                    .getSystemService(Context.ACTIVITY_SERVICE);
            activityMgr.killBackgroundProcesses(context.getPackageName());
            System.exit(0);
        } catch (Exception ignored) {}
    }

2、退出指定Activity的方法

首先,我們提供一種使用命令行查看當前所有Activity的命令

使用下述命令即可,前提是你爲SDK配置了環境變量:

adb shell dumpsys activity

其次,我們可以用這個Activity管理類來更精確的管理Activity:

public class AppManager {
    private static Stack<Activity> activityStack;
    private static AppManager instance;
    private AppManager(){}
    /**
     * 單一實例
     */
    public static AppManager getAppManager(){
        if(instance==null){
            instance=new AppManager();
        }
        return instance;
    }
    /**
     * 添加Activity到堆棧
     */
    public void addActivity(Activity activity){
        if(activityStack==null){
            activityStack=new Stack<Activity>();
        }
        activityStack.add(activity);
    }
    /**
     * 獲取當前Activity(堆棧中最後一個壓入的)
     */
    public Activity currentActivity(){
        Activity activity=activityStack.lastElement();
        return activity;
    }
    /**
     * 結束當前Activity(堆棧中最後一個壓入的)
     */
    public void finishActivity(){
        Activity activity=activityStack.lastElement();
        finishActivity(activity);
    }
    /**
     * 結束指定的Activity
     */
    public void finishActivity(Activity activity){
        if(activity!=null){
            activityStack.remove(activity);
            activity.finish();
            activity=null;
        }
    }
    /**
     * 結束指定類名的Activity
     */
    public void finishActivity(Class<?> cls){
        for (Activity activity : activityStack) {
            if(activity.getClass().equals(cls) ){
                finishActivity(activity);
            }
        }
    }
    /**
     * 結束所有Activity
     */
    public void finishAllActivity(){
        for (int i = 0, size = activityStack.size(); i < size; i++){
            if (null != activityStack.get(i)){
                activityStack.get(i).finish();
            }
        }
        activityStack.clear();
    }
    /**
     * 退出應用程序
     */
    public void AppExit(Context context) {
        try {
            finishAllActivity();
            ActivityManager activityMgr=(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
            activityMgr.restartPackage(context.getPackageName());
            System.exit(0);
        } catch (Exception e) { }
    }
}

四、雙擊退出程序的兩種方法

1、定義一個變量來標識是否退出

private static boolean isExit = false;
Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        isExit = false;
    }
};

public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        if (!isExit) {
            isExit = true;
            Toast.makeText(getApplicationContext(), "再按一次退出程序",
                    Toast.LENGTH_SHORT).show();
            // 利用handler延遲發送更改狀態信息
            mHandler.sendEmptyMessageDelayed(0, 2000);
        } else {
            exit(this);
        }
        return false;
    }
return super.onKeyDown(keyCode, event);}

2、保存點擊時間

    private long exitTime = 0;
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if ((System.currentTimeMillis() - exitTime) > 2000) {
                Toast.makeText(getApplicationContext(), "再按一次退出程序",
                        Toast.LENGTH_SHORT).show();
                exitTime = System.currentTimeMillis();
            } else {
                exit();
            }
            return false;
        }
        return super.onKeyDown(keyCode, event);
    }

五、啓動Activity的最佳寫法

啓動Activity的方法相信你已經非常熟悉了,首先通過 Intent 構建出當前的“意圖”,然後調用 startActivity() 或 startActivityForResult() 方法將Activity啓動起來,如果有數據需要從一個Activity傳遞到另一個Activity,也可以藉助 Intent 來完成。

假設 SecondActivity 中需要用到兩個非常重要的字符串參數,在啓動 SecondActivity 的時候必須要傳遞過來,那麼我們很容易會寫出如下代碼:

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2");
startActivity(intent);

這樣寫是完全正確的,不管是從語法上還是規範上,只是在真正的項目開發中經常會有對接的問題出現。比如 SecondActivity 並不是由你開發的,但現在你負責的部分需要有啓動 SecondActivity 這個功能,而你卻不清楚啓動這個Activity需要傳遞哪些數據。這時無非就有兩種辦法,一個是你自己去閱讀 SecondActivity 中的代碼,二是詢問負責編寫 SecondActivity 的同事,但這樣你會不會覺得很麻煩呢?其實只需要換一種寫法,就可以輕鬆解決掉上面的窘境。

修改 SecondActivity 中的代碼,如下所示:
    public class SecondActivity extends BaseActivity {
        public static void actionStart(Context context, String data1, String data2) {
            Intent intent = new Intent(context, SecondActivity.class);
            intent.putExtra("param1", data1);
            intent.putExtra("param2", data2);
            context.startActivity(intent);
        }
    }

我們在 SecondActivity 中添加了一個 actionStart() 方法,在這個方法中完成了 Intent 的構建,另外所有 SecondActivity 中需要的數據都是通過 actionStart() 方法的參數傳遞過來的,然後把它們存儲到 Intent 中,最後調用 startActivity()方法啓動 SecondActivity。

這樣寫的好處在哪裏呢?最重要的一點就是一目瞭然, SecondActivity 所需要的數據全部都在方法參數中體現出來了,這樣即使不用閱讀 SecondActivity 中的代碼,或者詢問負責編寫 SecondActivity 的同事,你也可以非常清晰地知道啓動 SecondActivity 需要傳遞哪些數據。另外,這樣寫還簡化了啓動活動的代碼,現在只需要一行代碼就可以啓動 SecondActivity,如下所示:

    button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            SecondActivity.actionStart(MainActivity.this, "data1", "data2");
        }
    });

養成一個良好的習慣, 給你編寫的每個Activity都添加類似的啓動方法,這樣不僅可以讓啓動Activity變得非常簡單,還可以節省不少你同事過來詢問你的時間。

六、Activity全屏與對話框風格的實現

(1)Activity全屏風格的實現

1、代碼隱藏ActionBar

在Activity的onCreate方法中調用getActionBar.hide();,以此隱藏ActionBar。

2、通過requestWindowFeature設置

在代碼中調用requestWindowFeature(Window.FEATURE_NO_TITLE); ,不過該代碼需要在setContentView ()之前調用,不然會報錯。

不過有的時候這種方法並不能奏效,解決方法有兩種:

(1)將AppCompatActivity改爲Activity,此時 requestWindowFeature(Window.FEATURE_NO_TITLE);是有效的。
(2)在onCreate()方法中加入如下代碼:

if (getSupportActionBar() != null){
   getSupportActionBar().hide();
}
3、通過AndroidManifest.xml的theme設置

在需要全屏的Activity的標籤內設置 theme = @android:style/Theme.NoTitleBar.FullScreen

(2)Activity對話框風格的實現

在某些情況下,我們可能需要將Activity設置成對話框風格的,Activity一般是佔滿全屏的, 而Dialog則是佔據部分屏幕的,實現起來也很簡單。

1、直接設置下Activity的theme
android:theme="@android:style/Theme.Dialog"
2、設置標題和小圖標
// 設置左上角小圖標
requestWindowFeature(Window.FEATURE_LEFT_ICON);
setContentView(R.layout.main);
getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, android.R.drawable.ic_lion_icon);
// 設置文字
setTitle(R.string.actdialog_title);  //XML代碼中設置:android:label="@string/activity_dialog"

七、爲Activity設置過場動畫:

所謂的過場動畫就是切換到另外的Activity時加上一些切換動畫,比如淡入淡出,放大縮小,左右互推等。當然,我們並不在這裏詳細講解動畫,後面我們會有專門的篇幅來講解這個,這裏只教大家如何去加載動畫,另外給大家提供了一些比較常用的過渡動畫,只要將相關動畫文件添加到res/anim目錄下,然後下述方法二選一 就可以實現Activity的切換動畫了。

方法一:

Activity A 跳轉到 Activity B,在startActivity(intent)後面加上overridePendingTransition(R.anim.anim_in, R.anim.anim_out);
Activity B 跳轉到 Activity A,要在finish()後面加上overridePendingTransition(R.anim.anim_in, R.anim.anim_out);
其中,anim_in是進入Activity的動畫,而anim_out是退出Activity的動畫。

方法二:

通過style進行配置,這個是全局的,就是所有的Activity都會加載這個動畫。

在style.xml中自定義style:
<!-- 默認Activity跳轉動畫 -->
<style name="default_animation" mce_bogus="1" parent="@android:style/Animation.Activity">
    <item name="android:activityOpenEnterAnimation">@anim/default_anim_in</item>
    <item name="android:activityOpenExitAnimation">@anim/anim_stay</item>
    <item name="android:activityCloseEnterAnimation">@anim/anim_stay</item>
    <item name="android:activityCloseExitAnimation">@anim/default_anim_out</item>
</style>
其中的四個item分別代表:
  • Activity A跳轉到Activity B時Activity B進入動畫
  • Activity A跳轉到Activity B時Activity A退出動畫
  • Activity B返回Activity A時Activity A的進入動畫
  • Activity B返回Activity A時ActivityB的退出動畫
然後修改AppTheme:
<style name="AppTheme" mce_bogus="1" parent="@android:style/Theme.Light">
        <item name="android:windowAnimationStyle">@style/default_animation</item>
        <item name="android:windowNoTitle">true</item>
</style>
最後在appliction設置下:
<application
   android:icon="@drawable/logo"
   android:label="@string/app_name"
   android:theme="@style/AppTheme" >

好的,Activity過場動畫就這樣設置好了。

八、爲Activity設置透明背景

方法一:

這種方法比較簡單,只有一個步驟,只需要在配置文件中把需要設置爲透明的Activity的樣式設置爲:

Android:theme="@android:style/Theme.Translucent"

即可,這種方式只改變背景的顏色,對其他控件沒有影響,但是它只能把背景設置爲完全透明,如果要設置爲半透明或者要設置透明的程度無法實現。

方法二:

這種方法也比較簡單,只需要在方法一的基礎上,再佈局文件中配置背景顏色就可以:

android:background="#01000000"  

“#01000000”中的“01”表示的是背景透明的程度,這個值只能設置01及以上的值,不能設置爲00,及不能設置爲完全透明,不過設置爲01其實和透明的效果也很接近了,肉眼幾乎看不出來區別了,這種方法同樣對其他控件沒有影響。

方法三

這種方法稍微複雜些,有幾個步驟,這種方法對其他控件的透明度也會產生影響,並且可以自己設置透明的程度,相對來說要靈活一些。
- 第一步
在res/values下建立colors.xml文件,設置一個背景顏色,在這裏可以設置你背景的顏色和透明度。

<color name="transparent">#55ff</color>  
  • 第二步
    在res/values/下建styles.xml,設置程序的風格
<style name="Transparent">  
        <item name="android:windowBackground">@color/transparent</item>  
        <item name="android:windowIsTranslucent">true</item>  
        <item name="android:windowAnimationStyle">@+android:style/Animation.Translucent</item>  
</style>  
  • 第三步
    把這個styles.xml用在相應的Activity上,即在AndroidManifest.xml中的任意標籤中添加:
android:theme="@style/Transparent"  

如果想設置所有的activity都使用這個風格,可以把這句標籤語句添加在中。
  
這個方法不僅對背景透明有效,而且對其他控件也有效,如果其他控件沒有設置背景顏色,會呈現出透明的效果。這種方法比較複雜些,如果不是需要對整個頁面及控件都有透明度要求,建議使用前面兩種方法。 

點此進入:GitHub開源項目“愛閱”。“愛閱”專注於收集優質的文章、站點、教程,與大家共分享。下面是“愛閱”的效果圖:


聯繫方式:

簡書:WillFlow
CSDN:WillFlow
微信公衆號:WillFlow

微信公衆號:WillFlow

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章