視頻講解
視頻原鏈接 : 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<>();
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>