史上最方便的Android頁面框架XPage使用指南

視頻講解

視頻原鏈接 : https://www.bilibili.com/video/BV1eD4y1R73U

簡介

> XPage設計的初衷是希望能做一個通用的Activity作爲殼,Fragment作爲頁面填充展示,並且能夠像Activity那樣自由的切換和數據交互。

XPage是一個非常方便的fragment頁面框架!天下武功,唯快不破,XPage最大的特點就是快,提高開發的效率!

如果你喜歡的話,歡迎start收藏, 項目地址: https://github.com/xuexiangjys/XPage

特徵

  • 支持assets下“corepage.json”靜態配置Fragment頁面信息。
  • 支持Application中動態配置Fragment頁面信息。
  • 支持通過註解@Page的方式動態自動配置頁面信息。
  • 支持自定義Fragment頁面信息配置。
  • 支持4種默認Fragment頁面切換動畫。
  • 支持Fragment頁面間參數傳遞。
  • 支持Fragment頁面屬性保存。
  • 支持Fragment頁面的onKeyDown、onFragmentResult等生命週期
  • 支持Fragment和Fragment頁面自由跳轉以及數據交互。
  • 支持導航欄通過註解的方式自動添加及設置。
  • 支持進行內存泄露監測。
  • 支持自定義TitleBar全局主題屬性。
  • 支持自定義Fragment頁面容器。
  • 支持自定義Activity頁面容器。
  • 支持Fragment之間、activity和fragment之間的數據交互。
  • 兼容kotlin和androidx。

設計原由

當初做Android開發時每當我寫一個頁面,都需要創建一個Activity,並且還需要在manifest中註冊一堆Activity信息,這樣既不方便,而且對資源的開銷也比較大。因此當時我就設想能否創造出一個通用萬能的Activity容器,可以全權負責Fragment的切換展示和數據交互,只需要一行代碼即可完成所有的操作,還不需要自己手動去註冊,可以一鍵生成。

設計思路

剛開始的時候真的很難,沒有什麼好的思路,最初只是簡單封裝了一個Activity,通過傳入一些key值從而獲取並加載對應的fragment,類似ARouter中Fragment發現那種。其實這樣做並沒有解決一個容器的問題,而且頁面切換也不是很靈活,不夠通用,使用起來也不是很方便。

突然有一天我發現Github上有個開源項目CorePage寫得非常好,完美地解決了我對一個Activity容器的問題,於是我決定仔細研究其代碼,並在其基礎上設計出了XPage的最初版本。

就在XPage正式投入使用的過程中,我發現還是存在不少問題的:

  • 1.對外API不夠靈活,使用起來不夠方便;

  • 2.每個Fragment仍需要手動註冊,很麻煩;

對於API不夠靈活的問題,我在之後的版本中陸續通過構造者模式設計以及Android主題屬性等手段解決了。

而對於手動註冊的問題,我正是借鑑了ARouter的思路,通過Android APT技術,從而實現了Fragment信息的自動註冊。

解決痛點

  • 只需要一個Activity容器就可以實現多個頁面的交互。

  • Fragment自由切換和數據交互。

  • 無需在manifest中註冊一堆Activity信息,通過@Page註解一鍵自動註冊。


集成指南

添加Gradle依賴

1.在項目根目錄的build.gradle的 repositories 添加jitpack倉庫

allprojects {
     repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}

2.在dependencies添加引用

以下是版本說明,選擇一個即可。

  • androidx版本:3.0.0及以上
dependencies {
  ...
  // XPage
  implementation 'com.github.xuexiangjys.XPage:xpage-lib:3.0.0'
  annotationProcessor 'com.github.xuexiangjys.XPage:xpage-compiler:3.0.0'
  // ButterKnife的sdk
  implementation 'com.jakewharton:butterknife:10.1.0'
  annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
}
  • support版本:2.3.0及以下
dependencies {
  ...
  // XPage
  implementation 'com.github.xuexiangjys.XPage:xpage-lib:2.3.0'
  annotationProcessor 'com.github.xuexiangjys.XPage:xpage-compiler:2.3.0'
  // ButterKnife的sdk
  implementation 'com.jakewharton:butterknife:8.4.0'
  annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
}

【注意】如果你使用的是kotlin,請使用如下配置:

apply plugin: 'kotlin-kapt'

