ViewPager2在滑动时闪烁/重新加载

huangapple go评论72阅读模式
英文:

ViewPager2 flashes/reloads on swipe

问题

public class MainActivity extends FragmentActivity {

    ActivityMainBinding viewBinding;

    MyPager adapter1, adapter2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        List<String> data = populateData();

        adapter1 = new MyPager(this, data);
        adapter2 = new MyPager(this, data);

        viewBinding.viewPager.setAdapter(adapter1);
        viewBinding.viewPager2.setAdapter(adapter2);

        viewBinding.viewPager2.setOffscreenPageLimit(data.size());
        viewBinding.viewPager.setOffscreenPageLimit(data.size());

        viewBinding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {

            @Override
            public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
                viewBinding.viewPager2.scrollTo(positionOffsetPixels, 0);
            }

            @Override
            public void onPageScrollStateChanged(final int state) {
                viewBinding.viewPager2.setCurrentItem(viewBinding.viewPager.getCurrentItem(), true);
            }
        });
        viewBinding.viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {

            @Override
            public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
                viewBinding.viewPager.scrollTo(positionOffsetPixels, 0);
            }

            @Override
            public void onPageScrollStateChanged(final int state) {
                viewBinding.viewPager.setCurrentItem(viewBinding.viewPager2.getCurrentItem(), true);
            }
        });

    }
    private List<String> populateData() {
        List<String> data = new ArrayList<>();
        for (int x = 0; x < 10; x++) {
            String derril = "derril " + x;
            data.add(derril);
        }
        return data;
    }
}
public class MyPager extends FragmentStateAdapter {

    List<String> data;

    public MyPager(@NonNull FragmentActivity fragmentActivity, List<String> data) {
        super(fragmentActivity);
        this.data = data;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return DerrilFragment.newInstance(data.get(position));
    }

    @Override
    public int getItemCount() {
        return data.size();
    }
}
public class DerrilFragment extends Fragment {

    PizzaBinding viewBinding;

    String data;

    private DerrilFragment(String data) {
        this.data = data;
    }

    public static DerrilFragment newInstance(String data) {
        return new DerrilFragment(data);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        viewBinding = DataBindingUtil.inflate(inflater, R.layout.pizza, container, false);

        viewBinding.text.setText(data);

        return viewBinding.getRoot();

    }
}
英文:

I'm trying to build an Android application using the new ViewPager2. I've added two ViewPagers, separated by a view, and when you swipe, both viewpagers should move. Both view pagers are moving correctly but upon completion of the gesture, the swiped view flashes and the non swiped view reloads, as demonstrated by the attached gif. Here is my code for the Activity, ViewPagerAdapter, and Fragment. Any help is appreciated

ViewPager2在滑动时闪烁/重新加载

    public class MainActivity extends FragmentActivity {
ActivityMainBinding viewBinding;
MyPager adapter1, adapter2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
List&lt;String&gt; data = populateData();
adapter1 = new MyPager(this, data);
adapter2 = new MyPager(this, data);
viewBinding.viewPager.setAdapter(adapter1);
viewBinding.viewPager2.setAdapter(adapter2);
viewBinding.viewPager2.setOffscreenPageLimit(data.size());
viewBinding.viewPager.setOffscreenPageLimit(data.size());
viewBinding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
viewBinding.viewPager2.scrollTo(positionOffsetPixels, 0);
}
@Override
public void onPageScrollStateChanged(final int state) {
viewBinding.viewPager2.setCurrentItem(viewBinding.viewPager.getCurrentItem(), true);
}
});
viewBinding.viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
viewBinding.viewPager.scrollTo(positionOffsetPixels, 0);
}
@Override
public void onPageScrollStateChanged(final int state) {
viewBinding.viewPager.setCurrentItem(viewBinding.viewPager2.getCurrentItem(), true);
}
});
}
private List&lt;String&gt; populateData() {
List&lt;String&gt; data = new ArrayList&lt;&gt;();
for (int x = 0; x &lt; 10; x++) {
String derril = &quot;derril &quot; + x;
data.add(derril);
}
return data;
}
}
public class MyPager extends FragmentStateAdapter {
List&lt;String&gt; data;
public MyPager(@NonNull FragmentActivity fragmentActivity, List&lt;String&gt; data) {
super(fragmentActivity);
this.data = data;
}
@NonNull
@Override
public Fragment createFragment(int position) {
return DerrilFragment.newInstance(data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
}
public class DerrilFragment extends Fragment {
PizzaBinding viewBinding;
String data;
private DerrilFragment(String data) {
this.data = data;
}
public static DerrilFragment newInstance(String data) {
return new DerrilFragment(data);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
viewBinding = DataBindingUtil.inflate(inflater, R.layout.pizza, container, false);
viewBinding.text.setText(data);
return viewBinding.getRoot();
}
}

答案1

得分: 1

你可以通过 RecyclerView 来实现。

class MainActivity : AppCompatActivity() {
    private var isFirstFocused = false

    private val helper: SnapHelper = LinearSnapHelper()
    private val helper2: SnapHelper = LinearSnapHelper()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        recycler1.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                if (isFirstFocused) recycler2.scrollBy(dx, dy)
            }
        })
        recycler2.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                if (!isFirstFocused) recycler1.scrollBy(dx, dy)
            }
        })
        recycler2.setOnTouchListener { v, event ->
            if (event.action != null && event.action == MotionEvent.ACTION_DOWN) {
                isFirstFocused = false
                return@setOnTouchListener true
            }
            return@setOnTouchListener false
        }
        recycler1.setOnTouchListener { v, event ->
            if (event.action != null && event.action == MotionEvent.ACTION_DOWN) {
                isFirstFocused = true
                return@setOnTouchListener true
            }
            return@setOnTouchListener false
        }
        helper.attachToRecyclerView(recycler1)
        helper2.attachToRecyclerView(recycler2)
        val manager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
        val manager2 = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
        recycler1.layoutManager = manager
        recycler2.layoutManager = manager2
        recycler2.adapter = Adapter(items)
        recycler1.adapter = Adapter(items)
    }

    private val items = listOf("One", "Two", "Three")

}

