即使offsetlimit为默认值(0),ViewPager2也会展开下一个片段 [英] ViewPager2 preloaing next fragment even when offsetlimit is the default(0)

查看:173
本文介绍了即使offsetlimit为默认值(0),ViewPager2也会展开下一个片段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在用viewPager2面对这个相当奇怪的问题。
我做了一些阅读,发现viewPager2的默认偏移量限制为0,非常适合我的应用程序。
我使用的是标签布局,并且有3个片段(首页,个人资料和通知)。
当活动加载且第一个片段(Home)加载时,我可以在logcat中看到未按预期加载下一个片段(Profile)。
但是,当我单击配置文件选项卡时,会发生一些奇怪的事情,下一个选项卡(通知)已预加载。调用通知选项卡的方法
onAttach,onCreate,onCreateView,onActivityCreated,onStart。
为什么这样做以及如何解决这个问题?
Logcat屏幕截图
我已经附上了我的屏幕截图日志猫在这里。
谢谢。

I am facing this rather strange problem with viewPager2. I did some reading and found out that viewPager2 has a default offset limit of 0 which is perfect for my application. I'm using it with a tab layout and I have 3 fragments (Home, Profile, Notification). When the activity loads and the first fragment(Home) loads, I can see in my logcat that the next fragment(Profile) is not loaded, as expected. But when I click on the profile tab something strange happens, the next tab(Notification) is preloaded. The methods onAttach,onCreate,onCreateView,onActivityCreated,onStart for the Notification tab is called. Why is this doing so and how to I fix this ? Logcat Screenshot I have attached a screen shot of my logcat here. Thankyou in advance.

推荐答案

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

I Assume you mean OFFSCREEN_PAGE_LIMIT_DEFAULT not offsetlimit as you are talking about a preloading of fragments problem.

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

和默认值


该值指示应使用RecyclerView的默认缓存机制,而不是显式预取并将页面保留在当前页面的任何一侧。

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的性能优化,我想说这并不保证它不会预加载您的片段,而是留给了缓存机制

As this is a performance optimisation of the recyclerview, I would say it's not a guarantee that it won't preload your fragments, it's just left to the caching mechanism of the recyclerview to decide.

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

There are a number of factors that can affect the recyclerview's caching mechanism.

如果片段的预加载是一个问题,因为其中包含动态数据,只希望在显示页面时才加载,那么最好将片段移动为使用延迟加载方法,即仅加载数据

If preloading of your fragment is a problem because you have dynamic data in it that you only want to be loaded when the page is shown then it would be better to move your fragment to use a "lazy loading" method i.e. only load the data when it is shown.

我对原始的viewpager也遇到了类似的问题,并通过延迟加载解决了。如果问题是动态数据的加载时间不正确,请先更新问题,然后我可以概述一个可能的解决方案。

I had a similar problem with the original viewpager and solved it with "lazy loading". If the timing of loading of your dynamic data is the problem then update the question and then I can outline a possible solution.

更新:3

似乎Viewpager2实际上可以在Fragments生命周期中正常工作,这与原始Viewpager不同,因此您可以调用 updateView()如Fragments onResume 方法中update2示例所示,而不必通过适配器使用 pageSelected 回调来触发更新

It seems that Viewpager2 actually works correctly with the Fragments lifecycle unlike the original Viewpager thus you can call updateView() as shown in update2 example from the Fragments onResume method without having to use the pageSelected callback via the Adapter to trigger the update of the View.

更新:

我相信从在viewpager2代码中,选择Tab可以进行平滑的滚动,而平滑滚动会将选定的项目+1添加到缓存中,如果您自己从Tab0滑动到Tab1,则不会创建Tab2

I believe the actual cause from looking at the viewpager2 code is that selecting the Tab does a fake drag with smooth scrolling and smooth scrolling adds the selected item +1 to the cache, if you swipe from Tab0 to Tab1 yourself it does not create Tab2

ViewPager2有点不同,我用于原始ViewPager的延迟加载方法并不完全适合,但略有不同

ViewPager2 is a bit different and "lazy loading" method I used for the original ViewPager does not totally fit but there is a slightly different way to do the same.

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

The main idea with the original ViewPager was to update the view ONLY when a page was selected using a onPageChangeListener but ViewPager2 uses a callback instead

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

So add the following after you have created the ViewPager2 (in the Activity onCreate usually)

        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);
                }
            }
        });

这比较简单,但缺点是动态数据在预加载时先加载,然后再加载

This is simpler but has a minor drawback that the dynamic data is loaded when it is preloaded and then again when it is actually displayed.

Update2:
对第一种方法的更有效的添加与我的原始方法类似

Update2: A more efficient addition to first method more similar to my original approach

这是一个完整的示例,更易于解释。

This is a full working example as it is easier to explain.

主要思想是在动态片段中您只想在显示时加载的数据是创建一个空的占位符视图项,并且您不会在Fragments onViewCreated 中填充数据。例如,它是第二个 textview 没有文本,但可以是带有零个对象或任何其他类型视图的recyclerview。

The main idea is in your fragment with the dynamic data that you ONLY want to load when it is displayed is to create an empty "placeholder" view item and you don't fill it with data in the Fragments onViewCreated, in this example it is a second textview with no text but could be a recyclerview with zero objects or any other type of view.

在您的Fragment中,然后创建一个使用数据更新占位符的方法(在这种情况下,该方法称为 updateView(),它将textview文本设置为当前日期和时间)

In your Fragment you then create a method to update the "placeholder" with the data (in this case the method is called updateView() which sets the textview text to the current date and time)

然后在片段适配器中,将对它创建的每个片段的引用存储在 ArrayList 中(这使您可以将片段取回),然后创建一个<$ c适配器中的$ c> updateFragment()方法,该方法使用该位置获取Fragment以便能够在其上调用 updateView()

Then in your Fragment Adapter you store a reference to each fragment it creates in a ArrayList (this allows you get the fragment back) and you then create an updateFragment() method in the adapter that uses the position to get the Fragment to be able to call the updateView() on it.

最后再次使用 onPageSelected 调用 updateFragment

所以 textview1 显示了创建碎片的数据和时间,并且 textview2 仅显示在第三个选项卡上,并具有选择页面时的日期和时间。请注意,当您点击标签2标题以更改标签时,标签2和标签3上的 texview1 是同一时间。问题)

So textview1 shows the data and time the fragement was created and textview2 is only shown on the third Tab and has a date and time on when page was selected. Note that texview1 on "Tab 2" and "Tab 3" is the same time when you click on the "Tab 2" headers to change tabs (this is the problem in the question)

MainActivity.java

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

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

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

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>

这篇关于即使offsetlimit为默认值(0),ViewPager2也会展开下一个片段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