如何确定片段何时在ViewPager中可见

4nt*_*ine 728 android android-fragments android-viewpager

问题:片段onResume()ViewPager片段实际可见之前被触发.

例如,我有2个片段与ViewPagerFragmentPagerAdapter.第二个片段仅供授权用户使用,我需要让用户在片段可见时登录(使用警告对话框).

但是ViewPager当第一个片段可见时创建第二个片段,以便缓存第二个片段,并在用户开始滑动时使其可见.

因此,onResume()事件在第二个片段变为可见之前就被触发了.这就是为什么我试图找到一个事件,当第二个片段变得可见时,它会在适当的时刻显示一个对话框.

如何才能做到这一点?

gor*_*orn 566

如何确定片段何时在ViewPager中可见

你可以通过覆盖以下setUserVisibleHintFragment:

public class MyFragment extends Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
        }
        else {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我发现在调用onCreateView之前调用setUserVisibleHint方法,这使得跟踪任何初始化变得困难. (57认同)
  • @AndroidDev如果你想在提示进入true时运行一些代码,但需要已经初始化的视图树,只需将该代码块包装在`isResumed()`中以避免NPE.对我来说工作得很好. (10认同)
  • 对于SDK应该默认提供的东西有很多不同的黑客,这简直太荒谬了. (9认同)
  • 感谢今天的Android支持库更新(第11版),最终修复了用户可见提示问题.现在可以安全地使用ViewPager的用户可见提示. (6认同)
  • 我在viewstatepager中有3个片段,发现getUserVisibleHint()总是返回true. (5认同)
  • 我没有看到setUserVisibleHint()作为Fragment的生命周期事件记录在任何地方. (3认同)
  • 完全没有被召唤. (3认同)
  • 您知道,在Java中,部分地使用名称(如您所用)和CLASS来识别方法.所以,当你建议覆盖一种方法时,请给上课.特别是因为有两个对象可以被操纵(`Fragment`和`ViewPager`). (2认同)
  • setUserVisibleHint`现已**弃用** (2认同)

Oas*_*eng 517

更新:Android支持库(第11版)最终修复了用户可见的提示问题,现在如果您使用片段支持库,那么您可以安全地使用getUserVisibleHint()或覆盖setUserVisibleHint()以捕获更改,如gorn的答案所述.

更新1这是一个小问题getUserVisibleHint().该值是默认值true.

// Hint provided by the app that this fragment is currently visible to the user.
boolean mUserVisibleHint = true;
Run Code Online (Sandbox Code Playgroud)

因此,在setUserVisibleHint()调用之前尝试使用它时可能会出现问题.作为一种解决方法,您可以在这样的onCreate方法中设置值.

public void onCreate(@Nullable Bundle savedInstanceState) {
    setUserVisibleHint(false);
Run Code Online (Sandbox Code Playgroud)

过时的答案:

在大多数用例中,一次ViewPager只显示一个页面,但如果您正在使用FragmentStatePagerAdapter,则预缓存的片段也会处于"可见"状态(实际上是不可见的)Android Support Library pre-r11.

我覆盖:

public class MyFragment extends Fragment {
    @Override
    public void setMenuVisibility(final boolean visible) {
        super.setMenuVisibility(visible);
        if (visible) {
            // ...
        }
    }
   // ...
}
Run Code Online (Sandbox Code Playgroud)

捕获片段的焦点状态,我认为这是你所说的"可见性"最合适的状态,因为ViewPager中只有一个片段实际上可以将其菜单项与父活动的项目放在一起.

  • 小心使用getActivity(),首先启动它将为null. (72认同)
  • 请注意,setUserVisibleHint()始终在FragmentPagerAdapter中正常工作. (9认同)
  • 这个解决方案(也是gorn的)有点滞后.如果快速刷过viewpager并且多次滑动,则会延迟调用此方法(当片段不再可见/不可见时). (7认同)
  • setUserVisibleHint`现已**弃用** (6认同)
  • 当调用`onCreateOptionsMenu`时,我为getUserVisibleHint()得到`true`,当调用`setUserVisibleHint`时,似乎还没有创建菜单.最后,当我只想看到可见片段的菜单时,我会添加两个选项.对此有何建议? (2认同)

cra*_*s84 135

这似乎可以恢复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
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为自己调用Resume是一个非常糟糕的主意,但除此之外,你需要回答这篇文章的问题是在setUserVisibleHint函数中! (8认同)
  • setUserVisibleHint 已弃用! (4认同)
  • 正是我在寻找什么.解决了在`onCreateView`之前调用`setUserVisibleHint`的问题,如果app变为背景然后前景,则不会调用`setUserVisibleHint`.真棒!谢谢! (2认同)
  • 我宁愿实现一个简单的onVisibleToUser()方法,并从onResume()和setUserVisibleHint(boolean)中调用,而不是自己调用onResume()并干扰生命周期回调。否则,我认为这种方法效果很好,谢谢! (2认同)

Ost*_*tan 68

这是另一种使用方式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

  }
});
Run Code Online (Sandbox Code Playgroud)

  • 值得注意的是,如果你使用FragmentStatePagerAdapter并在getItem()上实例化一个新的Fragment实例,那么这无法给你预期的结果,因为你只会在新片段上设置状态而不是viewpager获得的状态 (6认同)