class Adapter(val list: List<String>) : RecyclerView.Adapter<Adapter.Holder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val view = LayoutInflater.from(parent.context).inflate(
            R.layout.activity_listview,
            parent,
            false
        )
        return Holder(view)
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        holder.itemView.text_view.text = list[position]
    }

    override fun getItemCount() = list.size

    class Holder(view: View) : RecyclerView.ViewHolder(view)
}
英文:

You can implement by RecyclerView.

class MainActivity : AppCompatActivity() {
private var isFirstFocused = false
private val helper: SnapHelper = LinearSnapHelper()
private val helper2: SnapHelper = LinearSnapHelper()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recycler1.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (isFirstFocused) recycler2.scrollBy(dx, dy)
}
})
recycler2.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (!isFirstFocused) recycler1.scrollBy(dx, dy)
}
})
recycler2.setOnTouchListener { v, event -&gt;
if (event.action  != null &amp;&amp; event.action == MotionEvent.ACTION_DOWN ) {
isFirstFocused = false
return@setOnTouchListener  true
}
return@setOnTouchListener false
}
recycler1.setOnTouchListener { v, event -&gt;
if (event.action  != null &amp;&amp; event.action == MotionEvent.ACTION_DOWN ) {
isFirstFocused = true
return@setOnTouchListener  true
}
return@setOnTouchListener false
}
helper.attachToRecyclerView(recycler1)
helper2.attachToRecyclerView(recycler2)
val manager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
val manager2 = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
recycler1.layoutManager = manager
recycler2.layoutManager = manager2
recycler2.adapter = Adapter(items)
recycler1.adapter = Adapter(items)
}
private val items = listOf(&quot;One&quot;, &quot;Two&quot;, &quot;Three&quot;)
}
class Adapter(val list: List&lt;String&gt;) : RecyclerView.Adapter&lt;Adapter.Holder&gt;() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.activity_listview,
parent,
false
)
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.itemView.text_view.text = list[position]
}
override fun getItemCount() = list.size
class Holder(view: View) : RecyclerView.ViewHolder(view)
}

答案2

得分: 0

你可以尝试使用 adapter.setOffscreenPageLimit = n。它设置应保留在当前可见页面两侧的页面数量。超出此限制的页面将在需要时从适配器中重新创建,如果您的页面数量有限的话。更多详细信息请参见此处

英文:

You can try to use adapter.setOffscreenPageLimit = n. It sets the number of pages that should be retained to either side of the currently visible page(s). Pages beyond this limit will be recreated from the adapter when needed If you have a limited number of pages.
More details are here.

答案3

得分: 0

在您的setCurrentItem()用法中(位于onPageScrollStateChanged()下方),将第二个参数设置为false

这将在您以编程方式设置页面时在ViewPager中禁用动画(不是永久性的),这就是您看到类似闪烁的动画的原因。


要了解原因:

考虑两个ViewPager,A和B

您在A中从一页滚动到另一页,这会在ViewPager B中引发相同的滚动。一旦您在A中完全从一页滚动到另一页,currentItem会在A内部设置,但不会用于B

现在,通过调用setCurrentItem([index], true),您通知B页面已更改,而不管您的滚动如何,都会播放页面更改动画,从而引发闪烁(您引发的滚动被重置为正常状态,并开始一页到另一页的页面动画)。

英文:

In your setCurrentItem() usages (under onPageScrollStateChanged()), set the second parameter to false

