Android中全局管理Fragment的FragmentFactory

这篇文章专门用于讲述LevineUtils中的FragmentFactory,需要集成LevineUtils

1.FragmentFactory简介

  • FragmentFactory是利用apt技术,即通过注解的方式来管理整个应用中的自定义的Fragment,通过FragmentFactory对象的showFragment(String tag)方法来控制fragment的显示和隐藏,从而实现了fragment的切换.
  • FragmentFactory同时也对fragment的重影问题给出了解决方案,通过使用<font color=green>saveCurrentFragmentInfo(Bundle bundle)</font>和<font color=green>restoreCurrentFragmentInfo(Bundle bundle)</font>方法保存状态和恢复状态.

2.使用FragmentFactory

初始化FragmentFactory

在activity中的onCreate方法中初始化FragmentFactory对象,但是需要注意的是需要继承自FragmentActicity 或者AppCompatActicity,因为在FragmentFactory中使用的是getSupportFragmentManager,所以你的activity必须继承自FragmentActivity或

AppCompatActivity:

public class MainActivity extends AppCompatActivity {
    private FragmentFactory mFactory;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         //使用单例模式创建`FragmentFactory`对象,R.id.mContentnF1是要显示的Fragment的布局容器的id
         mFactory = FragmentFactory.getInstance()
                .init(this, R.id.mContentFl);
        ....
    }
     @Override
    protected void onResume() {
        super.onResume();
        //很关键,在activity切换时起作用,如果APP使用单activity,则不需要这句代码
        mFactory.onResume(this,R.id.mContentFl);
    }
}

自定义Fragment

例如我的主界面是这样的布局,里面含有两个tab,用于两个Fragment的切换:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">
    <FrameLayout
        android:id="@+id/mContentFl"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="0.9" />
    <com.google.android.material.tabs.TabLayout
        android:layout_gravity="bottom"
        android:id="@+id/mTabLayout"
        android:layout_weight="0.1"
        android:layout_width="match_parent"
        android:layout_height="0dp">
        <com.google.android.material.tabs.TabItem
            android:id="@+id/tab1"
            android:text="page1"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent"/>
        <com.google.android.material.tabs.TabItem
            android:id="@+id/tab2"
            android:text="page2"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent"/>
    </com.google.android.material.tabs.TabLayout>
</LinearLayout>

自定义两个Fragment用于tab点击时,切换使用(此时要在Fragment的外部使用@TargetFragmentTag(String tag)注解,里面需要接收一个string类型的tag,用于和当前的fragment 一 一对应):

@TargetFragmentTag("fragment1")
public class Fragment1 extends Fragment {
    .....
}

@TargetFragmentTag("fragment2")
public class Fragment2 extends Fragment {
    .....
}

注: 相当于Fragment1的tag就是“ <font color=green face="consolas">fragment1</font>”,Fragment2的tag就是“ <font color=green face="consolas">fragment2</font>”,但是鉴于方便管理整个应用中所有Fragment的标签,所以建议创建一个类来存贮所有fragment的标签,比如,建立一个接口类FragmentTag:

public interface FragmentTag {
 String FRAGMENT1="fragment1";
 String FRAGMENT2="fragment2";
 
}

那么我们的代码就可以这样写:

@TargetFragmentTag(FragmentTag.FRAGMENT1)
public class Fragment1 extends Fragment {
 .....
}

@TargetFragmentTag(FragmentTag.FRAGMENT2)
public class Fragment2 extends Fragment {
 .....
}

然后,这样就能统一调度fragment了。

使用FragmentFactory来显示指定的Fragment

在activity中调用showFragment(String tag)来显示fragment:

public class MainActivity extends AppCompatActivity {
    private FragmentFactory mFactory;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         //使用单例模式创建`FragmentFactory`对象,R.id.mContentnF1是要显示的Fragment的布局容器的id
         mFactory = FragmentFactory.getInstance()
                .init(this, R.id.mContentFl);
        //默认显示fragment1
         mFactory.showFragment(FragmentTag.FRAGMENT1)
        ....
    }
}

另外在tab切换时,同时切换Fragment的显示:

 @Override
    public void onTabSelected(TabLayout.Tab tab) {
        switch (tab.getPosition()) {
            case 0:
                mFactory.showFragment(FragmentTag.FRAGMENT1);
                break;
            case 1:
                mFactory.showFragment(FragmentTag.FRAGMENT2);
                break;
        }
    }

Fragment在切换时,会自动将当前的Fragment隐藏,但并非销毁掉,下次重新切换回来时,会再将这个Fragment显示出来.其实FragmetnFactory还能实现回退栈的功能:回退到上一个Fragment,同样能使用showFragment方法,只要传入上一个Fragment的tag即可.

