Android如何在切换标签时停止刷新Fragments

44

我有以下代码:

MainActivity.java

package com.erc.library;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.FragmentTransaction;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;

import com.erc.sayeghlibrary.adapter.TabsPagerAdapter;

public class MainActivity extends FragmentActivity implements
        ActionBar.TabListener {

    private ViewPager viewPager;
    private TabsPagerAdapter mAdapter;
    private ActionBar actionBar;
    // Tab titles
    private String[] tabs = { "Stories", "Dictionaries", "eBooks"};

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






        int actionBarTitleId = Resources.getSystem().getIdentifier("action_bar_title", "id", "android");
        if (actionBarTitleId > 0) {
            TextView title = (TextView) findViewById(actionBarTitleId);
            if (title != null) {
                title.setTextColor(Color.WHITE);
            }
        }       

        // Initilization
        viewPager = (ViewPager) findViewById(R.id.pager);
        actionBar = getActionBar();
        mAdapter = new TabsPagerAdapter(getSupportFragmentManager());

        viewPager.setAdapter(mAdapter);
        actionBar.setHomeButtonEnabled(false);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);


        // Adding Tabs
        for (String tab_name : tabs) {
            actionBar.addTab(actionBar.newTab().setText(tab_name)
                    .setTabListener(this));
        }

        /**
         * on swiping the viewpager make respective tab selected
         * */
        viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

            @Override
            public void onPageSelected(int position) {
                // on changing the page
                // make respected tab selected
                actionBar.setSelectedNavigationItem(position);
            }

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {
            }

            @Override
            public void onPageScrollStateChanged(int arg0) {
            }
        });







    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        // on tab selected
        // show respected fragment view
        viewPager.setCurrentItem(tab.getPosition());


    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    }






       @Override
       public boolean onCreateOptionsMenu(Menu menu) {
         MenuInflater inflater = getMenuInflater();
         inflater.inflate(R.menu.main, menu);
         return true;
       } 




       @Override
       public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
         // action with ID action_refresh was selected
         case R.id.Favorites:
           Toast.makeText(this, "Favorites selected", Toast.LENGTH_SHORT)
               .show();
           break;
         // action with ID action_settings was selected

         default:
           break;
         }

         return true;
       } 


}

TabsPagerAdapter.java

package com.erc.library.adapter;

import com.erc.library.Dictionaries;
import com.erc.library.Ebooks;
import com.erc.library.Stories;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

public class TabsPagerAdapter extends FragmentPagerAdapter {

    public TabsPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int index) {

        switch (index) {
        case 0:

            return new Stories();
        case 1:

            return new Dictionaries();
        case 2:
            // Movies fragment activity
            return new Ebooks();
        }

        return null;
    }

    @Override
    public int getCount() {
        // get item count - equal to number of tabs
        return 3;
    }

}

我正在制作一个图书馆应用程序,其中导航是通过选项卡实现的,问题是每次我从第三个选项卡切换到第一个或者从第一个切换到第三个时,选项卡内容都会刷新,我希望阻止这种刷新,请问有什么帮助吗?


好的,我已经找到了解决方案,我应该加入以下代码行: viewPager.setOffscreenPageLimit(选项卡数量); - Monzer Yaghi
7个回答

143

默认情况下,ViewPager 在滑动页面时会重新创建片段。为了防止这种情况,您可以尝试以下三种方法之一:

1. 在片段的onCreate()中调用setRetainInstance(true)

2. 如果片段数量固定且相对较少,则可以在onCreate()中添加以下代码:

mViewPager = (ViewPager)findViewById(R.id.pager);
mViewPager.setOffscreenPageLimit(limit);         /* limit is a fixed integer*/

3. 使用 FragmentPagerAdapter 作为你的 ViewPager 的一部分。

如果我没记错的话,第二个选项更有前途。但我建议你尝试所有三个选项,看看哪一个可行。


3
在这种情况下,保留片段将不起作用,而且保留通常是一种不良的做法,因为如果不小心使用,它可能会导致难以跟踪的内存泄漏。我将使用第二个选项,在这里告诉ViewPager -“为我缓存X个片段”。就是这样。 - Gaskoin
是的,正如你所说,第一种选项应该谨慎使用...虽然它确实有效 ;) - Yash Sampat
1
@Y.S. 我的片段有2个选项卡,当调用片段时,会同时调用2个选项卡的片段,我能否防止这种情况发生,例如当片段调用第一个选项卡时,选项卡1被选中,但同时后台选项卡2也会调用onCreate,我能否防止这种情况发生 - 请帮忙,当我从服务器加载数据时,我遇到了问题,因为两个选项卡具有不同的API调用,所以当选项卡活动同时调用时,两个选项卡的API都会被调用。 - Mansukh Ahir
2
第一种方法对我没用,但第二个选项完美地解决了问题。所以谢谢你们。 - Ali.DM
@YashSampat 你好,这些对我来说不起作用,因为我正在使用FragmentStateAdapter和ViewPager2。请也为我提供解决方案,谢谢。 - CODAR747

25

首先需要实例化FragmentPagerAdapter,然后.getCount()会返回一个值 -

同时.getCount() - 1应该被设置为默认的离屏限制:

TabsPagerAdapter adapter = new TabsPagerAdapter(getSupportFragmentManager());

