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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10024739/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 02:14:04  来源:igfitidea点击:

How to determine when Fragment becomes visible in ViewPager

androidandroid-fragmentsandroid-viewpager

提问by 4ntoine

Problem: Fragment onResume()in ViewPageris fired before the fragment becomes actually visible.

问题:onResume()ViewPager片段实际可见之前触发片段输入。

For example, I have 2 fragments with ViewPagerand FragmentPagerAdapter. The second fragment is only available for authorized users and I need to ask the user to log in when the fragment becomes visible (using an alert dialog).

例如,我有 2 个带有ViewPager和 的片段FragmentPagerAdapter。第二个片段仅对授权用户可用,我需要让用户在片段可见时登录(使用警报对话框)。

BUT the ViewPagercreates the second fragment when the first is visible in order to cache the second fragment and makes it visible when the user starts swiping.

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

So the onResume()event is fired in the second fragment long before it becomes visible. That's why I'm trying to find an event which fires when the second fragment becomes visible to show a dialog at the appropriate moment.

所以onResume()事件在它变得可见之前很久就在第二个片段中被触发。这就是为什么我试图找到一个事件,当第二个片段变得可见以在适当的时候显示对话框时会触发该事件。

How can this be done?

如何才能做到这一点?

采纳答案by gorn

How to determine when Fragment becomes visible in ViewPager

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

You can do the following by overriding setUserVisibleHintin your Fragment:

您可以通过覆盖setUserVisibleHint您的Fragment:

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

回答by Oasis Feng

UPDATE: Android Support Library (rev 11) finally fixed the user visible hint issue, now if you use support library for fragments, then you can safely use getUserVisibleHint()or override setUserVisibleHint()to capture the changes as described by gorn's answer.

更新:Android 支持库(修订版 11)终于修复了用户可见的提示问题,现在如果您对片段使用支持库,那么您可以安全地使用getUserVisibleHint()或覆盖setUserVisibleHint()来捕获由 Gorn 的回答所描述的更改。

UPDATE 1Here is one small problem with getUserVisibleHint(). This value is by default true.

更新 1这是getUserVisibleHint(). 该值为默认值true

// Hint provided by the app that this fragment is currently visible to the user.
boolean mUserVisibleHint = true;

So there might be a problem when you try to use it before setUserVisibleHint()was invoked. As a workaround you might set value in onCreatemethod like this.

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

public void onCreate(@Nullable Bundle savedInstanceState) {
    setUserVisibleHint(false);


The outdated answer:

过时的答案:

In most use cases, ViewPageronly show one page at a time, but the pre-cached fragments are also put to "visible" state (actually invisible) if you are using FragmentStatePagerAdapterin Android Support Library pre-r11.

在大多数用例中,一次ViewPager只显示一页,但如果您使用FragmentStatePagerAdapterin ,预缓存的片段也会被置于“可见”状态(实际上是不可见的)Android Support Library pre-r11

I override :

我覆盖:

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

To capture the focus state of fragment, which I think is the most suitable state of the "visibility" you mean, since only one fragment in ViewPager can actually place its menu items together with parent activity's items.

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

回答by craigrs84

This seems to restore the normal onResume()behavior that you would expect. It plays well with pressing the home key to leave the app and then re-entering the app. onResume()is not called twice in a row.

这似乎恢复了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
}

回答by Ostkontentitan

Here is another way using onPageChangeListener:

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

  }
});

回答by Jemshit Iskenderov

setUserVisibleHint()gets called sometimes beforeonCreateView()and sometimes after which causes trouble.

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

To overcome this you need to check isResumed()as well inside setUserVisibleHint()method. But in this case i realized setUserVisibleHint()gets called onlyif Fragment is resumed and visible, NOT when Created.

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

So if you want to update something when Fragment is visible, put your update function both in onCreate()and setUserVisibleHint():

因此,如果您想在 Fragment is 时更新某些内容visible,请将您的更新函数放入onCreate()和 中setUserVisibleHint()

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

UPDATE:Still i realized myUIUpdate()gets called twice sometimes, the reason is, if you have 3 tabs and this code is on 2nd tab, when you first open 1st tab, the 2nd tab is also created even it is not visible and myUIUpdate()is called. Then when you swipe to 2nd tab, myUIUpdate()from if (visible && isResumed())is called and as a result,myUIUpdate()may get called twice in a second.

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

