視頻講解
視頻鏈接 : https://www.bilibili.com/video/BV1eD4y1R73U
簡介
XPage是一個非常方便的fragment頁面框架汁政!天下武功拍霜,唯快不破,XPage最大的特點就是快,提高開發(fā)的效率!
XPage設(shè)計的初衷是希望能做一個通用的Activity作為殼,F(xiàn)ragment作為頁面填充展示,并且能夠像Activity那樣自由的切換和數(shù)據(jù)交互。
特征
- 支持assets下“corepage.json”靜態(tài)配置Fragment頁面信息肥缔。
- 支持Application中動態(tài)配置Fragment頁面信息。
- 支持通過注解@Page的方式動態(tài)自動配置頁面信息摹恰。
- 支持自定義Fragment頁面信息配置辫继。
- 支持4種默認Fragment頁面切換動畫。
- 支持Fragment頁面間參數(shù)傳遞俗慈。
- 支持Fragment頁面屬性保存姑宽。
- 支持Fragment頁面的onKeyDown、onFragmentResult等生命周期
- 支持Fragment和Fragment頁面自由跳轉(zhuǎn)以及數(shù)據(jù)交互闺阱。
- 支持導(dǎo)航欄通過注解的方式自動添加及設(shè)置炮车。
- 支持進行內(nèi)存泄露監(jiān)測。
- 支持自定義TitleBar全局主題屬性酣溃。
- 支持自定義Fragment頁面容器瘦穆。
- 支持自定義Activity頁面容器。
- 支持Fragment之間赊豌、activity和fragment之間的數(shù)據(jù)交互扛或。
- 兼容kotlin和androidx。
設(shè)計原由
當初做Android開發(fā)時每當我寫一個頁面碘饼,都需要創(chuàng)建一個Activity熙兔,并且還需要在manifest中注冊一堆Activity信息悲伶,這樣既不方便,而且對資源的開銷也比較大住涉。因此當時我就設(shè)想能否創(chuàng)造出一個通用萬能的Activity容器麸锉,可以全權(quán)負責(zé)Fragment的切換展示和數(shù)據(jù)交互,只需要一行代碼即可完成所有的操作舆声,還不需要自己手動去注冊花沉,可以一鍵生成。
設(shè)計思路
剛開始的時候真的很難媳握,沒有什么好的思路碱屁,最初只是簡單封裝了一個Activity,通過傳入一些key值從而獲取并加載對應(yīng)的fragment毙芜,類似ARouter
中Fragment發(fā)現(xiàn)那種忽媒。其實這樣做并沒有解決一個容器的問題,而且頁面切換也不是很靈活腋粥,不夠通用,使用起來也不是很方便架曹。
突然有一天我發(fā)現(xiàn)Github上有個開源項目CorePage寫得非常好隘冲,完美地解決了我對一個Activity容器的問題,于是我決定仔細研究其代碼绑雄,并在其基礎(chǔ)上設(shè)計出了XPage的最初版本展辞。
就在XPage正式投入使用的過程中,我發(fā)現(xiàn)還是存在不少問題的:
1.對外API不夠靈活万牺,使用起來不夠方便罗珍;
2.每個Fragment仍需要手動注冊,很麻煩脚粟;
對于API不夠靈活的問題覆旱,我在之后的版本中陸續(xù)通過構(gòu)造者模式設(shè)計以及Android主題屬性等手段解決了。
而對于手動注冊的問題核无,我正是借鑒了ARouter的思路扣唱,通過Android APT技術(shù),從而實現(xiàn)了Fragment信息的自動注冊团南。
解決痛點
只需要一個Activity容器就可以實現(xiàn)多個頁面的交互噪沙。
Fragment自由切換和數(shù)據(jù)交互。
無需在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中動態(tài)注冊【推薦】
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") //開啟調(diào)試
.setContainActivityClazz(XPageActivity.class) //設(shè)置默認的容器Activity
.enableWatcher(false) //設(shè)置是否開啟內(nèi)存泄露監(jiān)測
.init(this); //初始化頁面配置
【注意】:如果你的項目中只是增加了依賴,還沒有使用@Page注解XPageFragment頁面的話仲墨,在編譯時是不會自動生成注冊頁面的9寸浴!
2.手動動態(tài)進行頁面注冊
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") //開啟調(diào)試
.enableWatcher(false) //設(shè)置是否開啟內(nèi)存泄露監(jiān)測
.init(this); //初始化頁面配置
方法二:assets中靜態(tài)注冊
在assets文件夾中新建“corepage.json“目养,然后進行如下配置:
[
{
"name": "測試頁面1",
"classPath": "com.xuexiang.xpagedemo.fragment.TestFragment1",
"params": ""
},
{
"name": "測試頁面2",
"classPath": "com.xuexiang.xpagedemo.fragment.TestFragment2",
"params": {
"key1":"這是參數(shù)1的值",
"key2":"這是參數(shù)2的值"
}
}俩由,
]
混淆配置
# fastjson
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.fastjson.** { *; }
-keepattributes Signature
# xpage
-keep class com.xuexiang.xpage.annotation.** { *; }
基礎(chǔ)使用
頁面跳轉(zhuǎn)
使用XPage,Activity必須要繼承
XPageActivity
,Fragment必須要繼承XPageFragment
癌蚁,否則將無法調(diào)用頁面跳轉(zhuǎn)的openPage
方法幻梯。
頁面的簡單打開和關(guān)閉
- 使用
openPage
即可打開頁面,入?yún)⒖蔀樘D(zhuǎn)類的類名努释,也可以是類的標識(@Page標注的name字段)
// 使用類名打開
openPage(TestFragment.class);
// 使用標識打開
openPage("TestFragment");
- 使用
popToBack
即可關(guān)閉頁面碘梢。
// 關(guān)閉當前頁,返回上一頁
popToBack();
// 關(guān)閉當前頁并跳轉(zhuǎn)至某個頁面
popToBack("popBackName", null);
頁面打開等待結(jié)果返回
- 1.使用
openPageForResult
即可伐蒂,類似Activity里的startActivityForResult
煞躬。
openPageForResult(TestFragment.class, null, REQUEST_CODE);
- 2.使用
setFragmentResult
來設(shè)置頁面關(guān)閉的返回值,類似Activity里的setResult
方法逸邦。
// 設(shè)置返回的數(shù)據(jù)恩沛,類似Activity里的setResult
Intent intent = new Intent();
intent.putExtra(KEY_BACK_DATA, "==【返回的數(shù)據(jù)】==");
setFragmentResult(500, intent);
// 返回操作
popToBack();
- 3.重寫Fragment的
onFragmentResult
方法來接收返回的數(shù)據(jù),類似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));
}
}
數(shù)據(jù)傳遞
- 使用
openPage
打開頁面時雷客,可傳入Bundle作為參數(shù)。
// 設(shè)置需要傳遞的參數(shù)
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 + "攜帶的數(shù)據(jù)");
// 把參數(shù)傳入
openPage(DateReceiveFragment.class, params);
- 數(shù)據(jù)接收
數(shù)據(jù)接收和普通Fragment接收數(shù)據(jù)一樣烛卧,使用getArguments
獲取傳入的數(shù)據(jù)佛纫。
Bundle arguments = getArguments();
String eventName = arguments.getString(DateReceiveFragment.KEY_EVENT_NAME);
String eventData = arguments.getString(DateReceiveFragment.KEY_EVENT_DATA);
頁面轉(zhuǎn)場動畫
頁面轉(zhuǎn)場動畫可以動態(tài)設(shè)置,也可以靜態(tài)設(shè)置总放。
靜態(tài)設(shè)置
在我們使用@Page
進行頁面注冊的時候呈宇,我們可以靜態(tài)設(shè)置轉(zhuǎn)場動畫、默認參數(shù)局雄、拓展字段等甥啄。
Page注解的屬性表:
屬性名 | 類型 | 默認值 | 備注 |
---|---|---|---|
name | String | 注解類的類名 | 頁面的名稱、唯一標識符 |
params | String[] | {""} | 靜態(tài)設(shè)置默認參數(shù) |
anim | CoreAnim | CoreAnim.slide | 頁面轉(zhuǎn)場動畫 |
extra | int | -1 | 拓展字段 |
動態(tài)設(shè)置
使用openPage
打開頁面時炬搭,可傳入CoreAnim枚舉設(shè)置頁面轉(zhuǎn)場動畫蜈漓。
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;
}
設(shè)置自定義轉(zhuǎn)場動畫
//自定義動畫
openPage(TestFragment.PAGE_NAME, null, new int[]{
// OpenEnterAnimation, 頁面打開進場動畫
R.anim.custom_open_enter,
// OpenExitAnimation, 頁面打開退場動畫
R.anim.custom_open_exit,
// CloseEnterAnimation, 頁面關(guān)閉進場動畫
R.anim.custom_close_enter,
// CloseExitAnimation, 頁面關(guān)閉退場動畫
R.anim.custom_close_exit
});
進階使用
使用PageOption進行頁面操作【推薦】
使用PageOption.to
進行頁面選項構(gòu)建穆桂。
- setAnim: 設(shè)置頁面轉(zhuǎn)場動畫
- setRequestCode: 設(shè)置頁面打開的請求碼,用于返回結(jié)果
- setAddToBackStack: 設(shè)置是否加入堆棧
- setNewActivity: 設(shè)置是否使用新的Activity打開
- setContainActivityClazz:設(shè)置新打開Activity的容器
- putBoolean融虽、putString享完、putAll等:設(shè)置傳遞的參數(shù)
- open:打開頁面進行跳轉(zhuǎn)
PageOption.to(TestFragment.class) //跳轉(zhuǎn)的fragment
.setAnim(CoreAnim.zoom) //頁面轉(zhuǎn)場動畫
.setRequestCode(100) //請求碼,用于返回結(jié)果
.setAddToBackStack(true) //是否加入堆棧
.setNewActivity(true, ContainActivity.class) //是否使用新的Activity打開
.putBoolean(DateReceiveFragment.KEY_IS_NEED_BACK, true) //傳遞的參數(shù)
.open(this); //打開頁面進行跳轉(zhuǎn)
自定義TitleBar樣式
可以設(shè)置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>
<!--標題欄的背景圖片般又,優(yōu)先使用背景圖片,沒有背景圖片才使用背景顏色巍佑,可選-->
<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>
<!--標題欄兩側(cè)文字的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主頁當前容器的狀態(tài)糖耸,需要在打開新頁面的使用設(shè)置使用新容器。
PageOption.to(TestFragment.class)
//新建一個容器丘薛,以不影響當前容器
.setNewActivity(true)
.open(this);
復(fù)雜Activity界面容器的自定義
詳細可參見ComplexActivity
1.自定義頁面容器的布局,在布局中一定要包含idfragment_container
邦危。
<FrameLayout
android:id="@id/fragment_container"
android:layout_width="match_parent"
android:layout_height="400dp">
</FrameLayout>
2.在XPageActivity中設(shè)置頁面容器的布局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頁面中的數(shù)據(jù)陵且。
TabAFragment tabAFragment = getPage(TabAFragment.class);
if (tabAFragment != null) {
ToastUtils.toast(tabAFragment.getData());
} else {
ToastUtils.toast("頁面還未加載裁僧!");
}
常見問題
1.問:我使用的是自動注冊,為什么我剛接入的時候慕购,一直報錯找不到AppPageConfig
?
答:首先需要明確的是聊疲,AppPageConfig
是需要編譯之后才會出現(xiàn)的,如果你沒有編譯的話沪悲,是肯定沒有的获洲。如果你編譯了還是找不到,你可以根據(jù)如下步驟依次進行排查:
- 排查當前項目中是否有Fragment被
@Page
注解了殿如,如果沒有的話贡珊,即使編譯了也是不會生成AppPageConfig
文件的最爬。 - 排查是否進行了moduleName注冊,因為自動生成的注冊類是根據(jù)
"moduleName"+PageConfig
的規(guī)則進行自動生成的门岔,如果沒有配置moduleName的話爱致,默認才是app,這樣自動生成的注冊類才是AppPageConfig
寒随。如果你配置了moduleName糠悯,而且模塊的名稱也不是app,那么自動生成的注冊類肯定不是AppPageConfig
牢裳。 - 查看編譯時是否有其他報錯逢防,如果在編譯的過程中就報錯了,那么作為apt這種編譯時自動生成的注冊類也是無法生成的蒲讯。
- 如果以上都沒能解決你的問題忘朝,那么考慮八成是你哪里集成出錯了,所以需要你回頭重新閱讀集成指南判帮,不能放過每一個細節(jié)局嘁。如果還是不行,考慮直接使用簡化版的Android空殼模版工程 先熟悉一下集成和使用晦墙。