dependencies {
  ...
  //XPage
  implementation 'com.github.xuexiangjys.XPage:xpage-lib:3.0.0'
  kapt 'com.github.xuexiangjys.XPage:xpage-compiler:3.0.0'
  //ButterKnife的sdk
  implementation 'com.jakewharton:butterknife:10.1.0'
  kapt 'com.jakewharton:butterknife-compiler:10.1.0'
}

3.進行moduleName註冊(非必要)

defaultConfig {
    ...

    javaCompileOptions {
        annotationProcessorOptions {
            arguments = [ moduleName : project.getName() ]
        }
    }
}

【注意】:如果不註冊的話,默認ModuleName爲app


頁面註冊

方法一:Application中動態註冊【推薦】

1.自動進行頁面註冊【推薦】

使用apt編譯時自動生成的頁面註冊配置類 "moduleName"+PageConfig 的getPages()進行註冊。

PageConfig.getInstance()
        .setPageConfiguration(new PageConfiguration() { //頁面註冊
            @Override
            public List<pageinfo> registerPages(Context context) {
                //自動註冊頁面,是編譯時自動生成的,build一下就出來了。如果你還沒使用@Page的話,暫時是不會生成的。
                return AppPageConfig.getInstance().getPages(); //自動註冊頁面
            }
        })
        .debug("PageLog")       //開啓調試
        .setContainActivityClazz(XPageActivity.class) //設置默認的容器Activity
        .enableWatcher(false)   //設置是否開啓內存泄露監測
        .init(this);            //初始化頁面配置

【注意】:如果你的項目中只是增加了依賴,還沒有使用@Page註解XPageFragment頁面的話,在編譯時是不會自動生成註冊頁面的!!

2.手動動態進行頁面註冊

PageConfig.getInstance()
        .setPageConfiguration(new PageConfiguration() { //頁面註冊
            @Override
            public List<pageinfo> registerPages(Context context) {
                List<pageinfo> pageInfos = new ArrayList&lt;&gt;();
                addPageInfoAndSubPages(pageInfos, MainFragment.class);
                pageInfos.add(PageConfig.getPageInfo(DateReceiveFragment.class));
                return pageInfos;        //手動註冊頁面
            }
        })
        .debug("PageLog")       //開啓調試
        .enableWatcher(false)   //設置是否開啓內存泄露監測
        .init(this);            //初始化頁面配置

方法二:assets中靜態註冊

在assets文件夾中新建“corepage.json“,然後進行如下配置:

[
  {
    "name": "測試頁面1",
    "classPath": "com.xuexiang.xpagedemo.fragment.TestFragment1",
    "params": ""
  },
  {
    "name": "測試頁面2",
    "classPath": "com.xuexiang.xpagedemo.fragment.TestFragment2",
    "params": {
      "key1":"這是參數1的值",
      "key2":"這是參數2的值"
    }
  },
]

混淆配置

# fastjson
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.fastjson.** { *; }
-keepattributes Signature

# xpage
-keep class com.xuexiang.xpage.annotation.** { *; }

基礎使用

頁面跳轉

> 使用XPage,Activity必須要繼承XPageActivity,Fragment必須要繼承XPageFragment,否則將無法調用頁面跳轉的openPage方法。

頁面的簡單打開和關閉

  • 使用openPage即可打開頁面,入參可爲跳轉類的類名,也可以是類的標識(@Page標註的name字段)
// 使用類名打開
openPage(TestFragment.class);
// 使用標識打開
openPage("TestFragment");
  • 使用popToBack即可關閉頁面。
// 關閉當前頁,返回上一頁
popToBack();
// 關閉當前頁並跳轉至某個頁面
popToBack("popBackName", null);

頁面打開等待結果返回

  • 1.使用openPageForResult即可,類似Activity裏的startActivityForResult
openPageForResult(TestFragment.class, null, REQUEST_CODE);
  • 2.使用setFragmentResult來設置頁面關閉的返回值,類似Activity裏的setResult方法。
// 設置返回的數據,類似Activity裏的setResult
Intent intent = new Intent();
intent.putExtra(KEY_BACK_DATA, "==【返回的數據】==");
setFragmentResult(500, intent);
// 返回操作
popToBack();
  • 3.重寫Fragment的onFragmentResult方法來接收返回的數據,類似Activity裏的onActivityResult方法。
@Override
public void onFragmentResult(int requestCode, int resultCode, Intent data) {
    super.onFragmentResult(requestCode, resultCode, data);
    if (data != null) {
        Bundle extras = data.getExtras();
        XToastUtils.toast("requestCode:" + requestCode + " resultCode:" + resultCode + " data:" + extras.getString(TestFragment.KEY_BACK_DATA));
    }
}