The other problemis !visiblein setUserVisibleHintgets called both 1) when you go out of fragment screen and 2) before it is created, when you switch to fragment screen first time.

另一个问题!visiblesetUserVisibleHint1) 离开片段屏幕时和 2) 在创建之前,当您第一次切换到片段屏幕时被调用。

Solution:

解决方案:

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

Explanation:

解释:

fragmentResume,fragmentVisible: Makes sure myUIUpdate()in onCreateView()is called only when fragment is created and visible, not on resume. It also solves problem when you are at 1st tab, 2nd tab is created even if it is not visible. This solves that and checks if fragment screen is visible when onCreate.

fragmentResume, fragmentVisible: 确保仅在片段创建且可见时调用myUIUpdate()in onCreateView(),而不是在恢复时调用。当您处于第一个选项卡时,它也可以解决问题,即使第二个选项卡不可见也会创建。这解决了这个问题并检查片段屏幕在onCreate.

fragmentOnCreated: Makes sure fragment is not visible, and not called when you create fragment first time. So now this if clause only gets called when you swipe out of fragment.

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

UpdateYou can put all this code in BaseFragmentcode like thisand override method.

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

回答by Phan Van Linh

To detect Fragmentin ViewPagervisible, I'm quite sure that only usingsetUserVisibleHintis not enough.
Here is my solution to check if a fragment is visible or invisible. First when launching viewpager, switch between page, go to another activity/fragment/ background/foreground`

为了检测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();
    }
}

EXPLANATIONYou can check the logcat below carefully then I think you may know why this solution will work

解释您可以仔细检查下面的 logcat 然后我想您可能知道为什么这个解决方案会起作用

First launch

首次发布

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

Go to page2

转到第 2 页

Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment2: setUserVisibleHint: isVisibleToUser=true isResumed=true

Go to page3

转到第 3 页

Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment3: setUserVisibleHint: isVisibleToUser=true isResumed=true

Go to background:

转到背景:

Fragment1: onStop mIsVisibleToUser=false
Fragment2: onStop mIsVisibleToUser=false
Fragment3: onStop mIsVisibleToUser=true

Go to foreground

转到前台

Fragment1: onStart mIsVisibleToUser=false
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=true

DEMO project here

演示项目在这里

Hope it help

希望有帮助

回答by w3hacker

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

回答by lukjar

In ViewPager2and ViewPagerfrom version androidx.fragment:fragment:1.1.0you can just use onPauseand onResumecallbacks to determine which fragment is currently visible for the user. onResumecallback is called when fragment became visible and onPausewhen it stops to be visible.

在版本中ViewPager2ViewPager从版本中,androidx.fragment:fragment:1.1.0您可以仅使用onPauseonResume回调来确定用户当前可见的片段。onResume当片段变得可见和onPause停止可见时调用回调。

In case of ViewPager2 it is default behavior but the same behavior can be enabled for old good ViewPagereasily.

在 ViewPager2 的情况下,它是默认行为,但可以ViewPager轻松地为旧商品启用相同的行为。

To enable this behavior in the first ViewPager you have to pass FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTparameter as second argument of FragmentPagerAdapterconstructor.

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

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

Note: setUserVisibleHint()method and FragmentPagerAdapterconstructor with one parameter are now deprecated in the new version of Fragment from android jetpack.

注意:带有一个参数的setUserVisibleHint()方法和FragmentPagerAdapter构造函数现在在 android jetpack 的新版本 Fragment 中已弃用。

回答by kris larson

Override setPrimaryItem()in the FragmentPagerAdaptersubclass. I use this method, and it works well.

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

回答by dan

Override Fragment.onHiddenChanged()for that.

Fragment.onHiddenChanged()为此覆盖。

public?void?onHiddenChanged(boolean?hidden)

Called when the hidden state (as returned by isHidden()) of the fragment has changed. Fragments start out not hidden; this will be called whenever the fragment changes state from that.

Parameters
hidden- boolean: True if the fragment is now hidden, false if it is not visible.

public?void?onHiddenChanged(boolean?hidden)

isHidden()片段的隐藏状态(由 返回)发生变化时调用。片段开始时没有隐藏;每当片段从那里改变状态时,就会调用它。

参数
hidden- boolean:如果片段现在隐藏,则为真,如果不可见,则为假。