跨Activity使用FragmentFactory

在APP中可能有多个Activity,每个Activity中有多个Fragment,所以经常会在全局中使用FragmentFactory(跨Activity),那也是可以的,因为FragmentFactory在初始化时,传入了一个Activity的对象,所以在跨Activity使用时,只需要重新初始化一下即可,各个Activity中使用FragmentFactory是相互独立的.

例如:在另一个Activity中有一个有Fragment3和Fragment4,同样地:

@TargetFragmentTag(FragmentTag.FRAGMENT3)
public class Fragment3 extends Fragment {
    .....
}

@TargetFragmentTag(FragmentTag.FRAGMENT4)
public class Fragment4 extends Fragment {
    .....
}

public class AnotherActivity extends AppCompatActivity {
    private FragmentFactory mFactory;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_another);
         //使用单例模式创建`FragmentFactory`对象,R.id.mContentnF2是要显示的Fragment的布局容器的id
         mFactory = FragmentFactory.getInstance()
                .init(this, R.id.mContentF2);
        //默认显示fragment3
         mFactory.showFragment(FragmentTag.FRAGMENT3)
        ....
    }
    
     @Override
    public void onTabSelected(TabLayout.Tab tab) {
        switch (tab.getPosition()) {
            case 0:
                mFactory.showFragment(FragmentTag.FRAGMENT3);
                break;
            case 1:
                mFactory.showFragment(FragmentTag.FRAGMENT4);
                break;
        }
    }
}

3.延伸

Fragment确实能减少Activity的数量,使用灵活方便,但是也会引发一些问题,例如Fragment重影问题.重影问题主要是由于Activity会保存当前视图层级引起的,在某些事件发生后,Activity会调用onSaveInstance方法进行保存状态,在重新打开activity后,Activity会调用onRestoreInstance方法恢复之前保存的状态,再切换到另外的Fragment,就会出现重影.

关于onSaveInstance和onRestoreInstance方法的时机大致是:当activity可能会被回收时,调用onSaveInstance,当activity被回收后,又重新创建一个实例后,会执行onRestoreInstance方法

重影问题主要有几种方式引发:

  • 横竖屏切换

  • 当任务在后台被回收,重新打开页面时

  • 在开发者模式下设置 不保存活动

第一个问题解决方案:

在AndroidManifest.xml中的Activity中添加android:configChanges="keyboardHidden|orientation|screenSize",则可以让Activity在横竖屏切换时,不调用这些生命周期的方法即可.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.levine.ucall">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true">
       
        <activity
            android:name=".ui.activity.MainActivity"
            android:configChanges="keyboardHidden|orientation|screenSize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

另外,其他情况下的解决方案,在FragmentFactory中也给出了解决方案:

  • 1.首先重写Activity的OnSaveInstance和OnRestoreInstance方法

     @SuppressLint("MissingSuperCall")
     @Override
     protected void onSaveInstanceState(@NonNull Bundle outState) {
          //去掉super.onSaveInstanceState(outState),这是重影问题的根源
            mFactory.saveCurrentFragmentInfo(outState);
        }
     @Override
     protected void onRestoreInstanceState(Bundle savedInstanceState) {
            if (savedInstanceState != null) {
                mFactory.restoreCurrentFragmentInfo(savedInstanceState);
            }
        }
    
  • 2.在onCreate中也调用restoreCurrentFragmentInfo方法

      private FragmentFactory mFactory;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.activity_another);
             //使用单例模式创建`FragmentFactory`对象,R.id.mContentnF2是要显示的Fragment的布局容器的id
             mFactory = FragmentFactory.getInstance()
                    .init(this, R.id.mContentF2);
            
            if (savedInstanceState != null) {
                mFactory.restoreCurrentFragmentInfo(savedInstanceState);
            } else {
               //默认显示fragment1
              mFactory.showFragment(FragmentTag.FRAGMENT1)
            }
            ....
        }
    
    

这样就解决了重影问题。

补充:虽然重影问题解决了,但是如果想保存Fragment中某些控件状态该怎么办呢?

在Fragment重写onSaveInstanceState和onViewStateRestored方法即可,比如我的Fragment中有一个两个Tab,需要保存tab的位置:

@Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        // 保存fragment的状态
        super.onSaveInstanceState(outState);
        outState.putInt("tabPosition",tabLayout.getSelectedTabPosition());
    }

    @Override
    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
        super.onViewStateRestored(savedInstanceState);
        if(savedInstanceState!=null){
            //恢复fragment的状态
            int position=savedInstanceState.getInt("tabPosition");
            tabLayout.getTabAt(position).select();
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容