无法在 OnPageChangeCallBack 中从 ViewPager2 获取 ScrollView
can't get ScrollView from ViewPager2 in OnPageChangeCallBack
我有一个包含 ScrollView 的 ViewPager2。
问题是,当我尝试在 onPageSelected() 中获取当前页面的 ScrollView 时,它不起作用。
在这里,当用户返回查看所选页面时,我想将以前的 scrollY 设置为 ScrollView。 (因为在此之前由于某种原因重置了 scrollY)
我的代码如下。
ViewPagerAdapter.java(已编辑)
public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.ViewHolder> {
private LayoutInflater mInflater;
private List<String> mText;
private ViewPager2 pager2;
MainActivity main;
public ViewPagerAdapter(Context context, List<String> data, ViewPager2 pager2, MainActivity main){
this.mInflater = LayoutInflater.from(context);
this.mText = data;
this.pager2 = pager2;
this.main = main;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View view = mInflater.inflate(R.layout.tab_scroll_item, parent, false);
return new ViewPagerAdapter.ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, int position){
holder.scrollView.setTag("scv_tab" + position);
holder.scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
@Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if(scrollY != 0) {
main.setScrollY(scrollY, getPosition());
}
System.out.println("onScrollChanged : " + scrollY);
}
});
holder.textView.setEnabled(false);
holder.textView.setEnabled(true);
holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, applyTextSize());
holder.textView.setText(mText.get(position));
holder.textView.setTag("tv_tab" + position);
}
@Override
public int getItemCount(){
return mText.size();
}
protected int getPosition(){
return pager2.getCurrentItem();
}
public class ViewHolder extends RecyclerView.ViewHolder{
ScrollView scrollView;
TextView textView;
public ViewHolder(View itemView){
super(itemView);
scrollView = itemView.findViewById(R.id.tab_scroll);
textView = itemView.findViewById(R.id.tab_textview);
}
}
[修改]onBindViewHolder
@Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, final int position){
holder.scrollView.setTag("scv_tab" + position);
holder.scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
@Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if(scrollY != 0) {
main.storeScrollY(scrollY, position);
}
System.out.println("onScrollChanged : " + scrollY);
}
});
int y = main.retrieveScrollY(position);
holder.scrollView.setScrollY(y);
holder.textView.setEnabled(false);
holder.textView.setEnabled(true);
holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, applyTextSize());
holder.textView.setText(mText.get(position));
holder.textView.setTag("tv_tab" + position);
}
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
...
mPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if(!searchView.isIconified()){
searchView.setIconified(true);
}
if(highlightModel.getHighlitedOrNot(position)){
searchText.deleteTextHighlight(position);
highlightModel.setHighlitedOrNot(position, false);
}
int positionY[] = getScrollFromViewModel();
ScrollView sv = findScrollView(); // HERE IS THE PROBLEM
if(sv != null) {
sv.setScrollY(positionY[position]);
}else{
System.out.println("sv is null"); // ALWAYS SHOWS NULL
}
}
});
...
searchView = (SearchView) findViewById(R.id.searchview);
searchView.setOnSearchClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
fab.setVisibility(View.VISIBLE);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int actualResult = searchText.scrollToHighlightedWord(findTextView()
, findScrollView() // here findScrollView() works perfectly.
, searchResultIndex);
if(actualResult == (searchResultIndex + 1)) {
++searchResultIndex;
}else if(actualResult == searchResultIndex){
showToastAtMain("last word");
}else{
forOnClose();
}
}
});
}
});
private ScrollView findScrollView(){
ScrollView sv = mPager2.findViewWithTag("scv_tab" + mPager2.getCurrentItem());
return sv;
}
tab_scroll_item.xml(已编辑)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="@+id/tab1_layout">
<ScrollView
android:id="@+id/tab_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tab_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:lineSpacingExtra="7dp"
android:textIsSelectable="true"
android:paddingStart="9dp"
android:paddingTop="9dp"
android:paddingEnd="9dp"/>
</ScrollView>
</LinearLayout>
非常感谢任何建议。感谢您阅读这么长的问题。
onPageSelected
将比选定的页面绘制机制更早调用,在任何 onCreateViewHolder
和 onBindViewHolder
之前。我建议你以某种方式将你的数据“注入”到你的“页面”中(它是 View
还是整个 Fragment
?这有很大的不同)并且在你的情况下将这个滚动位置设置在 onBindViewHolder
.这将使 RecyclerView
的渲染甚至在正确的滚动位置绘制第一帧。你的方式,即使你会等待一段时间 RecyclerView
绘图,也会使 onBindViewHolder
将在 0 滚动上绘制第一帧,你的方法将在下一帧滚动一点 - 这将像一些一样可见闪烁或快速自动滚动(我认为这种行为不是故意的)
编辑 - 添加正确的方法来为您的 Adapter
设置滚动,甚至在 super
调用
之前
@Override
public void onPageSelected(int position) {
int positionY[] = getScrollFromViewModel();
adapter.setScrollForPosition(positionY[position]);
super.onPageSelected(position);
...
适配器
private final HashMap<Integer, Integer> scrollYHistory = new HashMap<>();
public void setScrollForPosition(int position, int scrollY){
scrollYHistory.put(position, scrollY);
}
使用 onBindViewHolder
中存储的 scrollY 值并清除 HashMap
中的条目
@Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, final int position){
Integer scrollY = scrollYHistory.remove(position);
if (scrollY == null) scrollYa = 0; // may be null if not stored yet!
holder.scrollView.setScrollY(scrollY);
...
还删除了适配器构造函数和内部变量中对 MainActivity main
的不必要引用,它已经获得了 Context
引用并且不需要(不应该)知道哪个 Activity
是创建它
编辑 - 扩展评论:
与其直接在 onBindViewHolder
中调用 scrollView.setScrollY
,不如先设置 TextView
,然后等待渲染,然后滚动到正确的位置
@Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, final int position){
...
holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, applyTextSize());
holder.textView.setText(mText.get(position));
...
holder.textView.post(new Runnable(){
// this will be called after nearest drawing
@Override
public void run() {
Integer scrollY = scrollYHistory.remove(position);
if (scrollY == null) scrollYa = 0; // may be null if not stored yet!
holder.scrollView.setScrollY(scrollY);
}
});
}
多亏了 snachmsm,我才知道 onPageSelected 会比 ViewPager2 绘图更早被调用。出于某种原因,我使用 mPager2.postDelayed().
找到了我的解决方案
添加这些后,我这里没有空的 ScrollView。
mPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
...
//Here I added onPageScrollStateChanged() and postDelayed() in it.
@Override
public void onPageScrollStateChanged(int state){
if(state == ViewPager2.SCROLL_STATE_SETTLING){
mPager2.postDelayed(new Runnable() {
@Override
public void run() {
int position = mPager2.getCurrentItem();
int y = scrollModel.getPreviousScrollY(position);
ScrollView sv = findScrollView();
sv.scrollTo(0, y);
}
}, 50);
}
}
});
}
我有一个包含 ScrollView 的 ViewPager2。
问题是,当我尝试在 onPageSelected() 中获取当前页面的 ScrollView 时,它不起作用。
在这里,当用户返回查看所选页面时,我想将以前的 scrollY 设置为 ScrollView。 (因为在此之前由于某种原因重置了 scrollY)
我的代码如下。
ViewPagerAdapter.java(已编辑)
public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.ViewHolder> {
private LayoutInflater mInflater;
private List<String> mText;
private ViewPager2 pager2;
MainActivity main;
public ViewPagerAdapter(Context context, List<String> data, ViewPager2 pager2, MainActivity main){
this.mInflater = LayoutInflater.from(context);
this.mText = data;
this.pager2 = pager2;
this.main = main;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View view = mInflater.inflate(R.layout.tab_scroll_item, parent, false);
return new ViewPagerAdapter.ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, int position){
holder.scrollView.setTag("scv_tab" + position);
holder.scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
@Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if(scrollY != 0) {
main.setScrollY(scrollY, getPosition());
}
System.out.println("onScrollChanged : " + scrollY);
}
});
holder.textView.setEnabled(false);
holder.textView.setEnabled(true);
holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, applyTextSize());
holder.textView.setText(mText.get(position));
holder.textView.setTag("tv_tab" + position);
}
@Override
public int getItemCount(){
return mText.size();
}
protected int getPosition(){
return pager2.getCurrentItem();
}
public class ViewHolder extends RecyclerView.ViewHolder{
ScrollView scrollView;
TextView textView;
public ViewHolder(View itemView){
super(itemView);
scrollView = itemView.findViewById(R.id.tab_scroll);
textView = itemView.findViewById(R.id.tab_textview);
}
}
[修改]onBindViewHolder
@Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, final int position){
holder.scrollView.setTag("scv_tab" + position);
holder.scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
@Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if(scrollY != 0) {
main.storeScrollY(scrollY, position);
}
System.out.println("onScrollChanged : " + scrollY);
}
});
int y = main.retrieveScrollY(position);
holder.scrollView.setScrollY(y);
holder.textView.setEnabled(false);
holder.textView.setEnabled(true);
holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, applyTextSize());
holder.textView.setText(mText.get(position));
holder.textView.setTag("tv_tab" + position);
}
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
...
mPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if(!searchView.isIconified()){
searchView.setIconified(true);
}
if(highlightModel.getHighlitedOrNot(position)){
searchText.deleteTextHighlight(position);
highlightModel.setHighlitedOrNot(position, false);
}
int positionY[] = getScrollFromViewModel();
ScrollView sv = findScrollView(); // HERE IS THE PROBLEM
if(sv != null) {
sv.setScrollY(positionY[position]);
}else{
System.out.println("sv is null"); // ALWAYS SHOWS NULL
}
}
});
...
searchView = (SearchView) findViewById(R.id.searchview);
searchView.setOnSearchClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
fab.setVisibility(View.VISIBLE);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int actualResult = searchText.scrollToHighlightedWord(findTextView()
, findScrollView() // here findScrollView() works perfectly.
, searchResultIndex);
if(actualResult == (searchResultIndex + 1)) {
++searchResultIndex;
}else if(actualResult == searchResultIndex){
showToastAtMain("last word");
}else{
forOnClose();
}
}
});
}
});
private ScrollView findScrollView(){
ScrollView sv = mPager2.findViewWithTag("scv_tab" + mPager2.getCurrentItem());
return sv;
}
tab_scroll_item.xml(已编辑)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="@+id/tab1_layout">
<ScrollView
android:id="@+id/tab_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tab_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:lineSpacingExtra="7dp"
android:textIsSelectable="true"
android:paddingStart="9dp"
android:paddingTop="9dp"
android:paddingEnd="9dp"/>
</ScrollView>
</LinearLayout>
非常感谢任何建议。感谢您阅读这么长的问题。
onPageSelected
将比选定的页面绘制机制更早调用,在任何 onCreateViewHolder
和 onBindViewHolder
之前。我建议你以某种方式将你的数据“注入”到你的“页面”中(它是 View
还是整个 Fragment
?这有很大的不同)并且在你的情况下将这个滚动位置设置在 onBindViewHolder
.这将使 RecyclerView
的渲染甚至在正确的滚动位置绘制第一帧。你的方式,即使你会等待一段时间 RecyclerView
绘图,也会使 onBindViewHolder
将在 0 滚动上绘制第一帧,你的方法将在下一帧滚动一点 - 这将像一些一样可见闪烁或快速自动滚动(我认为这种行为不是故意的)
编辑 - 添加正确的方法来为您的 Adapter
设置滚动,甚至在 super
调用
@Override
public void onPageSelected(int position) {
int positionY[] = getScrollFromViewModel();
adapter.setScrollForPosition(positionY[position]);
super.onPageSelected(position);
...
适配器
private final HashMap<Integer, Integer> scrollYHistory = new HashMap<>();
public void setScrollForPosition(int position, int scrollY){
scrollYHistory.put(position, scrollY);
}
使用 onBindViewHolder
中存储的 scrollY 值并清除 HashMap
@Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, final int position){
Integer scrollY = scrollYHistory.remove(position);
if (scrollY == null) scrollYa = 0; // may be null if not stored yet!
holder.scrollView.setScrollY(scrollY);
...
还删除了适配器构造函数和内部变量中对 MainActivity main
的不必要引用,它已经获得了 Context
引用并且不需要(不应该)知道哪个 Activity
是创建它
编辑 - 扩展评论:
与其直接在 onBindViewHolder
中调用 scrollView.setScrollY
,不如先设置 TextView
,然后等待渲染,然后滚动到正确的位置
@Override
public void onBindViewHolder(final ViewPagerAdapter.ViewHolder holder, final int position){
...
holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, applyTextSize());
holder.textView.setText(mText.get(position));
...
holder.textView.post(new Runnable(){
// this will be called after nearest drawing
@Override
public void run() {
Integer scrollY = scrollYHistory.remove(position);
if (scrollY == null) scrollYa = 0; // may be null if not stored yet!
holder.scrollView.setScrollY(scrollY);
}
});
}
多亏了 snachmsm,我才知道 onPageSelected 会比 ViewPager2 绘图更早被调用。出于某种原因,我使用 mPager2.postDelayed().
找到了我的解决方案
添加这些后,我这里没有空的 ScrollView。
mPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
...
//Here I added onPageScrollStateChanged() and postDelayed() in it.
@Override
public void onPageScrollStateChanged(int state){
if(state == ViewPager2.SCROLL_STATE_SETTLING){
mPager2.postDelayed(new Runnable() {
@Override
public void run() {
int position = mPager2.getCurrentItem();
int y = scrollModel.getPreviousScrollY(position);
ScrollView sv = findScrollView();
sv.scrollTo(0, y);
}
}, 50);
}
}
});
}