项目地址: https://github.com/liu230503/Dokodemo-Door.git
本框架是以AOP思想实现的一款Android端用于管理Fragment
栈的框架。无需继承任何基类即可使用。
annotation-v7 | dokodemoDoor-v7 | annotation-x | dokodemoDoor-x |
---|---|---|---|
1.2.0 | 1.2.0 | 1.2.0 | 1.2.0 |
- 使
Fragment
的入栈出栈、显示隐藏变得更容易. - 解决
ViewPager
嵌套Fragment
时造成的初始化慢问题,本框架支持懒加载. - 解决
Fragment
嵌套Fragment
时栈管理困难问题. - 让
Fragment
使用起来更加简单.
- Dokomedo Door 支持
Support-V7
与AndroidX
包下的Fragment
与FragmentActivity
.但不支持android.app
包下的Fragment
. - 本框架是基于HuJian适配的可用于
Android
平台的AspectJX
开发,在使用时如想缩短编译时间可参考 AspectJX-HuJian 进行配置.
- 在工程的
build.gradle
文件中添加
buildscript {
repositories {
maven { url "https://jitpack.io" }
}
dependencies {
...
classpath 'org.aspectj:aspectjtools:1.8.13'
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.1'
}
allprojects {
maven { url "https://jitpack.io" }
}
}
- 在项目
app
的build.gradle
文件中添加
apply plugin: 'android-aspectjx'
android {
...
}
- 在需要支持的
Module
的build.gradle
文件中添加
dependencies {
implementation 'com.github.liu230503:dokodemoDoor-annotation-v7:TAG'
implementation 'com.github.liu230503:dokodemoDoor-v7:TAG'
...
}
@Node
用于标记一个可被管理的节点,该注解可被继承,只可作用于类上且只支持FragmentActivity
与Fragment
.
containerViewId
可选参数.此处需要传入
Fragment
在添加时候占用的容器的id
,作为Fragment
在添加的时候代替的位置.如果是小于等于0,Fragment
将不会代替任何位置.默认值是0.
stickyStack
可选参数如果值为
true
: 当被操纵的节点(Activity
/Fragment
)中的栈底Fragment
和栈顶Fragment
是同一个元素并且该Fragment
被关闭的时候,这个宿主类也会被关闭,并且栈顶的Fragment
不会执行退出转场动画. 如果值为false
: 当寄主类内部的最后一个Fragment
出栈的时候,宿主类不会做任何动作.并且所有的转场动画都是正常执行的。
@Node
public abstract class BaseFragmentActivity extends FragmentActivity {
...
}
@Node(containerViewId = R.id.contentFrame)
public class MainActivity extends FragmentActivity {
...
}
@Node
public abstract class BaseFragment extends Fragment {
...
}
@Node(containerViewId = R.id.contentFrame,stickyStack = true)
public class MainFragment extends Fragment {
...
}
-
@Animation
用于标记一个添加了@Node
注解的节点,该注解可被继承.enterAnim
入场的动画,主要是对于新加进来的Fragment
执行动画.exitAnim
出场动画,主要是对于关闭Fragment
执行动画。需要注意的是当调用replace
的时候,前一个Fragment
也会执行退出动画.popEnterAnim
当调用addToBackStack()
方法后,当前的Fragment
退出返回上一个Fragment
,上一个Fragment
就是popEnter
,这时候它就会执行这个动画。这个动画对于新创建的Fragment
无效.popExitAnim
当前Fragment
在back stack
中,并且出栈的时候会执行popExit
动画.
@Animator(enter = R.anim.push_left_in_no_alpha, exit = R.anim.push_right_out_no_alpha,
popEnter = R.anim.push_right_in_no_alpha, popExit = R.anim.push_left_out_no_alpha)
@Node
public abstract class BaseFragment extends Fragment{
...
}
-
LoadModel
用于标记一个添加了@Node
注解的节点,该注解不可被继承。loadModel
必须使用EnumLoadModel
中定义的元素.EnumLoadModel.NORMAL_LOAD
表示普通加载.EnumLoadModel.LAZY_LOAD
表示懒加载.@LoadModel(loadModel = EnumLoadModel.LAZY_LOAD) public class ItemFragment extends BaseFragment { ... }
1. add 方法:
> `addFragment(@IdRes int containerViewId, Fragment... fragments)`用于在一个容器中同一添加`Fragment`
Fragment fragments[] = new Fragment[4];
DokodemoDoor.getNodeProxy(this).addFragment(R.id.contentFrame, fragments);
- show 方法:
showFragment(@NonNull Fragment fragment, @IdRes int containerViewId)
用于对指定容器中的一个Fragment
进行显示,同时隐藏该容器中其他Fragment
.如果容器中不包含当前Fragment
则会先调用add
方法进行添加.
DokodemoDoor.getNodeProxy(this).showFragment(new TestFragment(), R.id.contentFrame);
void showFragment(@NonNull Fragment fragment, @IdRes int containerViewId, boolean showRepeatAnim)
用于对指定容器中的一个Fragment
进行显示,同时隐藏该容器中其他Fragment
.如果容器中不包含当前Fragment
则会先调用add
方法进行添加.showRepeatAnim
如果为true
,则如果当前要显示的Fragment
已经显示,则触发该Fragment
的动画。
DokodemoDoor.getNodeProxy(this).showFragment(new TestFragment(),R.id.contentFrame, true);
void showFragment(@NonNull String tag)
显示一个已经被添加的Fragment
同时隐藏其所属宿主容器中包含的其他Fragment
,如果该Fragment
已经显示,则什么也不做。如果该Tag对应的Fragment
还未添加会抛出一个NotExistException
.
Fragment fragments[] = new Fragment[4];
DokodemoDoor.getNodeProxy(this).addFragment(R.id.contentFrame, fragments);
DokodemoDoor.getNodeProxy(this).show(DokodemoDoor.getNodeProxy(fragments[0]).getFragmentTag());
void showFragment(@NonNull String tag, boolean showRepeatAnim)
同上.
- hide 方法:
hide
方法和show
方法对应,用法也比较类似,不同的是,hide
的fragment
必须已经存在.
void hideFragment(@NonNull Fragment fragment)
DokodemoDoor.getNodeProxy(this).hideFragment(fragments[0]);
void hideFragment(@NonNull String tag)
DokodemoDoor.getNodeProxy(this).hideFragment(DokodemoDoor.getNodeProxy(fragments[0]).getFragmentTag());
- start 方法:
void startFragment(@NonNull Fragment fragment)
方法是对add
与show
方法的整合。其作用为添加并显示一个Fragment
,同时隐藏起宿主容器内的其他Fragment
.需要注意的是如果调用
startFragment
的节点在@Noed
注解中提供的containerViewId
大于0,那么start
的Fragment
将被添加到此类的栈中。如果提供的containerViewId
小于等于0,那么将会添加至当前节点所处容器的持有者的栈中.
@Node(containerViewId = R.id.contentFrame)
public class MainActivity extends FragmentActivity {
DokodemoDoor.getNodeProxy(this).startFragment(new TestFragment1());
}
@Node()
public class TestFragment1 extends Fragment {
DokodemoDoor.getNodeProxy(this).startFragment(new TestFragment2());
}
@Node()
public class TestFragment2 extends Fragment {
...
}
此时
TestFragment1
与TestFragment2
都在MainActivity
所持有的栈中,并且所使用的容器都是R.id.contentFrame
@Node(containerViewId = R.id.contentFrame1)
public class MainActivity extends FragmentActivity {
DokodemoDoor.getNodeProxy(this).startFragment(new TestFragment1());
}
@Node(containerViewId = R.id.contentFrame2)
public class TestFragment1 extends Fragment {
DokodemoDoor.getNodeProxy(this).startFragment(new TestFragment2());
}
@Node()
public class TestFragment2 extends Fragment {
...
}
此时
TestFragment1
在MainActivity
所持有的栈中,并且所使用的容器是R.id.contentFrame1
TestFragment2
在TestFragment1
所持有的栈中,并且所使用的容器是R.id.contentFrame2
-
startForResult 方法:
void startFragmentForResult(Object receive, @NonNull Fragment fragment, int requestCode)
效果同
startFragment
,但可以将一个requestCode
传给将要显示的Fragment
,将要显示的Fragment
可以在退栈时调用setResult(int resultCode, Bundle bundle)
函数将数据返回给上一页面.类似于Activity
的startActivityForResult
函数
@Node()
public class TestFragment1 extends Fragment {
DokodemoDoor.getNodeProxy(this).startFragmentForResult(this,new TestFragment2(),0x1001);
// 用于接收回调数据的函数
public void onFragmentResult(int requestCode, int resultCode, Bundle args) {
}
}
@Node()
public class TestFragment2 extends Fragment {
Bundle bundle = new Bundle();
DokodemoDoor.getNodeProxy(this).setResult(DokodemoDoor.FRAGMENT_RESULT_OK, bundle);
DokodemoDoor.getNodeProxy(this).close();
}
-
replace 方法:
需要注意
replace
方法实际上是add
和show
方法的合并,可以保证在使用的时候,一个容器中只存在一个Fragment
.void replaceFragment(@NonNull Fragment fragment)
显示一个Fragment
并移除宿主容器中其他Fragment
.如果Fragment
已经显示了什么也不会做.该方法添加的Fragment
不会入栈,所以调用onBackPressed
时该Fragment
没有任何操作.void replaceFragment(@NonNull Fragment fragment, @IdRes int containerViewId)
同上,可以指定一个容器.
@Node(containerViewId = R.id.contentFrame)
public class MainActivity extends FragmentActivity {
DokodemoDoor.getNodeProxy(this).replaceFragment(new TestFragment1());
}
@Node
public class TestFragment1 extends Fragment {
DokodemoDoor.getNodeProxy(this).replaceFragment(new TestFragment2(),DokodemoDoor.getNodeProxy(DokodemoDoor.getNodeProxy(this).getHost()).getContainerViewId());
}
-
close 方法:
本框架默认是监听
onBackPressed
函数来处理Fragment
退栈。调用close
函数也可以实现退栈. 退栈默认的操作是移除当前的栈顶节点,并对宿主的下一个栈顶节点进行显示.当持有容器的节点所提供的
stickyStack = true
,退栈时,当栈内节点为空或者只剩一个,宿主会跟着出栈或者finish
,此时最后一个栈顶节点不执行转场动画. 当持有容器的节点所提供的stickyStack = false
,退栈时,只有在栈内节点为空,宿主自己才会出栈或者finish
,所有的栈内节点都会执行转场动画.
@Node
public class TestFragment1 extends Fragment {
DokodemoDoor.getNodeProxy(this).close();
}
@Node(containerViewId = R.id.contentFrame)
public class MainActivity extends FragmentActivity {
DokodemoDoor.getNodeProxy(this).close(fragments[0]);
}
添加了@Node
注解的Fragment
在构造函数执行时,Dokodemo Door会为期分配一个默认的Tag,如果想要自定义Tag 本框架提供了两种方式.
void setFragmentTag(@NonNull String tag)
此方法需要在
Fragment
创建完成之后,并且在使用Dokodemo Door操纵Fragment
之前才会生效.
@Node(containerViewId = R.id.contentFrame)
public class MainActivity extends FragmentActivity {
TestFragment1 fragment = new TestFragment1();
DokodemoDoor.getNodeProxy(fragment).setFragmentTag("123");
DokodemoDoor.getNodeProxy(this).addFragment(R.id.contentFrame,fragment);
}
public String getFragmentTag()
需要在自定义Tag的
Fragment
中添加此函数,并返回一个不为空的String
对象。此方法优先级低于方法1.
@Node
public class TestFragment1 extends Fragment {
public String getFragmentTag(){
return "123";
}
}
在使用ViewPager
+Fragment
时会出现同时将多个Fragment
同时初始化导致页面加载时间过长,对用户不友好。部分数据只有在显示时才需要加载。本框架提供的懒加载模式即可解决这一问题.
- 在需要使用懒加载的
Fragment
中添加@LoadModel
注解,并赋值loadModel = EnumLoadModel.LAZY_LOAD
@LoadModel(loadModel = EnumLoadModel.LAZY_LOAD)
public class TestFragment extends Fragment {
...
}
- 在需要实现懒加载的
Fragment
中添加public void onLazyLoadViewCreated(Bundle savedInstanceState)
函数.
@LoadModel(loadModel = EnumLoadModel.LAZY_LOAD)
public class TestFragment extends Fragment {
public void onLazyLoadViewCreated(Bundle savedInstanceState) {
// TODO 该函数只会在页面首次显示时被调用,可以在此处处理要显示的内容
}
}
本框架对
Fragment
的出栈操作是通过onBackPressed
方法,但是在某些时候,我们需要拦截这个方法并进行一些额外的处理,本框架对这种场景提供了支持.
boolean onInterruptBackPressed()
此方法用于标识是否拦截继续向子节点传递onInterruptBackPressed()
方法.
@Node
public class TestFragment1 extends Fragment {
public Boolean onInterruptBackPressed() {
// 需要拦截,则返回true,否则返回false
return false;
}
}
boolean onNodeBackPressed()
此方法用于拦截退栈事件,返回值标识了是否继续向上层传递onNodeBackPressed()
方法.
@Node
public class TestFragment1 extends Fragment {
public Boolean onNodeBackPressed() {
// 需要拦截,则返回true,否则返回false
return false;
}
}
在
application
的Module
中,我们可以使用注解来声明一些变量,但是在library
的Module
中,R
中的数据并不是最终常量,所以本框架提供的一些注解就不可以传入值了。为了解决此问题,本框架提供了Api
来扩展注解.
@Node
中的containerViewId
可以通过在类中添加public Integer getContainerViewId()
函数来实现相同效果.
@Node
public class MainActivity extends FragmentActivity {
public Integer getContainerViewId(){
return R.id.contentFrame;
}
}
@Animation
中的参数可以通过在节点中添加public int[] getNodeAnimations()
函数来实现相同效果.
@Node
public class MainActivity extends FragmentActivity {
public int[] getNodeAnimations(){
// 此函数返回值必须为一个不为null 的整形数组,且数组长度不得小于4,如不需要对应动画,请传0.
return new int[]{ R.anim.enter,R.anim.exit,R.anim.popEnter,R.anim.popExit};
}
}
需要用到代码混淆时,请在混淆规则文件中添加以下规则
keep class org.alee.dokodemo.door.core.DokodemoDoor.** {*;}
-keep interface org.alee.dokodemo.door.core.IProxy.** {*;}
-keep public class * extends android.app.Activity
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends androidx.app.Activity
-keep public class * extends androidx.app.Fragment
-keepclassmembers class * extends android.app.Activity {
public Integer getContainerViewId();
public Boolean onNodeBackPressed();
public void onFragmentResult(int,int,android.os.Bundle);
public Boolean onInterceptBackPressed();
public void onLazyLoadViewCreated(android.os.Bundle);
public int[] getNodeAnimations();
public String getFragmentTag();
}
-keepclassmembers class * extends android.support.v4.app.Fragment {
public Integer getContainerViewId();
public Boolean onNodeBackPressed();
public void onFragmentResult(int,int,android.os.Bundle);
public Boolean onInterceptBackPressed();
public void onLazyLoadViewCreated(android.os.Bundle);
public int[] getNodeAnimations();
public String getFragmentTag();
}
-keepclassmembers class * extends androidx.app.Activity {
public Integer getContainerViewId();
public Boolean onNodeBackPressed();
public void onFragmentResult(int,int,android.os.Bundle);
public Boolean onInterceptBackPressed();
public void onLazyLoadViewCreated(android.os.Bundle);
public int[] getNodeAnimations();
public String getFragmentTag();
}
-keepclassmembers class * extends androidx.app.Fragment {
public Integer getContainerViewId();
public Boolean onNodeBackPressed();
public void onFragmentResult(int,int,android.os.Bundle);
public Boolean onInterceptBackPressed();
public void onLazyLoadViewCreated(android.os.Bundle);
public int[] getNodeAnimations();
public String getFragmentTag();
}
- 遇到
Gradle
编译不过的问题可以尝试clean Build
后再试. - 遇到其他未知问题请联系作者 l15040565660@gmail.com .