简体   繁体   English

片段未在 android 上正确显示

[英]Fragments not being displayed correctly on android

I am creating a budgeting app in Android.我正在 Android 中创建预算应用程序。 Currently, I'm trying to use fragments to display all of the users' current wallets (one fragment per wallet).目前,我正在尝试使用fragments来显示所有用户当前的钱包(每个钱包一个片段)。 Each fragment will take up the whole screen and use a TabLayout at the top of the screen for navigation.每个fragment将占据整个屏幕并使用屏幕顶部的TabLayout进行导航。 Since my fragments are displaying data that is going to be changed often, they need to be dynamic.由于我的fragments显示的数据会经常更改,因此它们需要是动态的。 The issue right now is that they are being created (I can see them in the FragmentManager using breakpoints etc), however, they aren't being displayed in the ViewPager or the TabLayout .现在的问题是它们正在被创建(我可以使用断点等在FragmentManager看到它们),但是,它们没有显示在ViewPagerTabLayout

My XML code for the main activity:我的主要活动的 XML 代码:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<androidx.viewpager.widget.ViewPager
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />


<com.google.android.material.tabs.TabLayout
    android:id="@+id/tabLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:elevation="4dp"
    android:minHeight="?attr/actionBarSize"
    android:background="@color/colorTest"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:tabGravity="fill"
    app:tabMode="fixed" />

<Button
    android:id="@+id/createWallet"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Create wallet"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<Button
    android:id="@+id/currentFragment"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="onClick"
    android:text="Current Fragment"
    app:layout_constraintBottom_toBottomOf="@+id/createWallet"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

My code for creating the fragments and tying the ViewPager and TabLayout together in MainActivity :我在MainActivity创建fragments并将ViewPagerTabLayout在一起的代码:

ViewPager viewPager = findViewById(R.id.pager);
ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(), MainActivity.this);
viewPager.setAdapter(viewPagerAdapter);

TabLayout tabLayout = findViewById(R.id.tabLayout);
tabLayout.setupWithViewPager(viewPager);

public void loadFragments()
{
    ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(), MainActivity.this);

    for (int i = 0; i < walletList.size(); i++)
    {
        WalletClass object = walletList.get(i);

        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragment frag = new fragment().newInstance(object);
        fragmentTransaction.add(R.id.pager, frag, "fragment" + i);
        viewPagerAdapter.addFragment(frag); // this line can cause crashes
        fragmentTransaction.commit();
    }
}

My fragment class code:我的片段类代码:

public fragment newInstance(WalletClass wallet)
{
    fragment frag = new fragment();
    Bundle bundle = new Bundle();

    bundle.putString("walletName", wallet.getWalletName());
    bundle.putInt("walletBalance", wallet.getBalance());

    frag.setArguments(bundle);
    return frag;
}


@Override
public void onCreate(@Nullable Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    Bundle bundle = getArguments();
    Toast.makeText(getContext(), "" + bundle.getString("walletName"), Toast.LENGTH_SHORT).show();
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    // Inflate the layout for this fragment
    View view =  inflater.inflate(R.layout.fragment_layout, container, false);

    Bundle bundle = getArguments();

    TextView walletName = view.findViewById(R.id.walletName);
    walletName.setText(bundle.getString("walletName"));

    return view;
}

And my code for the ViewPager adapter.还有我的ViewPager适配器代码。 This code is mostly from some other tutorials online mashed together, this is essentially what needs to be reworked I think:这段代码主要来自其他一些在线教程,我认为这基本上是需要重新编写的:

List<fragment> fragmentList = new ArrayList<>(); // this line can cause crashes
List<String> fragmentNameList = new ArrayList<>();

private Context context;

public ViewPagerAdapter(FragmentManager fm, Context context)
{
    super(fm);
    this.context = context;
}

@Override
public fragment getItem(int position)
{
    return fragmentList.get(position);
}

public void addFragment (Fragment fragment, WalletClass wallet) // this line can cause crashes
{
    Bundle bundle = new Bundle();
    bundle.putString("walletName", wallet.getWalletName());
    bundle.putInt("walletBalance", wallet.getBalance());
    fragment.setArguments(bundle);

    fragmentList.add(fragment);
    fragmentNameList.add(wallet.getWalletName());
}

public void removeFragment (fragment fragment)
{
    fragmentList.remove(fragment);
}

