FragmentTransaction 隐藏/显示有时不起作用

Posted

技术标签:

【中文标题】FragmentTransaction 隐藏/显示有时不起作用【英文标题】:FragmentTransaction hide/show doesn't work sometimes 【发布时间】:2017-08-05 08:33:44 【问题描述】:

我有一个带有底部导航选项卡的活动,这些选项卡正在更改其中的片段。当我在这些选项卡上来回单击时,它有时会停止工作。当我在其中放入一些日志时,代码执行得很好。但是片段没有被切换。

代码在 kotlin 中,但相当简单

fun showTabFragment(tag: String) 
        val currentFragment: Fragment? = supportFragmentManager.fragments?.lastOrNull()
        var fragment = supportFragmentManager.findFragmentByTag(tag)
        val fragmentExists = fragment != null
        if (fragment == null) 
            when (tag) 
                TAG_LOGBOOK -> fragment = LogbookFragment()
                TAG_RECIPES -> fragment = RecipesFragment()
                TAG_PROFILE -> fragment = ProfileFragment()
                else -> fragment = MeetingPlacesFragment()
            
        

        val transaction = supportFragmentManager.beginTransaction()

        if (currentFragment != null) 
            Log.i("jacek", "hiding " + currentFragment.javaClass.simpleName)
            transaction.hide(currentFragment)
        

        if (fragmentExists) 
            Log.i("jacek", "showing " + fragment.javaClass.simpleName)
            transaction.show(fragment)
         else 
            Log.i("jacek", "adding " + fragment.javaClass.simpleName)
            transaction.add(R.id.container, fragment, tag)
        

        transaction.commit()
    

碎片很重。我会尝试一些轻量级的,但在我看来这仍然不是问题。还有什么我可以尝试的吗?

我正在使用最新的支持库 - 25.2.0 另外我对替换片段不感兴趣,因为重点是添加交叉淡入淡出动画而不重新创建它们

【问题讨论】:

当你在同一个事务的同一个片段上调用hide()然后show()时可能有问题?就像currentFragment 指代与fragment 相同的片段时,这里肯定会发生这种情况。说到它,我认为声称supportFragmentManager.fragments 列表中的最后一个片段将是最后显示的片段(而不是最近添加的片段)是错误的。您应该遍历所有 fragments 并搜索 isVisible() 为 true 的一个,或者只存储最后显示的片段标签并稍后找到它。 【参考方案1】:

您需要重复使用您想要隐藏或显示的片段的同一实例。

private fun replaceFragment(fragment: Fragment) 
    supportFragmentManager.beginTransaction().apply 
        if (fragment.isAdded) 
            show(fragment)
         else 
            add(R.id.fmFragmentContainer, fragment)
        

        supportFragmentManager.fragments.forEach 
            if (it != fragment && it.isAdded) 
                hide(it)
            
        
    .commit()

【讨论】:

【参考方案2】:

@Ali 的回答很好,但想象一下如果你有 5 个片段。这是显示/隐藏片段的另一种方式:

    // in BaseFragment
    public abstract String getTAG();

    //in FragmentA, FragmentB and FragmentC
    public String getTAG()
        return TAG;
    

    //Activity containing the fragments
    //android.support.v4.app.Fragment;    
    private FragmentA fragmentA; //inherited BaseFragment
    private FragmentB fragmentB; //inherited BaseFragment
    private FragmentC fragmentC; //inherited BaseFragment
    private ConcurrentHashMap<String,BaseFragment> mapOfAddedFragments = new ConcurrentHashMap<>();


    /**
     * Displays fragment A
     */
    private void displayFragmentA() 
        displayFragment(fragmentA)
    

    /**
     * Displays  fragment B
     */
    private void displayFragmentB() 
       displayFragment(fragmentB)
    

    /**
     * Displays  fragment C
     */
    private void displayFragmentC() 
        displayFragment(fragmentC)
    


     /**
     * Loads a fragment using show a fragment
     * @param fragment
     */
    private void displayFragment(BaseFragment fragment)
        if(!mapOfAddedFragments.containsKey(fragment.getTAG()))
            mapOfAddedFragments.put(fragment.getTAG(), fragment);

        showFragment(fragment.getTAG(), R.id.containerBody);
    

    /**
     * Displays a fragment and hides all the other ones
     * @param fragmentTag is the tag of the fragment we want to display
     */
    private void showFragment(String fragmentTag, @IdRes int containerViewId)
        FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction();
        BaseFragment fragment = null;

        fragment = mapOfAddedFragments.get(fragmentTag);
        if(fragment != null) 
            if (fragment.isAdded())
                ft.show(fragment);
            else  //fragment needs to be added to the frame container
                ft.add(containerViewId, fragment, fragment.getTAG());
            
        
        else //the chosen fragment doesn't exist
            return;

        //we hide the other fragments
        for (ConcurrentHashMap.Entry<String, BaseFragment> entry : mapOfAddedFragments.entrySet())
            if(!entry.getKey().equals(fragmentTag))
                BaseFragment fragmentTemp = entry.getValue();
                // Hide the other fragments
                if(fragmentTemp != null)
                    if(fragmentTemp.isAdded())
                        ft.hide(fragmentTemp);
            
        

        //commit changes
        ft.commit();
    