數據傳遞

  • 使用openPage打開頁面時,可傳入Bundle作爲參數。
// 設置需要傳遞的參數
Bundle params = new Bundle();
params.putBoolean(DateReceiveFragment.KEY_IS_NEED_BACK, false);
int id = (int) (Math.random() * 100);
params.putString(DateReceiveFragment.KEY_EVENT_NAME, "事件" + id);
params.putString(DateReceiveFragment.KEY_EVENT_DATA, "事件" + id + "攜帶的數據");
// 把參數傳入
openPage(DateReceiveFragment.class, params);
  • 數據接收

數據接收和普通Fragment接收數據一樣,使用getArguments獲取傳入的數據。

Bundle arguments = getArguments();
String eventName = arguments.getString(DateReceiveFragment.KEY_EVENT_NAME);
String eventData = arguments.getString(DateReceiveFragment.KEY_EVENT_DATA);

頁面轉場動畫

頁面轉場動畫可以動態設置,也可以靜態設置。

靜態設置

在我們使用@Page進行頁面註冊的時候,我們可以靜態設置轉場動畫、默認參數、拓展字段等。

Page註解的屬性表:

屬性名 類型 默認值 備註
name String 註解類的類名 頁面的名稱、唯一標識符
params String[] {""} 靜態設置默認參數
anim CoreAnim CoreAnim.slide 頁面轉場動畫
extra int -1 拓展字段

動態設置

使用openPage打開頁面時,可傳入CoreAnim枚舉設置頁面轉場動畫。

switch(position) {
    case 0:
        openPage(TestFragment.PAGE_NAME, null, CoreAnim.none);//沒有動畫
        break;
    case 1:
        openPage(TestFragment.PAGE_NAME, null, CoreAnim.present);//由下到上動畫
        break;
    case 2:
        openPage(TestFragment.PAGE_NAME, null, CoreAnim.slide);//從左到右動畫
        break;
    case 3:
        openPage(TestFragment.PAGE_NAME, null, CoreAnim.fade);//漸變
        break;
    case 4:
        openPage(TestFragment.PAGE_NAME, null, CoreAnim.zoom);//放大
        break;
    default:
        break;
}

設置自定義轉場動畫

//自定義動畫
openPage(TestFragment.PAGE_NAME, null, new int[]{
        // OpenEnterAnimation, 頁面打開進場動畫
        R.anim.custom_open_enter,
        // OpenExitAnimation, 頁面打開退場動畫
        R.anim.custom_open_exit,

        // CloseEnterAnimation, 頁面關閉進場動畫
        R.anim.custom_close_enter,
        // CloseExitAnimation, 頁面關閉退場動畫
        R.anim.custom_close_exit
});

進階使用

使用PageOption進行頁面操作【推薦】

使用PageOption.to進行頁面選項構建。

  • setAnim: 設置頁面轉場動畫
  • setRequestCode: 設置頁面打開的請求碼,用於返回結果
  • setAddToBackStack: 設置是否加入堆棧
  • setNewActivity: 設置是否使用新的Activity打開
  • setContainActivityClazz:設置新打開Activity的容器
  • putBoolean、putString、putAll等:設置傳遞的參數
  • open:打開頁面進行跳轉
PageOption.to(TestFragment.class) //跳轉的fragment
    .setAnim(CoreAnim.zoom) //頁面轉場動畫
    .setRequestCode(100) //請求碼,用於返回結果
    .setAddToBackStack(true) //是否加入堆棧
    .setNewActivity(true, ContainActivity.class) //是否使用新的Activity打開
    .putBoolean(DateReceiveFragment.KEY_IS_NEED_BACK, true) //傳遞的參數
    .open(this); //打開頁面進行跳轉

自定義TitleBar樣式