Jem*_*rov 56

setUserVisibleHint()有时会在之前 onCreateView()和之后被调用,这会导致麻烦.

为了克服这个问题,你需要检查isResumed()内部setUserVisibleHint()方法.但在这种情况下,我意识到只有在片段恢复并且可见时才会setUserVisibleHint()被调用,而不是在创建时.

因此,如果您想在Fragment中更新某些内容visible,请将更新功能放在onCreate()setUserVisibleHint():

@Override
public View onCreateView(...){
    ...
    myUIUpdate();
    ...        
}
  ....
@Override
public void setUserVisibleHint(boolean visible){
    super.setUserVisibleHint(visible);
    if (visible && isResumed()){
        myUIUpdate();
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:我仍然意识到myUIUpdate()有时会被调用两次,原因是,如果你有3个选项卡并且这个代码在第二个选项卡上,当你第一次打开第一个选项卡时,第二个选项卡也会被创建,即使它不可见并被myUIUpdate()调用.然后当您滑动到第二个选项卡时,将调用myUIUpdate()from if (visible && isResumed()),因此myUIUpdate()可能会在一秒钟内调用两次.

另一个问题在于!visible,setUserVisibleHint当你离开片段屏幕时,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;
    }
}
Run Code Online (Sandbox Code Playgroud)

说明:

fragmentResume,fragmentVisible:确保myUIUpdate()onCreateView()创建只有当片段可见,不是简历被调用.当您处于第一个选项卡时,它也解决了问题,即使它不可见,也会创建第二个选项卡.这解决了这个问题并检查片段屏幕是否可见onCreate.

fragmentOnCreated:确保片段不可见,并且在第一次创建片段时不会调用.所以现在这个if子句只有在你滑出片段时才被调用.

更新 你可以把所有这些代码BaseFragment代码像这样和覆盖方法.


Pha*_*inh 26

FragmentViewPager可见的情况下进行检测,我很确定只使用 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

解释 您可以仔细检查下面的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
Run Code Online (Sandbox Code Playgroud)

转到第2页

Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment2: setUserVisibleHint: isVisibleToUser=true isResumed=true
Run Code Online (Sandbox Code Playgroud)

转到第3页

Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment3: setUserVisibleHint: isVisibleToUser=true isResumed=true
Run Code Online (Sandbox Code Playgroud)

转到后台:

Fragment1: onStop mIsVisibleToUser=false
Fragment2: onStop mIsVisibleToUser=false
Fragment3: onStop mIsVisibleToUser=true
Run Code Online (Sandbox Code Playgroud)