@Override
public int getCount()
{
    return fragmentList.size();
}

@Nullable
public CharSequence getPageTitle(int position)
{
    return fragmentNameList.get(position);
}

So does anyone have any ideas about how to create fragments dynamically with a ViewPager adapter?那么有人对如何使用ViewPager适配器动态创建fragments有任何想法吗? Or am I doing this the completely wrong way?或者我这样做是完全错误的方式吗?

FragmentPagerAdapter already manages the lifecycle of Fragments, you wouldn't need to add them manually. FragmentPagerAdapter 已经管理 Fragment 的生命周期,您不需要手动添加它们。 However, FragmentPagerAdapter encodes the position of a Fragment into its fragment tag, so it cannot be safely used to handle dynamic pager adapters.但是, FragmentPagerAdapter 将 Fragment 的位置编码到其片段标记中,因此不能安全地用于处理动态寻呼适配器。

You need to use the following code, or equivalent (that would probably end up to be the following code), based on code of FragmentPagerAdapter.您需要根据 FragmentPagerAdapter 的代码使用以下代码或等效代码(最终可能会成为以下代码)。

public class DynamicFragmentPagerAdapter extends PagerAdapter {
    private static final String TAG = "DynamicFragmentPagerAdapter";

    private final FragmentManager fragmentManager;

    public static abstract class FragmentIdentifier implements Parcelable {
        private final String fragmentTag;
        private final Bundle args;

        public FragmentIdentifier(@NonNull String fragmentTag, @Nullable Bundle args) {
            this.fragmentTag = fragmentTag;
            this.args = args;
        }

        protected FragmentIdentifier(Parcel in) {
            fragmentTag = in.readString();
            args = in.readBundle(getClass().getClassLoader());
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(fragmentTag);
            dest.writeBundle(args);
        }

        protected final Fragment newFragment() {
            Fragment fragment = createFragment();
            Bundle oldArgs = fragment.getArguments();
            Bundle newArgs = new Bundle();
            if(oldArgs != null) {
                newArgs.putAll(oldArgs);
            }
            if(args != null) {
                newArgs.putAll(args);
            }
            fragment.setArguments(newArgs);
            return fragment;
        }

        protected abstract Fragment createFragment();
    }

    private ArrayList<FragmentIdentifier> fragmentIdentifiers = new ArrayList<>();

    private FragmentTransaction currentTransaction = null;

    private Fragment currentPrimaryItem = null;

    public DynamicFragmentPagerAdapter(FragmentManager fragmentManager) {
        this.fragmentManager = fragmentManager;
    }

    private int findIndexIfAdded(FragmentIdentifier fragmentIdentifier) {
        for (int i = 0, size = fragmentIdentifiers.size(); i < size; i++) {
            FragmentIdentifier identifier = fragmentIdentifiers.get(i);
            if (identifier.fragmentTag.equals(fragmentIdentifier.fragmentTag)) {
                return i;
            }
        }
        return -1;
    }

    public void addFragment(FragmentIdentifier fragmentIdentifier) {
        if (findIndexIfAdded(fragmentIdentifier) < 0) {
            fragmentIdentifiers.add(fragmentIdentifier);
            notifyDataSetChanged();
        }
    }

    public void removeFragment(FragmentIdentifier fragmentIdentifier) {
        int index = findIndexIfAdded(fragmentIdentifier);
        if (index >= 0) {
            fragmentIdentifiers.remove(index);
            notifyDataSetChanged();
        }
    }

    @Override
    public int getCount() {
        return fragmentIdentifiers.size();
    }

    @Override
    public void startUpdate(@NonNull ViewGroup container) {
        if (container.getId() == View.NO_ID) {
            throw new IllegalStateException("ViewPager with adapter " + this
                    + " requires a view id");
        }
    }

