ViewPager2 预加载下一个片段,即使 offsetlimit 是默认值(0)

ViewPager2 preloaing next fragment even when offsetlimit is the default(0)

我在使用 viewPager2 时遇到了这个相当奇怪的问题。 我做了一些阅读,发现 viewPager2 的默认偏移限制为 0,这非常适合我的应用程序。 我将它与选项卡布局一起使用,并且有 3 个片段(主页、配置文件、通知)。 当 activity 加载并加载第一个片段 (Home) 时,我可以在 logcat 中看到下一个片段 (Profile) 没有加载,正如预期的那样。 但是,当我单击配置文件选项卡时,发生了一些奇怪的事情,预加载了下一个选项卡(通知)。方法 调用通知选项卡的 onAttach、onCreate、onCreateView、onActivityCreated、onStart。 为什么这样做以及如何解决这个问题? Logcat Screenshot 我在这里附上了我的 logcat 的屏幕截图。 提前谢谢你。

我假设你的意思是 OFFSCREEN_PAGE_LIMIT_DEFAULT 而不是 offsetlimit 因为你在谈论预加载片段问题。

并且默认值为-1而不是零https://developer.android.com/reference/androidx/viewpager2/widget/ViewPager2.html#OFFSCREEN_PAGE_LIMIT_DEFAULT

默认意味着

Value to indicate that the default caching mechanism of RecyclerView should be used instead of explicitly prefetch and retain pages to either side of the current page.

由于这是recyclerview的性能优化,我想说这并不能保证它不会预加载你的片段,这只是留给recyclerview的缓存机制来决定。

有很多因素会影响 recyclerview 的缓存机制。

如果预加载你的片段是一个问题,因为你有动态数据,你只想在页面显示时加载,那么最好移动你的片段以使用 "lazy loading" 方法即仅在显示时加载数据。

原来的viewpager也有类似的问题,用"lazy loading"解决了。如果加载动态数据的时间有问题,请更新问题,然后我可以概述一个可能的解决方案。

更新:3

似乎 Viewpager2 实际上与 Fragments 生命周期一起工作,与原始 Viewpager 不同,因此您可以调用 updateView(),如来自 Fragments onResume 方法的 update2 示例所示,而无需使用 pageSelected 通过 Adapter 回调触发 View 的更新。

更新:

我相信查看 viewpager2 代码的真正原因是选择 Tab 会进行平滑滚动的虚假拖动,如果您自己从 Tab0 滑动到 Tab1,平滑滚动会将所选项目 +1 添加到缓存中不创建 Tab2

ViewPager2 有点不同,我用于原始 ViewPager 的 "lazy loading" 方法并不完全适合,但有一种稍微不同的方法可以做到这一点。

原始 ViewPager 的主要思想是仅在使用 onPageChangeListener 选择页面时更新视图,但 ViewPager2 使用回调

因此在创建 ViewPager2 后添加以下内容(通常在 Activity onCreate 中)

        viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                // Tell the recyclerview that position 2 has changed when selected
                // Thus it recreates it updating the dynamic data
                if (position == 2) {
                    // the adapter for the ViewPager needs to a member of the Activity class so accessible here
                    adapter.notifyItemChanged(position);
                }
            }
        });

这更简单,但有一个小缺点,即动态数据在预加载时加载,然后在实际显示时再次加载。

更新2: 对第一种方法更有效的补充,更类似于我原来的方法

这是一个完整的工作示例,因为它更容易解释。

主要思想是在您的片段中包含您只想在显示时加载的动态数据,即创建一个空的 "placeholder" 视图项,而不用片段中的数据填充它onViewCreated,在此示例中,它是第二个 textview,没有文本,但可以是零 objects 或任何其他类型的视图的 recyclerview。

然后在您的 Fragment 中创建一个使用数据更新 "placeholder" 的方法(在本例中,该方法称为 updateView(),它将 textview 文本设置为当前日期和时间)

然后在您的片段适配器中存储对它在 ArrayList 中创建的每个片段的引用(这允许您取回片段)然后您在适配器中创建一个 updateFragment() 方法使用位置获取 Fragment 可以在其上调用 updateView()