转到前台

Fragment1: onStart mIsVisibleToUser=false
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=true
Run Code Online (Sandbox Code Playgroud)

DEMO项目在这里

希望它有所帮助


小智 25

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这应该是公认的答案.兼容android.app.Fragment,这是一个巨大的奖金. (2认同)
  • `setUserVisibleHint` 已弃用 (2认同)

kri*_*son 15

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这几乎有效,但在 NPE 方面存在问题并且被多次调用。在下面发布了一个替代方案! (2认同)

小智 11

覆盖Fragment.onHiddenChanged()它.

public void onHiddenChanged(boolean hidden)

isHidden()片段的隐藏状态(由返回者)发生更改时调用.片段开始没有隐藏; 只要片段从那里改变状态,就会调用它.

参数
hidden- boolean:如果片段现在隐藏则为True,如果片段不可见则为false.

  • 我正在使用API​​级别19并且可以确认虽然不推荐使用它,但它不像宣传的那样工作.例如,当片段被另一个片段隐藏时,不会调用onHiddenChanged. (7认同)
  • @bluewhile你在哪里看到它被弃用?http://developer.android.com/reference/android/app/Fragment.html#onHiddenChanged%28boolean%29 (4认同)
  • 此方法现已弃用,不能再使用 (3认同)

Rah*_*hul 8

setUserVisibleHint(booleanvisible)现已弃用所以这是正确的解决方案

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
Run Code Online (Sandbox Code Playgroud)

ViewPager2ViewPager from 版本中androidx.fragment:fragment:1.1.0,您只需使用onPause()onResume()来确定哪个片段当前对用户可见。当片段变得可见和停止可见时onResume()调用。onPause

要在第一个 ViewPager 中启用此行为,您必须将FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT参数作为构造函数的第二个参数传递FragmentPagerAdapter


luk*_*jar 6

ViewPager2ViewPager从版本androidx.fragment:fragment:1.1.0你可以使用onPauseonResume回调,以确定哪些片段是目前用户可见。onResume片段变为可见时以及片段onPause停止可见时调用回调。

对于ViewPager2,它是默认行为,但可以ViewPager很容易地为旧商品启用相同的行为。

要在第一个ViewPager中启用此行为,您必须将FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT参数作为FragmentPagerAdapter构造函数的第二个参数传递。

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
Run Code Online (Sandbox Code Playgroud)

注意:android jetpack的Fragment新版本中不推荐使用带有一个参数的setUserVisibleHint()方法和FragmentPagerAdapter构造函数。

  • 截至 2020 年 4 月,这是更新的解决方案,并且非常有效。 (2认同)
  • @4ntoine 请考虑接受此答案作为正确答案,因为到目前为止这是正确的,并且大多数答案都使用已弃用的 setUserVisibleHint 方法 (2认同)

Top*_*ter 6

只有这个对我有用!!和setUserVisibleHint(...)现在已被弃用(I附接在端文档),这意味着其他的答案的最已弃用;-)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

经过测试和使用NaviagationDrawerisMenuVisible()总会有回报trueonResume()似乎足够了,但我们也想支持ViewPager)。

setUserVisibleHint已弃用。如果覆盖此方法,则传入时实现的行为true应移至Fragment.onResume(),传入时实现的行为false应移至Fragment.onPause()


ism*_*lik 5

我想通了,onCreateOptionsMenu并且onPrepareOptionsMenu仅在片段的情况下调用的方法确实可见。我找不到任何行为与这些类似的方法,我也尝试过OnPageChangeListener但它不适用于这种情况,例如,我需要在onCreate方法中初始化一个变量。

所以这两种方法可以作为解决这个问题的一种方法,特别是对于小而短的工作。

我认为,这是更好的解决方案,但不是最好的。我会使用它,但同时等待更好的解决方案。

问候。