    @SuppressWarnings("ReferenceEquality")
    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        if (currentTransaction == null) {
            currentTransaction = fragmentManager.beginTransaction();
        }
        final FragmentIdentifier fragmentIdentifier = fragmentIdentifiers.get(position);
        // Do we already have this fragment?
        final String name = fragmentIdentifier.fragmentTag;
        Fragment fragment = fragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            currentTransaction.attach(fragment);
        } else {
            fragment = fragmentIdentifier.newFragment();
            currentTransaction.add(container.getId(), fragment, fragmentIdentifier.fragmentTag);
        }
        if (fragment != currentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }
        return fragment;
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        if (currentTransaction == null) {
            currentTransaction = fragmentManager.beginTransaction();
        }
        currentTransaction.detach((Fragment) object);
    }

    @SuppressWarnings("ReferenceEquality")
    @Override
    public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment) object;
        if (fragment != currentPrimaryItem) {
            if (currentPrimaryItem != null) {
                currentPrimaryItem.setMenuVisibility(false);
                currentPrimaryItem.setUserVisibleHint(false);
            }
            fragment.setMenuVisibility(true);
            fragment.setUserVisibleHint(true);
            currentPrimaryItem = fragment;
        }
    }

    @Override
    public void finishUpdate(@NonNull ViewGroup container) {
        if (currentTransaction != null) {
            currentTransaction.commitNowAllowingStateLoss();
            currentTransaction = null;
        }
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
        return ((Fragment) object).getView() == view;
    }

    @Override
    public Parcelable saveState() {
        Bundle bundle = new Bundle();
        bundle.putParcelableArrayList("fragmentIdentifiers", fragmentIdentifiers);
        return bundle;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
        Bundle bundle = ((Bundle)state);
        bundle.setClassLoader(loader);
        fragmentIdentifiers = bundle.getParcelableArrayList("fragmentIdentifiers");
    }
}

Then you can pass in FragmentIdentifier s into this pager, which will properly manage your fragments based on your fragment identifiers.然后你可以将FragmentIdentifier s 传入这个寻呼机,它会根据你的片段标识符正确管理你的片段。

 List<fragment> fragmentList = new ArrayList<>();

Those tutorials are trying to make your app crash, check #3 in https://proandroiddev.com/the-seven-actually-10-cardinal-sins-of-android-development-491d2f64c8e0这些教程试图让您的应用程序崩溃,请检查https://proandroiddev.com/the-seven-actually-10-cardinal-sins-of-android-development-491d2f64c8e0 中的#3

I think your fragment not attached properly to your ViewPager .我认为您的fragment没有正确附加到您的ViewPager

First , in your loadFragment() your new added Fragment has not been set on your viewPager .首先,在您的loadFragment()您的新添加的 Fragment 尚未在您的viewPager上设置。 you have to add setAdapter() again after adding new fragment to your adapter.在将新片段添加到适配器后,您必须再次添加setAdapter() As mentioned inofficial documentation (it's from ListAdapter class, but it fundamentally same):正如官方文档中提到的(它来自 ListAdapter 类,但基本相同):

In order to display items in the list, call setAdapter(android.widget.ListAdapter) to associate an adapter with the list.为了显示列表中的项目,调用 setAdapter(android.widget.ListAdapter) 将适配器与列表关联。

Second , also in your loadFragment() , you don't have to create a new ViewPagerAdapter instance inside the method.其次,同样在您的loadFragment() ,您不必在方法内创建新的ViewPagerAdapter实例。 Use your viewPagerAdapter that you instantiate early.使用您早期实例化的 viewPagerAdapter。 And as long you declare ViewPager in the beginning outside your onCreate() method.只要您在onCreate()方法之外的开头声明 ViewPager。

Third , you may want to remove your FragmentTransaction in loadFragment() .第三,您可能希望在loadFragment()删除您的FragmentTransaction I don't know if it means to be or not.我不知道是不是意味着。 But, as long as you have adapter, your new fragment could attached automatically without calling FragmentTransaction但是,只要你有适配器,你的新片段就可以自动附加而无需调用FragmentTransaction

Here what MainActivity looks like:这是MainActivity样子:

// declare ViewPager viewPager in the beginning of class.
viewPager = findViewById(R.id.pager);
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(), MainActivity.this);
viewPager.setAdapter(viewPagerAdapter);

TabLayout tabLayout = findViewById(R.id.tabLayout);
tabLayout.setupWithViewPager(viewPager);

public void loadFragments()
{

    for (int i = 0; i < walletList.size(); i++)
    {
        WalletClass object = walletList.get(i);

        fragment frag = new fragment().newInstance(object);
        viewPagerAdapter.addFragment(frag, object);
        viewPager.setAdapter(viewPagerAdapter)
    }
}

Note : When I ran your code, I think the problem is not your Adapter but the way you call your adapter注意:当我运行您的代码时,我认为问题不在于您的适配器,而在于您调用适配器的方式

Hope it helps.希望能帮助到你。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM