问题:onResume()
中的片段ViewPager
在片段实际可见之前被触发。
例如,我有两个ViewPager
和FragmentPagerAdapter
的片段。第二个片段仅供授权用户使用,我需要让用户在片段可见时登录(使用警告对话框)。
但是ViewPager
在第一个片段可见时创建第二个片段,以便缓存第二个片段,并在用户开始滑动时使其可见。
因此onResume()
事件在第二个片段变为可见之前就被触发了。这就是为什么我试图找到一个事件,当第二个片段变得可见时,它会在适当的时刻显示一个对话框。
如何做到这一点?
答案 0 :(得分:553)
如何确定片段何时在ViewPager中可见
您可以通过覆盖setUserVisibleHint
:
Fragment
来执行以下操作
public class MyFragment extends Fragment {
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
}
else {
}
}
}
答案 1 :(得分:129)
这似乎可以恢复您期望的正常onResume()
行为。它可以很好地按下主页键离开应用程序,然后重新进入应用程序。 onResume()
不会连续两次调用。
@Override
public void setUserVisibleHint(boolean visible)
{
super.setUserVisibleHint(visible);
if (visible && isResumed())
{
//Only manually call onResume if fragment is already visible
//Otherwise allow natural fragment lifecycle to call onResume
onResume();
}
}
@Override
public void onResume()
{
super.onResume();
if (!getUserVisibleHint())
{
return;
}
//INSERT CUSTOM CODE HERE
}
答案 2 :(得分:61)
以下是使用onPageChangeListener
的另一种方式:
ViewPager pager = (ViewPager) findByViewId(R.id.viewpager);
FragmentPagerAdapter adapter = new FragmentPageAdapter(getFragmentManager);
pager.setAdapter(adapter);
pager.setOnPageChangeListener(new OnPageChangeListener() {
public void onPageSelected(int pageNumber) {
// Just define a callback method in your fragment and call it like this!
adapter.getItem(pageNumber).imVisible();
}
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
}
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
});
答案 3 :(得分:53)
setUserVisibleHint()
有时在 onCreateView()
之前被称为,有时会导致麻烦。
要解决此问题,您需要检查isResumed()
内部setUserVisibleHint()
方法。但在这种情况下,我意识到如果片段恢复并且可见,setUserVisibleHint()
仅被称为 ,而不是在创建时。
因此,如果您想在片段为visible
时更新某些内容,请将更新功能放在onCreate()
和setUserVisibleHint()
中:
@Override
public View onCreateView(...){
...
myUIUpdate();
...
}
....
@Override
public void setUserVisibleHint(boolean visible){
super.setUserVisibleHint(visible);
if (visible && isResumed()){
myUIUpdate();
}
}
更新:我有时会意识到myUIUpdate()
有两次被调用,原因是,如果您有3个标签,并且此代码位于第二个标签页上,则当您首次打开第一个标签页时,即使它不可见,也会创建第二个选项卡,并调用myUIUpdate()
。然后,当您向第二个标签滑动时,系统会调用myUIUpdate()
中的if (visible && isResumed())
,因此,myUIUpdate()
可能会在一秒内被调用两次。
!visible
中的其他问题 setUserVisibleHint
被调用1)当你离开片段屏幕时2)在创建之前,当你切换到片段时屏幕第一次。
解决方案:
private boolean fragmentResume=false;
private boolean fragmentVisible=false;
private boolean fragmentOnCreated=false;
...
@Override
public View onCreateView(...){
...
//Initialize variables
if (!fragmentResume && fragmentVisible){ //only when first time fragment is created
myUIUpdate();
}
...
}
@Override
public void setUserVisibleHint(boolean visible){
super.setUserVisibleHint(visible);
if (visible && isResumed()){ // only at fragment screen is resumed
fragmentResume=true;
fragmentVisible=false;
fragmentOnCreated=true;
myUIUpdate();
}else if (visible){ // only at fragment onCreated
fragmentResume=false;
fragmentVisible=true;
fragmentOnCreated=true;
}
else if(!visible && fragmentOnCreated){// only when you go out of fragment screen
fragmentVisible=false;
fragmentResume=false;
}
}
说明:
fragmentResume
,fragmentVisible
:确保myUIUpdate()
中的onCreateView()
仅在片段创建且可见时调用,而不是在恢复时调用。当您处于第一个选项卡时,它也解决了问题,即使它不可见,也会创建第二个选项卡。这解决了这个问题,并检查在onCreate
时是否可以看到片段屏幕。
fragmentOnCreated
:确保片段不可见,并且在第一次创建片段时不会调用。所以现在这个if子句只有在你滑出片段时才被调用。
<强>更新强>
您可以将所有这些代码放在BaseFragment
代码like this中并覆盖方法。
答案 4 :(得分:24)
package com.example.com.ui.fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.com.R;
public class SubscribeFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_subscribe, container, false);
return view;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
// called here
}
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
}
答案 5 :(得分:23)
要检测Fragment
中可见的ViewPager
,我确信仅使用 setUserVisibleHint
是不够的。
这是我的解决方案,以检查片段是可见还是不可见。首先,当启动viewpager时,在页面之间切换,转到另一个activity / fragment / background / foreground`
public class BaseFragmentHelpLoadDataWhenVisible extends Fragment {
protected boolean mIsVisibleToUser; // you can see this variable may absolutely <=> getUserVisibleHint() but it not. Currently, after many test I find that
/**
* This method will be called when viewpager creates fragment and when we go to this fragment background or another activity or fragment
* NOT called when we switch between each page in ViewPager
*/
@Override
public void onStart() {
super.onStart();
if (mIsVisibleToUser) {
onVisible();
}
}
@Override
public void onStop() {
super.onStop();
if (mIsVisibleToUser) {
onInVisible();
}
}
/**
* This method will called at first time viewpager created and when we switch between each page
* NOT called when we go to background or another activity (fragment) when we go back
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
mIsVisibleToUser = isVisibleToUser;
if (isResumed()) { // fragment have created
if (mIsVisibleToUser) {
onVisible();
} else {
onInVisible();
}
}
}
public void onVisible() {
Toast.makeText(getActivity(), TAG + "visible", Toast.LENGTH_SHORT).show();
}
public void onInVisible() {
Toast.makeText(getActivity(), TAG + "invisible", Toast.LENGTH_SHORT).show();
}
}
<强>说明强> 你可以仔细检查下面的logcat,然后我想你可能知道为什么这个解决方案会起作用
首次发布
Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment3: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment1: setUserVisibleHint: isVisibleToUser=true isResumed=false // AT THIS TIME isVisibleToUser=true but fragment still not created. If you do something with View here, you will receive exception
Fragment1: onCreateView
Fragment1: onStart mIsVisibleToUser=true
Fragment2: onCreateView
Fragment3: onCreateView
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=false
转到第2页
Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment2: setUserVisibleHint: isVisibleToUser=true isResumed=true
转到第3页
Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment3: setUserVisibleHint: isVisibleToUser=true isResumed=true
转到后台:
Fragment1: onStop mIsVisibleToUser=false
Fragment2: onStop mIsVisibleToUser=false
Fragment3: onStop mIsVisibleToUser=true
转到前台
Fragment1: onStart mIsVisibleToUser=false
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=true
希望有所帮助
答案 6 :(得分:14)
覆盖setPrimaryItem()
子类中的FragmentPagerAdapter
。我使用这种方法,效果很好。
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
// This is what calls setMenuVisibility() on the fragments
super.setPrimaryItem(container, position, object);
if (object instanceof MyWhizBangFragment) {
MyWhizBangFragment fragment = (MyWhizBangFragment) object;
fragment.doTheThingYouNeedToDoOnBecomingVisible();
}
}
答案 7 :(得分:10)
public void onHiddenChanged(boolean hidden)
当片段的隐藏状态(由
isHidden()
返回)发生更改时调用。片段开始没有隐藏;只要片段从那里改变状态,就会调用它。参数
hidden
-boolean
:如果片段现在隐藏则为True,如果片段不可见则为false。
答案 8 :(得分:6)
在ViewPager2
版的ViewPager
和androidx.fragment:fragment:1.1.0
中,您可以仅使用onPause
和onResume
回调来确定用户当前可见的片段。当片段可见时调用onResume
回调,当片段不可见时调用onPause
。
对于ViewPager2,它是默认行为,但可以很容易地为旧商品ViewPager
启用相同的行为。
要在第一个ViewPager中启用此行为,您必须传递FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
参数作为FragmentPagerAdapter
构造函数的第二个参数。
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
注意:android jetpack的新版本Fragment中不推荐使用带有一个参数的setUserVisibleHint()
方法和FragmentPagerAdapter
构造函数。
答案 9 :(得分:5)
这只对我有用!!并且setUserVisibleHint(...)
现在已被弃用(我在文档末尾附加了文档),这意味着其他任何答案都已弃用;-)
public class FragmentFirewall extends Fragment {
/**
* Required cause "setMenuVisibility(...)" is not guaranteed to be
* called after "onResume()" and/or "onCreateView(...)" method.
*/
protected void didVisibilityChange() {
Activity activity = getActivity();
if (isResumed() && isMenuVisible()) {
// Once resumed and menu is visible, at last
// our Fragment is really visible to user.
}
}
@Override
public void onResume() {
super.onResume();
didVisibilityChange();
}
@Override
public void setMenuVisibility(boolean visible) {
super.setMenuVisibility(visible);
didVisibilityChange();
}
}
经过测试,也可以与NaviagationDrawer
一起使用,
那里isMenuVisible()
将始终返回true
(并且onResume()
似乎足够,但我们也希望支持ViewPager
)。
setUserVisibleHint
已过时。如果覆盖此方法,则应将传入true
时实现的行为移至Fragment.onResume()
,并将传入false
时实现的行为移至Fragment.onPause()
。
答案 10 :(得分:4)
我发现onCreateOptionsMenu
和onPrepareOptionsMenu
方法仅在片段确实可见的情况下调用。我找不到任何行为与此类似的方法,我也试过OnPageChangeListener
但它不适用于这种情况,例如,我需要在onCreate
方法中初始化变量。
因此,这两种方法可用于解决此问题,特别适用于小型和短期工作。
我认为,这是更好的解决方案,但不是最好的解决方案。我将使用它,但同时等待更好的解决方案。
问候。
答案 11 :(得分:3)
setUserVisibleHint(布尔值可见)现在已被弃用,所以这是正确的解决方案
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
在版本androidx.fragment:fragment:1.1.0
的 ViewPager2 和 ViewPager 中,您可以仅使用onPause()
和onResume()
确定当前可见的片段为用户。当片段变为可见时,将调用onResume()
,而当片段变得不可见时,将调用onPause
。
要在第一个ViewPager中启用此行为,您必须传递FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
参数作为FragmentPagerAdapter
构造函数的第二个参数。
答案 12 :(得分:2)
另一个解决方案posted here overriding setPrimaryItem in the pageradapter by kris larson几乎对我有用。但是每种设置都会多次调用此方法。此外,我从片段中的视图等获得NPE,因为在调用此方法的前几次没有准备好。通过以下更改,这对我有用:
private int mCurrentPosition = -1;
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position == mCurrentPosition) {
return;
}
if (object instanceof MyWhizBangFragment) {
MyWhizBangFragment fragment = (MyWhizBangFragment) object;
if (fragment.isResumed()) {
mCurrentPosition = position;
fragment.doTheThingYouNeedToDoOnBecomingVisible();
}
}
}
答案 13 :(得分:2)
尝试一下,对我有用:
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
}else
{}
}
答案 14 :(得分:2)
focused view
检测!
这对我有用
public static boolean isFragmentVisible(Fragment fragment) {
Activity activity = fragment.getActivity();
View focusedView = fragment.getView().findFocus();
return activity != null
&& focusedView != null
&& focusedView == activity.getWindow().getDecorView().findFocus();
}
答案 15 :(得分:2)
在片段
中添加以下代码@Override
public void setMenuVisibility(final boolean visible)
{
super.setMenuVisibility(visible);
if (visible && isResumed())
{
}
}
答案 16 :(得分:2)
使用FragmentStatePagerAdapters
和3个标签时遇到了同样的问题。每当点击第一个标签时我都必须显示Dilaog,并在点击其他标签时隐藏它。
单独覆盖setUserVisibleHint()
无助于找到当前可见的片段。
从第3个标签点击时-----&gt;第一个标签。 它为第二个片段和第一个片段触发了两次。 我将它与isResumed()方法结合起来。
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
isVisible = isVisibleToUser;
// Make sure that fragment is currently visible
if (!isVisible && isResumed()) {
// Call code when Fragment not visible
} else if (isVisible && isResumed()) {
// Call code when Fragment becomes visible.
}
}
答案 17 :(得分:1)
当我试图在viewpager中的片段在屏幕上供用户查看时,我试图触发计时器时遇到了这个问题。
计时器始终在用户看到片段之前启动。
这是因为在我们看到片段之前,会调用片段中的onResume()
方法。
我的解决方案是检查onResume()
方法。我想打电话给某种方法&#39; foo()&#39;当片段8是视图寻呼机当前片段时。
@Override
public void onResume() {
super.onResume();
if(viewPager.getCurrentItem() == 8){
foo();
//Your code here. Executed when fragment is seen by user.
}
}
希望这会有所帮助。我已经看到这个问题出现了很多。这似乎是我见过的最简单的解决方案。许多其他人与较低的API等不兼容。
答案 18 :(得分:1)
我有同样的问题。 ViewPager
执行其他片段生命周期事件,我无法改变该行为。我使用片段和可用的动画编写了一个简单的寻呼机。
SimplePager
答案 19 :(得分:0)
请注意,setUserVisibleHint(false)
未在活动/片段停止时调用。您仍然需要检查开始/停止以正确register/unregister
任何听众/等。
此外,如果您的片段以不可见状态开始,您将获得setUserVisibleHint(false)
;你不想unregister
那里,因为在那种情况下你从未注册过。
@Override
public void onStart() {
super.onStart();
if (getUserVisibleHint()) {
// register
}
}
@Override
public void onStop() {
if (getUserVisibleHint()) {
// unregister
}
super.onStop();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && isResumed()) {
// register
if (!mHasBeenVisible) {
mHasBeenVisible = true;
}
} else if (mHasBeenVisible){
// unregister
}
}
答案 20 :(得分:0)
我使用了它,而且有效!
mContext.getWindow().getDecorView().isShown() //boolean
答案 21 :(得分:0)
我支持带有子片段的SectionsPagerAdapter,因此,经过很多头痛之后,我终于获得了基于该主题解决方案的工作版本:
public abstract class BaseFragment extends Fragment {
private boolean visible;
private boolean visibilityHintChanged;
/**
* Called when the visibility of the fragment changed
*/
protected void onVisibilityChanged(View view, boolean visible) {
}
private void triggerVisibilityChangedIfNeeded(boolean visible) {
if (this.visible == visible || getActivity() == null || getView() == null) {
return;
}
this.visible = visible;
onVisibilityChanged(getView(), visible);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!visibilityHintChanged) {
setUserVisibleHint(false);
}
}
@Override
public void onResume() {
super.onResume();
if (getUserVisibleHint() && !isHidden()) {
triggerVisibilityChangedIfNeeded(true);
}
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
triggerVisibilityChangedIfNeeded(!hidden);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
visibilityHintChanged = true;
if (isVisibleToUser && isResumed() && !isHidden()) {
triggerVisibilityChangedIfNeeded(true);
} else if (!isVisibleToUser) {
triggerVisibilityChangedIfNeeded(false);
}
}
@Override
public void onPause() {
super.onPause();
triggerVisibilityChangedIfNeeded(false);
}
@Override
public void onStop() {
super.onStop();
triggerVisibilityChangedIfNeeded(false);
}
protected boolean isReallyVisible() {
return visible;
}
}
答案 22 :(得分:0)
一种简单的实现方式是检查用户是否在登录之前进入该片段。
在MainActivity中,您可以在 onNavigationItemSelected 方法中执行类似的操作。
case R.id.nav_profile_side:
if (User_is_logged_in) {
fragmentManager.beginTransaction()
.replace(R.id.content_frame
, new FragmentProfile())
.commit();
}else {
ShowLoginOrRegisterDialog(fragmentManager);
}
break;
但是,如果您使用的是导航抽屉,尽管我们没有转到ProfileFragment,但抽屉中的选择将更改为Profile。
要将选择重置为当前选择,请运行以下代码
navigationView.getMenu().getItem(0).setChecked(true);
答案 23 :(得分:0)
可能很晚。这对我有用。我从@Gobar和@kris Solutions稍微更新了代码。我们必须更新PagerAdapter
中的代码。
setPrimaryItem
。如果位置相同,则表示我们不动。如果位置发生变化而当前位置不是我们单击的选项卡,则设置为-1。
private int mCurrentPosition = -1;
@Override
public void setPrimaryItem(@NotNull ViewGroup container, int position, @NotNull Object object) {
// This is what calls setMenuVisibility() on the fragments
super.setPrimaryItem(container, position, object);
if (position == mCurrentPosition) {
return;
}
if (object instanceof YourFragment) {
YourFragment fragment = (YourFragment) object;
if (fragment.isResumed()) {
mCurrentPosition = position;
fragment.doYourWork();//Update your function
}
} else {
mCurrentPosition = -1;
}
}
答案 24 :(得分:-3)
我覆盖了相关FragmentStatePagerAdapter的Count方法,并让它返回总计数减去要隐藏的页数:
public class MyAdapter : Android.Support.V13.App.FragmentStatePagerAdapter
{
private List<Fragment> _fragments;
public int TrimmedPages { get; set; }
public MyAdapter(Android.App.FragmentManager fm) : base(fm) { }
public MyAdapter(Android.App.FragmentManager fm, List<Android.App.Fragment> fragments) : base(fm)
{
_fragments = fragments;
TrimmedPages = 0;
}
public override int Count
{
//get { return _fragments.Count; }
get { return _fragments.Count - TrimmedPages; }
}
}
因此,如果最初有3个片段添加到ViewPager中,并且只有前两个片段应该显示,直到满足某些条件,则通过将TrimmedPages设置为1覆盖页面计数,它应该只显示前两页。 / p>
这对于最后的页面很有用,但对于开头或中间的页面确实没有帮助(尽管有很多方法可以做到这一点)。