可以設置XPageTitleBarStyle主題樣式來自定義標題欄的默認樣式。

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/xpage_default_actionbar_color</item>
    <item name="colorPrimaryDark">@color/xpage_default_actionbar_color</item>
    <item name="colorAccent">@color/xpage_default_actionbar_color</item>

    <!--標題欄的背景圖片,優先使用背景圖片,沒有背景圖片才使用背景顏色,可選-->
    <item name="xpage_actionbar_background">@null</item>
    <!--標題欄的背景顏色-->
    <item name="xpage_actionbar_color">@color/xpage_default_actionbar_color</item>
    <!--是否支持沉浸式標題欄, 默認false-->
    <item name="xpage_actionbar_immersive">false</item>
    <!--標題欄返回箭頭, 默認R.drawable.xpage_ic_navigation_back_white-->
    <item name="xpage_actionbar_navigation_back">@drawable/xpage_ic_navigation_back_white</item>
    <!--標題欄的高度,默認52dp-->
    <item name="xpage_actionbar_height">60dp</item>
    <!--標題欄標題文字的大小,默認18sp-->
    <item name="xpage_actionbar_title_text_size">21sp</item>
    <!--標題欄副標題文字的大小,默認12sp-->
    <item name="xpage_actionbar_sub_text_size">14sp</item>
    <!--標題欄動作文字的大小,默認15sp-->
    <item name="xpage_actionbar_action_text_size">18sp</item>
    <!--標題欄動作圖片的padding,默認5dp-->
    <item name="xpage_actionbar_action_padding">6dp</item>
    <!--標題欄兩側文字的padding,默認14dp-->
    <item name="xpage_actionbar_side_text_padding">16dp</item>
    
    <item name="XPageTitleBarStyle">@style/XPageTitleBar.Custom</item>
</style>

<style name="XPageTitleBar.Custom">
    <item name="tb_immersive">false</item>
    <item name="tb_centerGravity">center</item>
</style>

利用XPage來寫程序的Tab主頁

詳細可參見BottomNavigationViewFragment

就像正常使用ViewPager加載Fragment那樣。但是這裏需要注意的兩點是:

  • 由於使用ViewPager進行加載,而非XPage,因此Fragment的initTitleBar方法需要被覆蓋。
@Override
protected TitleBar initTitleBar() {
    //不使用@Page標註的一定要注意覆蓋這個方法
    return null;
}
  • 由於爲了新開頁面不影響Tab主頁當前容器的狀態,需要在打開新頁面的使用設置使用新容器。
PageOption.to(TestFragment.class)
        //新建一個容器,以不影響當前容器
        .setNewActivity(true)
        .open(this);

複雜Activity界面容器的自定義

詳細可參見ComplexActivity

1.自定義頁面容器的佈局,在佈局中一定要包含idfragment_container

<framelayout android:id="@id/fragment_container" android:layout_width="match_parent" android:layout_height="400dp">
</framelayout>

2.在XPageActivity中設置頁面容器的佈局ID

@Override
protected int getLayoutId() {
    return R.layout.activity_complex;
}

3.使用changePage方法切換Fragment。

changePage(TestFragment.PAGE_NAME, null, CoreAnim.none);

【注意】在切換Fragment的時候,fragment並不會走onResume和onPause生命週期,建議使用onHiddenChanged代替。

4.使用getPage方法獲取指定的Fragment,就可以獲取該fragment頁面中的數據。

TabAFragment tabAFragment = getPage(TabAFragment.class);
if (tabAFragment != null) {
    ToastUtils.toast(tabAFragment.getData());
} else {
    ToastUtils.toast("頁面還未加載!");
}

常見問題

1.問:我使用的是自動註冊,爲什麼我剛接入的時候,一直報錯找不到AppPageConfig?

答:首先需要明確的是,AppPageConfig是需要編譯之後纔會出現的,如果你沒有編譯的話,是肯定沒有的。如果你編譯了還是找不到,你可以根據如下步驟依次進行排查:

  • 排查當前項目中是否有Fragment被@Page註解了,如果沒有的話,即使編譯了也是不會生成AppPageConfig文件的。
  • 排查是否進行了moduleName註冊,因爲自動生成的註冊類是根據"moduleName"+PageConfig的規則進行自動生成的,如果沒有配置moduleName的話,默認纔是app,這樣自動生成的註冊類纔是AppPageConfig。如果你配置了moduleName,而且模塊的名稱也不是app,那麼自動生成的註冊類肯定不是AppPageConfig
  • 查看編譯時是否有其他報錯,如果在編譯的過程中就報錯了,那麼作爲apt這種編譯時自動生成的註冊類也是無法生成的。
  • 如果以上都沒能解決你的問題,那麼考慮八成是你哪裏集成出錯了,所以需要你回頭重新閱讀集成指南,不能放過每一個細節。如果還是不行,考慮直接使用簡化版的Android空殼模版工程 先熟悉一下集成和使用。

微信公衆號

微信公衆號

</pageinfo></pageinfo></pageinfo>

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