/* the ViewPager requires a minimum of 1 as OffscreenPageLimit */
int limit = (adapter.getCount() > 1 ? adapter.getCount() - 1 : 1);

ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(limit);

3
你救了我的命。但希望你能更详细地解释一下 ViewPager.setOffscreenPageLimit() 方法。 - Sadeq Shajary
@SadeqShajary,这在文档中有详细解释,可能比我讲得更好:https://developer.android.com/reference/android/support/v4/view/ViewPager.html#setOffscreenPageLimit(int)。 - Martin Zeitler
@DoXuanNguyen更新了我的答案,因为在尝试设置0的限制时出现了错误。 - Martin Zeitler
@Zon https://developer.android.com/reference/android/support/v4/app/Fragment.html#getChildFragmentManager() - Martin Zeitler

5
你可以通过检查视图是否为空来处理视图重建。
public class FragmentExample extends Fragment {

    private View rootView;

    public FragmentExample() {}

@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {

    if (rootView == null) {

        rootView = inflater.inflate(R.layout.fragment_example_layout, container, false);

        // Initialise your layout here

    } else {
        ((ViewGroup) rootView.getParent()).removeView(rootView);
    }

    return rootView;
    }
}

3
在你的片段的onCreate()方法中,调用setRetainInstance(true)。

可以使用FragmentStatePagerAdapter代替调用setRetainInstance(true)(这取决于预期的行为,因为SDK文档中写道:“当活动重新创建时,片段生命周期会略有不同”)。 - Martin Zeitler

2
有点晚回答这个问题,但感谢Y.S.让我了解ViewPager的工作原理。我正在构建一个带有4个选项卡的应用程序,发现任何时候只有两个选项卡被刷新,这可能是默认行为。经过数小时的调查,我了解到Android会刷新多个选项卡以实现平滑的滑动性能 - 您可能会注意到您已单击选项卡2,但Android会为选项卡3提供数据并将其准备好。
虽然这种行为很好,但它有优点和缺点。优点-您可以获得流畅的滑动体验,而不必在着陆该选项卡时从外部服务器加载数据。缺点-您在选项卡中的后退堆栈实现可能会出现问题。当您单击选项卡时,视图页面实际上会调用更平滑的选项卡,如果您的方法基于所单击选项卡中的后退堆栈设置顶部左侧的返回箭头(主页),则会遇到大麻烦。
setOffscreenPageLimit就是解决方案。如果您希望自定义后退堆栈框架正常运行,并且不希望在单击选项卡2时调用选项卡3,则只需将值设置为选项卡数。例如,如果您有4个选项卡,则设置setOffScreePageLimit(4)。这意味着Android最初会刷新所有4个片段,这可能会带来一些性能开销(您应该适当管理)。但是,您的后退堆栈和选项卡切换仍然保持完好。希望这可以帮助到您。

1
由于活动实现了ActionBar.TabListener,活动的onCreate()方法会一遍又一遍地被调用。因此,请将以下代码放置在onResume()方法中:
   // Initilization
    viewPager = (ViewPager) findViewById(R.id.pager);
    actionBar = getActionBar();
    mAdapter = new TabsPagerAdapter(getSupportFragmentManager());

    viewPager.setAdapter(mAdapter);
    actionBar.setHomeButtonEnabled(false);
    actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);


    // Adding Tabs
    for (String tab_name : tabs) {
        actionBar.addTab(actionBar.newTab().setText(tab_name)
                .setTabListener(this));
    }

    /**
     * on swiping the viewpager make respective tab selected
     * */
    viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageSelected(int position) {
            // on changing the page
            // make respected tab selected
            actionBar.setSelectedNavigationItem(position);
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }
    });

0

在我的情况下,上面的建议不起作用。

为了限制片段的重新创建,我所做的是:

onCreateView中,您可以将充气视图存储在全局变量中,并仅在其为null时初始化它,如此代码:

    var root:View?=null
    var apiDataReceived=false
 override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    // Inflate the layout for this fragment
    if (root==null)
        root=inflater!!.inflate(R.layout.fragment_layout, container, false)
    return root
}

现在,如果您正在解析一些数据并将其填充到RecyclerView或任何其他View

  1. 像上面的代码中一样创建一个全局变量apiDataReceived

  2. 如果您成功解析了数据,请将其设置为true。

  3. 在apiCalls之前放置一个条件,如下所示:

    if (!apiDataReceived) { apiCalls() }

因此,只有在未解析数据时才会调用apiCalls()。

onCreateView之后调用方法(例如onStart)中执行http调用和解析或任何其他操作

以上代码是kotlin编写的,如果您遇到任何问题,请在评论中让我知道。


更换页面后,Api数据会被清空吗? - Harsh Bhavsar
@HarshBhavsar,那么你在共享首选项中的回应。 - Suraj Vaishnav
我正在尝试在GridView中针对特定项(网格项)的API调用后防止UI更新。 是否可以通过保留GridView中的某些项来更新其余项。 任何帮助将不胜感激。 提前致谢。 - ABHIMANGAL MS
@ABHIMANGALMS 我没有完全理解你的问题,但是如果你想要部分更新,你可以使用ListAdapter和DiffUtil代替RecyclerViewAdapter。每当你想要在列表中更新某些内容时,你只需要创建一个包含需要显示的更新数据的新列表,并将新列表传递给ListAdapter即可。 - Suraj Vaishnav

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接