This will disable animation (not permanently) in the ViewPager when you set a page programatically, which is the reason you see the flash-like animation.

<hr>

To know why:

Consider two ViewPagers A & B

You scroll from one page to another in A, which induces the same scroll in ViewPager B. Once you completely scroll from one page to another in A, the currentItem is internally set by A, but not for B.

Now, you inform B about the page change by calling setCurrentItem([index], true), which, irrespective of your scroll, will animate the page change introducing the flash (your induced scroll is reset to normal, and page animation for one page to another is started)

答案4

得分: 0

这看起来有点可疑,你正在每次传递的state内部调用onPageScrollStateChanged中的setCurrentItem... 尝试添加一些条件判断,仅在state==ViewPager2.SCROLL_STATE_IDLE时才调用setCurrentItem

状态列表:

  - ViewPager2.SCROLL_STATE_IDLE
  - ViewPager2.SCROLL_STATE_DRAGGING
  - ViewPager2.SCROLL_STATE_SETTLING

ViewPager2.SCROLL_STATE_DRAGGING状态在onPageScrolled方法中处理(共享位置偏移,即通过手指拖动)。

ViewPager2.SCROLL_STATE_SETTLINGViewPager2自动滚动(自动对齐)到最近的页面 - 在这里,我不确定通过代码(使用scrollTo)拖动ViewPager2是否也会进入此state,从而产生相同的对齐行为(因此也会调用onPageScrolled?)... 值得测试,在其中放置一些日志。

另外:你正在拖动一个ViewPager2,在onPageScrolled中对第二个ViewPager2调用scrollTo。这可能会导致在这第二个监听器上调用onPageScrolled,它会调用第一个ViewPager2scrollTo方法,依此类推... 形成循环。考虑在用户开始拖动一个ViewPager2时动态取消注册监听器,并在移动结束时重新注册监听器(SCROLL_STATE_IDLE状态)。

英文:

this looks a bit suspicious that you are calling setCurrentItem inside onPageScrollStateChanged for every passed state... try to add some if and setCurrentItem only when state==ViewPager2.SCROLL_STATE_IDLE

list of states:

  • ViewPager2.SCROLL_STATE_IDLE
  • ViewPager2.SCROLL_STATE_DRAGGING
  • ViewPager2.SCROLL_STATE_SETTLING

ViewPager2.SCROLL_STATE_DRAGGING state is handled in onPageScrolled method (sharing position offset aka. dragging by finger)

ViewPager2.SCROLL_STATE_SETTLING is auto-scroll (snap) of ViewPager2 to closest page - in here I'm not shure that ViewPager2 dragged "by code" (sharing position with scrollTo) will also enter in this state causing same snap behavior (so also onPageScrolled?) ... worth testing, put some logs in there

also: you are dragging one ViewPager2 and in onPageScrolled you are calling scrollTo on second ViewPager2. this may lead to call onPageScrolled call on this second listener, which is calling first ViewPager2 scrollTo method and so on... looped. consider dynamic unregistering listener when user will start drag one ViewPager2 and reregistering when movement ends (SCROLL_STATE_IDLE state)

答案5

得分: 0

我认为问题在于两个ViewPagers的onPageScrolled方法中,您正在滚动另一个pager。因此,两个页面可能会同时相互滚动。例如,如果您滚动ViewPager_1,那么它的onPageScrolled方法将会滚动ViewPager_2,而由于ViewPager_2现在已经滚动,所以它的onPageScrolled方法将会被调用,从而滚动ViewPager_1,反之亦然。
因此,您可能需要考虑应用以下检查:如果触摸是由ViewPager_1保持的,则不应执行ViewPager_2的onPageScrolled方法中的代码。或者,如果您想避免这些检查,那么在一个ViewPager保持触摸时,可以使用registerOnPageChangeCallback/unregisterOnPageChangeCallback来注册/注销另一个ViewPager的监听器。

@Override
public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
    viewBinding.viewPager.scrollTo(positionOffsetPixels, 0);
}
英文:

I think the problem is that in 'onPageScrolled' method of both ViewPagers you are scrolling other pager. So, both pages might be scrolling each other at the same. For example, if you are scrolling ViewPager_1 then its onPageScrolled method will scroll ViewPager_2 and as ViewPager_2 is now scrolled then its onPageScrolled method will be called and that will scroll ViewPager_1 and vice versa.
So, You may need to consider applying the check that if touch is holded ny ViewPager_1 then code inside onPageScrolled method of ViewPager_2 should not be executed. Or if you want to avoid those checks. Then registerOnPageChangeCallback/unregisterOnPageChangeCallback the listener of other ViewPager when one holds the touch.

            @Override
public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
viewBinding.viewPager.scrollTo(positionOffsetPixels, 0);
}

huangapple
  • 本文由 发表于 2020年9月25日 23:26:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/64067069.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定