最后,您再次使用 onPageSelected 调用 updateFragment 以及您要动态更新的位置。

所以 textview1 显示创建碎片的日期和时间,而 textview2 仅显示在第三个选项卡上,并且在选择页面时有日期和时间。注意"Tab 2"和"Tab 3"上的texview1是同时点击"Tab 2"headers换tab的(这就是题中的问题)

MainActivity.java

package com.test.viewpager2;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;

import android.os.Bundle;

import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;

public class MainActivity extends AppCompatActivity {

    TabLayout tabLayout;
    ViewPager2 viewPager2;
    ViewPager2Adapter adapter;

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

        viewPager2 = findViewById(R.id.viewpager2);
        tabLayout = findViewById(R.id.tabLayout);

        viewPager2.setAdapter(createCardAdapter());

        new TabLayoutMediator(tabLayout, viewPager2,
                new TabLayoutMediator.TabConfigurationStrategy() {
                    @Override public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
                        tab.setText("Tab " + (position + 1));
                    }
                }).attach();

        viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                // Tell the recyclerview that position 2 has changed when selected
                // Thus it recreates it updating the dynamic data
                if (position == 2) {
                    adapter.updateFragment(position);
                }
            }
        });
    }

    private ViewPager2Adapter createCardAdapter() {
        adapter = new ViewPager2Adapter(this);
        return adapter;
    }

}

ViewPager2Adapter.java

package com.test.viewpager2;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;

import java.util.ArrayList;

public class ViewPager2Adapter extends FragmentStateAdapter {

    private static final int numOfTabs = 3;
    private ArrayList<Fragment> fragments = new ArrayList<>();

    public ViewPager2Adapter(@NonNull FragmentActivity fragmentActivity) {
        super(fragmentActivity);
    }

    @NonNull @Override public Fragment createFragment(int position){
        Fragment fragment = TextFragment.newInstance(position);
        fragments.add(fragment);
        return fragment;
    }

    @Override
    public int getItemCount(){
        return numOfTabs;
    }

    public void updateFragment(int position){
        Fragment fragment = fragments.get(position);
        // Check fragment type to make sure it is one we know has an updateView Method
        if (fragment instanceof TextFragment){
            TextFragment textFragment = (TextFragment) fragment;
            textFragment.updateView();
        }
    }
}

TextFragment.java

package com.test.viewpager2;

import android.content.Context;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;

public class TextFragment extends Fragment {
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";

    private int mParam1;
    private View view;


    public TextFragment() {
        // Required empty public constructor
    }

    public static TextFragment newInstance(int param1) {
        TextFragment fragment = new TextFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_PARAM1, param1);
        fragment.setArguments(args);
        Log.d("Frag", "newInstance");
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getInt(ARG_PARAM1);
        }
        Log.d("Frag", "onCreate");
    }

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

        return view;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        Bundle args = getArguments();
        TextView textView1 = view.findViewById(R.id.textview1);
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss.sss", Locale.US);
        String dt = df.format(Calendar.getInstance().getTime());
        textView1.setText(dt);
    }


    public void updateView(){
        Log.d("Frag", "updateView");
        TextView textView2 = view.findViewById(R.id.textview2);
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss.sss", Locale.US);
        String dt = df.format(Calendar.getInstance().getTime());
        textView2.setText(dt);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.d("Frag", "onAttach");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.d("Frag", "onDetach");
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager2"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tabLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<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=".TextFragment">

    <TextView
        android:id="@+id/textview1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textview2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textview1" />/>

</androidx.constraintlayout.widget.ConstraintLayout>

您需要在每次滚动时通知适配器。

 pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {
                
                   


                super.onPageScrolled(position, positionOffset, positionOffsetPixels)
            }

            override fun onPageSelected(position: Int) {

                

                myToast(applicationContext, "1 changes $position")
                tapPosition = position
              
                super.onPageSelected(position)
            }

            override fun onPageScrollStateChanged(state: Int) {

                if (state.equals(0))
                {
                    pagerAdapter.notifyItemChanged(tapPosition)

                }
                super.onPageScrollStateChanged(state)
            }
        })

onPageScrollStateChanged 这里必须通知adapter。