片段内的 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());
}
});
...
}
编辑
原理是一样的,但是要使用 ViewPager2
和 FragmentStateAdapter
,您需要进行以下更改:
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);
}
...
}
我有一个带有 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());
}
});
...
}
编辑
原理是一样的,但是要使用 ViewPager2
和 FragmentStateAdapter
,您需要进行以下更改:
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);
}
...
}