0

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.

ntsrng
  • 3
  • 4

2 Answers2

0

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);
        }
    });
}
snachmsm
  • 17,866
  • 3
  • 32
  • 74
  • Thanks for your answer. I edited my question and code following your advice. Are there right code for "set this scroll position inside onBindViewHolder" ? I'm asking this because the scrollview doesn't scroll to ideal position when UI updated, even though "y" got correct value.... (I deleted code in MainActivity which corresponds to setScroll. ) – ntsrng Aug 02 '20 at 03:24
  • Thank you for your reply! I tried your solution, and I'm not sure but scrollView is not scrolled to ideal position. (also y got correct value thanks to scrollYHistory. probably I'm stil missing something) I was a little in a hurry and decided to choose other solution. Many Thanks for your help again! – ntsrng Aug 04 '20 at 14:05
  • you can't be shure that after 50 ms whole layout will be drawn. this may cause some rare `NullPointerException`s on slower devices. besides that this method causes small blink (scroll-jump) especially on fastest devices, as first frame would be drawn in less than 17 ms (1 sec / 60 frames) or even less (some devices have 90 Hz or 120 Hz screen) and you are scrolling in 4th (at least) frame. frames rendered at first will be drawn with scrollY=0 – snachmsm Aug 05 '20 at 06:34
  • Okay. That's true. So could you give me some hints for solving this in my case? I don't think the code you suggested was incorrect and scrollY always got correct value before tthis question and also when I tried your code. The code in onBindViewHolder seems to be working fine except for scroll. – ntsrng Aug 05 '20 at 12:31
  • well, I see your `ScrollView` contains `TextView` which may have different length, thus size/height (`wrap_content`). and you additionaly change font size in there... this child view may also need some time for being drawn, so you have to, again, wait for rendering. fonts rendering is pretty tough and heavy task, so maybe it tooks more time than 1 frame duration? set your scroll position when you are shure that all childs of `ScrollView` are drawn or at least measured (final height). this is proper place for using `post` method (not `postDelayed`), check out edit – snachmsm Aug 05 '20 at 13:00
  • It works! :D I really appriciate for your all help so far! – ntsrng Aug 09 '20 at 01:48
0

Thanks to snachmsm, I got to know that onPageSelected will be called way earlier than ViewPager2 drawing. And for some reason I found my solution using mPager2.postDelayed().
After adding these, I got NO null ScrollView here.

        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);
                }
            }
        });
    }
ntsrng
  • 3
  • 4