Search code examples
androidandroid-viewpager2

can't get ScrollView from ViewPager2 in OnPageChangeCallBack


I have a ViewPager2 contains ScrollView.
What the problem is, when I try to get ScrollView of current page in onPageSelected(), it doesn't work. Here I'd like to set previous scrollY to the ScrollView when user back to see selected page. (because scrollY is reset for some reason before that) My code is below.

ViewPagerAdapter.java (edited)

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

[REVICED]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 (edited)

<?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>

Any advice is truly appreciated. Thank you for reading this long question.


Solution

  • onPageSelected will be called way earlier than selected page drawing mechanism, before any onCreateViewHolder and onBindViewHolder. I would advise you to "incject" somehow your data into your "page" (is it View or whole Fragment? that makes big difference) and in your case set this scroll position inside onBindViewHolder. This will make rendering of RecyclerView draw even first frame in correct scrolled position. Your way, even when you will wait some time for RecyclerView drawing, will make that onBindViewHolder will draw first frame on 0 scroll and your method will scroll a bit in next frames - this will be visible like some blink or fast-autoscroll (I think this behavior isn't intended)

    edit - add proper method for setting scroll for your Adapter even before super call

    @Override
    public void onPageSelected(int position) {
        int positionY[] = getScrollFromViewModel();
        adapter.setScrollForPosition(positionY[position]);
        super.onPageSelected(position);
        ...
    

    for adapter

    private final HashMap<Integer, Integer> scrollYHistory = new HashMap<>();
    
    public void setScrollForPosition(int position, int scrollY){
        scrollYHistory.put(position, scrollY);
    }
    

    use stored scrollY value in onBindViewHolder and clean entry in your 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);
        ...
    

    also remove unnecessary reference to MainActivity main in adapter's constructor and variable inside, it already gets Context reference and doesn't (shouldn't) need to know which Activity is creating it

    edit - expanding comment:

    instead of calling scrollView.setScrollY straight inside onBindViewHolder you should set up your TextView at first, then wait for rendering it and then scrolling to proper position

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