片段内的 NestedScrollView 不以编程方式滚动

NestedScrollView inside fragment not programatically scrolling

我有一个带有 viewpager2 和 FragmentStateAdapter 的 tablayout。我有 3 个选项卡,每个选项卡都有一个包含线性布局的 NestedScrollView:

<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:id="@+id/scrollView"
        android:focusableInTouchMode="true"
        android:layout_height="wrap_content">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:descendantFocusability="blocksDescendants">

            /* ... */

       </LinearLayout>
</androidx.core.widget.NestedScrollView>

当我在 tablayout 中的选项卡之间切换时,滚动视图不会从顶部开始。在 viewPager 的每个片段的 onViewCreated() 方法中,我添加了以下行,但是滚动视图仍然没有滚动到顶部,它从停止的地方开始。

public static class MyFragment extends Fragment {

        private NestedScrollView scrollView;

        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                                 @Nullable Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment, container, false);
        }

        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            scrollView = view.findViewById(R.id.scrollView);
            scrollView.smoothScrollTo(0, 0);
        }
}

onViewCreated 仅在首次创建 Fragment 时调用。 ViewPager 不会改变 Fragment 的状态。您可以认为隐藏的 Fragments 被存储在当前可见的 Fragment.

的两侧,可见

我通过在 TabLayout 更改时设置回调来完成。 TabLayout 是监听器所在的位置。然后,您可以让它在您的 FragmentPagerAdapter 中调用回调方法。在每个 Fragment 中,您应该实现一个 returns 回调的方法。在 FragmentPagerAdapter 中,使用 getItem(int position) 将来自 Fragment 的回调存储在 Map 中。适配器中的回调方法从 TabLayout

中获取位置

首先,在你的片段中,给它们一个方法,returns 某种回调(我使用了 Runnable)将从适配器调用。

public class PlaceHolderFragment extends Fragment {

...

    public Runnable getOnTabChangedListener() {
        return new Runnable() {
            @Override
            public void run() {
                scrollView.smoothScrollTo(0, 0);
            }
        };
    }

...

}

然后,在你的 FragmentPagerAdapter(我叫我的 SectionsPagerAdapter)中,将回调存储在映射中的 getItem() 方法中,然后添加自定义回调。

public class SectionsPagerAdapter extends FragmentPagerAdapter {

    ...

    HashMap<Integer, Runnable> tabChangedCallbacks = new HashMap<>();

    ...

    @Override
    public Fragment getItem(int position) {
        // getItem is called to instantiate the fragment for the given page.
        // Return a PlaceholderFragment (defined as a static inner class below).
        PlaceholderFragment fragment = PlaceholderFragment.newInstance(position + 1);
        tabChangedCallbacks.put(position, fragment.getOnTabChangedListener());
        return fragment;
    }

    public void onTabChanged(int position) {
        tabChangedCallbacks.get(position).run();
    }

    ...

}

在包含 TabLayout 的 activity 中,您需要在 onCreate() 方法中调用 TabLayout 上的 addOnTabSelectedListener()

protected void onCreate(Bundle savedInstanceState) {

    ...

    final SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
    final ViewPager viewPager = findViewById(R.id.view_pager);
    viewPager.setAdapter(sectionsPagerAdapter);
    TabLayout tabs = findViewById(R.id.tabs);
    tabs.setupWithViewPager(viewPager);
    tabs.addOnTabSelectedListener(
            new TabLayout.ViewPagerOnTabSelectedListener(viewPager) {
                @Override
                public void onTabSelected(TabLayout.Tab tab) {
                    super.onTabSelected(tab);
                    sectionsPagerAdapter.onTabChanged(tab.getPosition());
                }
            });

    ...

}

编辑

原理是一样的,但是要使用 ViewPager2FragmentStateAdapter,您需要进行以下更改:

Fragment保持不变。

适配器在功能上是相同的,但有一些更新的方法:

public class SectionsStateAdapter extends FragmentStateAdapter {

    HashMap<Integer, Runnable> tabChangedCallbacks = new HashMap<>();

    ...

    public void onTabChanged(int position) {
        tabChangedCallbacks.get(position).run();
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        PlaceholderFragment fragment = PlaceholderFragment.newInstance(position + 1);
        tabChangedCallbacks.put(position, fragment.getOnTabChangedListener());
        return fragment;
    }

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

    ...
}

activity变化最大:

public class MainActivity extends AppCompatActivity {

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        ...

        final SectionsStateAdapter sectionsStateAdapter = new SectionsStateAdapter(this);
        final ViewPager2 viewPager = findViewById(R.id.view_pager);
        viewPager.setAdapter(sectionsStateAdapter);
        TabLayout tabs = findViewById(R.id.tabs);
        // connect the TabLayout to the ViewPager2
        new TabLayoutMediator(tabs, viewPager, new TabLayoutMediator.TabConfigurationStrategy() {
            @Override
            public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
                // set tab text, etc
            }
        }).attach();
        // set the change listener on the ViewPager2
        viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                sectionsStateAdapter.onTabChanged(position);
            }
        });

        ...

    }
}

请注意,唯一真正的变化是 ViewPager2 不需要 TabLayout 来处理页面更改。如果需要,您仍然可以使用 TabLayout.addOnTabSelectedListener(),但您必须实现自己的 TabLayout.OnTabSelecterListener,因为 TabLayout.ViewPagerOnTabSelectedListener 不适用于 ViewPager2

例如:

tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                sectionsStateAdapter.onTabChanged(tab.getPosition());
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });

@star4z 的解决方案应该可行,但还有一个更简单的选择。您可以改为使用 FragmentPagerAdapter 并将其行为设置为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT.

每次您更改选项卡时,都会调用 onResume,您可以在那里调用您的 scrollView.smoothScrollTo(0, 0);

public class MyAdapter extends FragmentPagerAdapter {
...
        public MyAdapter(FragmentManager fm) {
            super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
        }
...
    
}

然后在你的 Fragment:

public class MyFragment extends Fragment {

...

    public void onResume() {
        super.onResume();
        scrollView.smoothScrollTo(0, 0);
    }

...

}