要实例化它们,您可以在活动的 onCreate() 方法中执行此操作:

//don't forget to get the .TAG elsewhere before using them here
    //never call them directly
    private void instantiateFragments(Bundle inState) 
        if (inState != null) 
            fragmentA = inState.containsKey(FragmentA.TAG) ?
                    (FragmentA) getSupportFragmentManager().getFragment(inState, FragmentA.TAG):
                    FragmentA.newInstance(FragmentA.TAG,"0");

            fragmentB = inState.containsKey(FragmentB.TAG) ?
                    (FragmentB) getSupportFragmentManager().getFragment(inState, FragmentB.TAG):
                    FragmentB.newInstance(FragmentB.TAG,"1");        

            fragmentc = inState.containsKey(FragmentC.TAG) ?
                    (FragmentC) getSupportFragmentManager().getFragment(inState, FragmentC.TAG):
                    FragmentC.newInstance(FragmentC.TAG,"2");         
        
        else
            fragmentA = FragmentA.newInstance(FragmentA.TAG,"0");
            fragmentB = FragmentB.newInstance(FragmentB.TAG,"1");
            fragmentc = FragmentC.newInstance(FragmentC.TAG,"2");
        
    

根据 Shujaat Ali Khan 的问题进行编辑:

BaseFragment 扩展了 support4 片段:

public abstract class BaseFragment extends Fragment 
    public abstract String getTAG();
    //whatever we can add to be inherited

例如片段A:

public class FragmentA extends BaseFragment 
    // Store instance variables
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    private String mParam1;
    private String mParam2;

    public static final String TAG = "FragmentA";

    // newInstance constructor for creating fragment with arguments
    public static FragmentA newInstance(String param1, String param2) 
        FragmentA fragment = new FragmentA();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    

    // Store instance variables based on arguments passed
    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        if (getArguments() != null) 
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        
    

    // Inflate the view for the fragment based on layout XML
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) 
        View view = inflater.inflate(R.layout.fragmentA, container, false);
        return view;
    

    //other lifecycle methods

    @Override
    public String getTAG() 
        return TAG;
    

最后,R.id.containerBody 是 FrameLayout 的 id,其中包含包含这些片段的 Activity 中的片段。

【讨论】:

您能至少展示一下 FragmentA 类的实现吗?由于我是初学者,所以我很困惑如何定义 NewInstance() 方法。 @ShujaatAliKhan 我刚刚编辑了答案以添加示例。【参考方案3】:

这里的问题是,即使您隐藏了“当前”片段,内存中也会加载其他片段,这会导致不一致的行为。

您应该能够通过隐藏除要显示的片段之外的所有片段来解决此问题。

感谢这个答案。 Show hide fragment in android

例如:

private FragmentA fragmentA;
private FragmentB fragmentB;
private FragmentC fragmentC;

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    fragmentA = FragmentA.newInstance();
    fragmentB = FragmentB.newInstance();
    fragmentC = FragmentC.newInstance();



protected void displayFragmentA() 

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    if (fragmentA.isAdded())  
        ft.show(fragmentA);
     else  
        ft.add(R.id.fragement_container, fragmentA);
    

    if (fragmentB.isAdded())  ft.hide(fragmentB); 

    if (fragmentC.isAdded())  ft.hide(fragmentC); 

    ft.commit();

同样,您必须为 displayFragmentB() 和 displayFragmentC() 编写函数

【讨论】:

以上是关于FragmentTransaction 隐藏/显示有时不起作用的主要内容,如果未能解决你的问题,请参考以下文章

FragmentTransaction.replace() 淡入过渡显示“幽灵”片段

安卓x。 FragmentTransaction 动画在 backpress 上不起作用

Android中,FragmentTransaction类的replace()方法的作用是啥?

操作栏在 FragmentTransaction replace() 和 FragmentTransaction add() 上的行为方式

FragmentTransaction 没有做任何事情

当我使用 fragmentTransaction.add 方法时片段重叠