Skip to content

一款基于AOP思想实现的Android Fragment栈管理库,无侵入,无需继承任何类即可使用

Notifications You must be signed in to change notification settings

liu230503/Dokodemo-Door

Repository files navigation

Dokomedo Door 使用手册

项目地址: 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

解决痛点

  1. 使Fragment 的入栈出栈、显示隐藏变得更容易.
  2. 解决ViewPager嵌套Fragment时造成的初始化慢问题,本框架支持懒加载.
  3. 解决Fragment 嵌套Fragment 时栈管理困难问题.
  4. Fragment 使用起来更加简单.

使用

1.使用须知

  1. Dokomedo Door 支持Support-V7AndroidX 包下的Fragment FragmentActivity.但不支持android.app包下的Fragment.
  2. 本框架是基于HuJian适配的可用于Android平台的AspectJX开发,在使用时如想缩短编译时间可参考 AspectJX-HuJian 进行配置.

2.添加依赖

  1. 在工程的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" }
    }
}
  1. 在项目appbuild.gradle文件中添加
apply plugin: 'android-aspectjx'
android {
	...
}
  1. 在需要支持的Modulebuild.gradle文件中添加
dependencies {
    implementation 'com.github.liu230503:dokodemoDoor-annotation-v7:TAG'
    implementation 'com.github.liu230503:dokodemoDoor-v7:TAG'
    ...
}

3.Dokomedo Door的注解

  1. @Node 用于标记一个可被管理的节点,该注解可被继承,只可作用于类上且只支持FragmentActivityFragment.

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 {
    ...
}
  1. @Animation 用于标记一个添加了@Node注解的节点,该注解可被继承.

    enterAnim 入场的动画,主要是对于新加进来的Fragment执行动画. exitAnim 出场动画,主要是对于关闭Fragment执行动画。需要注意的是当调用replace的时候,前一个Fragment也会执行退出动画. popEnterAnim 当调用addToBackStack()方法后,当前的Fragment退出返回上一个Fragment,上一个Fragment就是popEnter,这时候它就会执行这个动画。这个动画对于新创建的Fragment无效. popExitAnim 当前Fragmentback 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{
	...
}
  1. LoadModel 用于标记一个添加了@Node注解的节点,该注解不可被继承。

    loadModel 必须使用EnumLoadModel中定义的元素.

    EnumLoadModel.NORMAL_LOAD 表示普通加载. EnumLoadModel.LAZY_LOAD 表示懒加载.

    @LoadModel(loadModel = EnumLoadModel.LAZY_LOAD)
    public class ItemFragment extends BaseFragment {
        ...
    }

4.管理Fragment

1. add 方法:

> `addFragment(@IdRes int containerViewId, Fragment... fragments)`用于在一个容器中同一添加`Fragment`
Fragment fragments[] = new Fragment[4];
DokodemoDoor.getNodeProxy(this).addFragment(R.id.contentFrame, fragments);
  1. 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) 同上.

  1. hide 方法:

hide方法和show方法对应,用法也比较类似,不同的是,hidefragment必须已经存在.

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());
  1. start 方法:

void startFragment(@NonNull Fragment fragment) 方法是对addshow方法的整合。其作用为添加并显示一个Fragment,同时隐藏起宿主容器内的其他Fragment.

需要注意的是如果调用startFragment的节点在@Noed注解中提供的containerViewId大于0,那么startFragment将被添加到此类的栈中。如果提供的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 {
    ...
}

此时TestFragment1TestFragment2都在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 {
    ...
}

此时TestFragment1MainActivity 所持有的栈中,并且所使用的容器是 R.id.contentFrame1 TestFragment2 TestFragment1 所持有的栈中,并且所使用的容器是 R.id.contentFrame2

  1. startForResult 方法:

    void startFragmentForResult(Object receive, @NonNull Fragment fragment, int requestCode)

    效果同startFragment,但可以将一个requestCode传给将要显示的Fragment,将要显示的Fragment可以在退栈时调用setResult(int resultCode, Bundle bundle)函数将数据返回给上一页面.类似于ActivitystartActivityForResult函数

@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();
}
  1. replace 方法:

    需要注意replace方法实际上是addshow方法的合并,可以保证在使用的时候,一个容器中只存在一个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());
}
  1. 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]);
}

5.Fragment Tag

添加了@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";
     }
}

5.LoadModel

​ 在使用ViewPager+Fragment 时会出现同时将多个Fragment同时初始化导致页面加载时间过长,对用户不友好。部分数据只有在显示时才需要加载。本框架提供的懒加载模式即可解决这一问题.

  1. 在需要使用懒加载的Fragment中添加@LoadModel注解,并赋值loadModel = EnumLoadModel.LAZY_LOAD
@LoadModel(loadModel = EnumLoadModel.LAZY_LOAD)
public class TestFragment extends Fragment {
    ...
}
  1. 在需要实现懒加载Fragment中添加 public void onLazyLoadViewCreated(Bundle savedInstanceState)函数.
@LoadModel(loadModel = EnumLoadModel.LAZY_LOAD)
public class TestFragment extends Fragment {
 
    public void onLazyLoadViewCreated(Bundle savedInstanceState) {
       // TODO 该函数只会在页面首次显示时被调用,可以在此处处理要显示的内容
    }
}

6.Fragment 退栈拦截

本框架对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;
    }
}

7.注解扩展

applicationModule中,我们可以使用注解来声明一些变量,但是在libraryModule中,R中的数据并不是最终常量,所以本框架提供的一些注解就不可以传入值了。为了解决此问题,本框架提供了Api来扩展注解.

  1. @Node 中的 containerViewId 可以通过在类中添加 public Integer getContainerViewId() 函数来实现相同效果.
@Node
public class MainActivity extends FragmentActivity {
	public Integer getContainerViewId(){
        return R.id.contentFrame;
    }
}
  1. @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};
	}
}

8.代码混淆注意事项

需要用到代码混淆时,请在混淆规则文件中添加以下规则

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();
}

9.其他问题

  1. 遇到Gradle编译不过的问题可以尝试clean Build 后再试.
  2. 遇到其他未知问题请联系作者 l15040565660@gmail.com .

About

一款基于AOP思想实现的Android Fragment栈管理库,无侵入,无需继承任何类即可使用

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages