วิธีการตรวจสอบว่าเมื่อใดที่ Fragment มองเห็นได้ใน ViewPager


754

ปัญหา: มีการยิงชิ้นส่วนonResume()ในViewPagerก่อนที่จะเห็นชิ้นส่วนจริง

ตัวอย่างเช่นฉันมี 2 ชิ้นส่วนด้วยและViewPager FragmentPagerAdapterส่วนที่สองมีให้เฉพาะสำหรับผู้ใช้ที่ได้รับอนุญาตและฉันต้องขอให้ผู้ใช้เข้าสู่ระบบเมื่อส่วนที่มองเห็นได้ (ใช้กล่องโต้ตอบการแจ้งเตือน)

แต่ViewPagerชิ้นส่วนที่สองจะสร้างขึ้นเมื่อมองเห็นชิ้นแรกเพื่อแคชชิ้นส่วนที่สองและทำให้สามารถมองเห็นได้เมื่อผู้ใช้เริ่มตบ

ดังนั้นonResume()เหตุการณ์จึงถูกยิงในส่วนที่สองนานก่อนที่จะปรากฏให้เห็น นั่นเป็นเหตุผลที่ฉันพยายามค้นหาเหตุการณ์ที่ไฟไหม้เมื่อส่วนที่สองปรากฏให้เห็นเพื่อแสดงการสนทนาในช่วงเวลาที่เหมาะสม

สิ่งนี้สามารถทำได้?


18
"ฉันมี 2 แฟรกเมนต์ที่มี ViewPager และ FragmentPagerAdapter ส่วนที่สองสามารถใช้ได้สำหรับผู้ใช้ที่ได้รับอนุญาตเท่านั้นและฉันควรขอใช้ในการเข้าสู่ระบบเมื่อส่วนจะปรากฏให้เห็น (กล่องโต้ตอบการแจ้งเตือน)" - IMHO นั่นคือ UX อันยิ่งใหญ่ การเปิดกล่องโต้ตอบเนื่องจากผู้ใช้รูดในแนวนอนจะทำให้ฉันได้รับการจัดอันดับหนึ่งดาวบน Play Store
CommonsWare

มันจะดีกว่าที่จะเพียงแค่แสดงข้อมูลที่ TextView ด้วยปุ่ม "เข้าสู่ระบบ"? อะไรคือทางออกของคุณสำหรับกรณีนี้?
4ntoine

5
"ไม่จำเป็นต้องโหลดข้อมูลหากจะไม่ปรากฏ" - ViewPagerแล้วคุณไม่ควรวางไว้ใน ในเพจเจอร์สองหน้าทั้งสองหน้าจะโหลดทันทีไม่ว่าคุณจะชอบหรือไม่ ประสบการณ์การใช้งานของผู้ใช้ViewPagerควรเป็นไปได้ว่าเนื้อหาจะอยู่ที่นั่นทันทีเมื่อทำการรูดไม่ใช่ในเวลาต่อมา นั่นคือเหตุผลที่ViewPagerเริ่มต้นหน้าล่วงหน้าก่อนที่จะมองเห็นได้เพื่อช่วยให้มั่นใจว่าผู้ใช้จะได้รับประสบการณ์
CommonsWare

2
มันดูเหมือนว่า ViewPager เป็นพอไม่ยืดหยุ่นและไม่อนุญาตให้มีการปิดการแคชตั้งแต่ setOffscreenPageLimit ต่ำสุดคือ 1: stackoverflow.com/questions/10073214/... ไม่เห็นเหตุผลใด ๆ สำหรับสิ่งนี้และพฤติกรรมที่คาดหวัง (ในกรณีของการแคชแบบบังคับ) คือการสร้างแฟรกเมนต์ แต่ onResume () ของแฟรกเมนต์ไฟไหม้เมื่อแฟรกเมนต์สามารถมองเห็นได้
4ntoine

1
ช้าไปหน่อย แต่สำหรับใครก็ตามที่ประสบปัญหาเดียวกันคุณสามารถลองใช้ ห้องสมุดFragmentViewPager (ฉันเป็นผู้เขียน) ซึ่งเกี่ยวข้องกับปัญหานี้และมีคุณสมบัติพิเศษบางอย่าง สำหรับตัวอย่างตรวจสอบหน้า GitHub ของโครงการหรือคำตอบ stackoverflowนี้
S. Brukhanda

คำตอบ:


582

วิธีการตรวจสอบว่าเมื่อใดที่ Fragment มองเห็นได้ใน ViewPager

คุณสามารถทำสิ่งต่อไปนี้ได้โดยการเอาชนะsetUserVisibleHintในFragment:

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

7
ต้องขอบคุณการอัปเดตห้องสมุดสนับสนุน Android ของวันนี้ (rev 11) ปัญหาคำใบ้ที่ผู้ใช้มองเห็นได้รับการแก้ไขในที่สุด ตอนนี้คุณสามารถใช้คำใบ้ที่ผู้ใช้มองเห็นได้อย่างปลอดภัยสำหรับ ViewPager
Oasis Feng

58
ฉันพบว่าเมธอด setUserVisibleHint ถูกเรียกก่อนที่ onCreateView จะถูกเรียกและสิ่งนี้ทำให้ยากต่อการติดตามการเริ่มต้นใด ๆ
AndroidDev

11
@AndroidDev หากคุณต้องการเรียกใช้รหัสบางส่วนเมื่อคำใบ้มาเป็นความจริง แต่สิ่งที่ต้องการต้นไม้มุมมองเริ่มต้นแล้วเพียงห่อบล็อกของรหัสในisResumed()เพื่อหลีกเลี่ยง NPE เคยทำงานได้ดีสำหรับฉัน
Ravi Thapliyal

13
มันไร้สาระเพียงว่ามีแฮ็กที่แตกต่างกันมากมายสำหรับบางสิ่งบางอย่างที่ SDK ควรมีให้ตามค่าเริ่มต้น
Mike6679

15
setUserVisibleHintได้เลิกใช้
SR

525

อัปเดต : Android Support Library (rev 11) ในที่สุดก็แก้ไขปัญหาคำใบ้ที่ผู้ใช้มองเห็นได้ตอนนี้ถ้าคุณใช้ไลบรารี่สนับสนุนสำหรับแฟรกเมนต์จากนั้นคุณสามารถใช้getUserVisibleHint()หรือแทนที่setUserVisibleHint()เพื่อบันทึกการเปลี่ยนแปลงตามที่อธิบายโดยคำตอบของ gorn

UPDATE 1นี่คือหนึ่งในปัญหาเล็ก ๆ getUserVisibleHint()ด้วย trueค่านี้เป็นค่าเริ่มต้น

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

ดังนั้นอาจมีปัญหาเมื่อคุณลองใช้ก่อนที่จะsetUserVisibleHint()เรียกใช้ คุณสามารถตั้งค่าในonCreateวิธีการเช่นนี้

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

คำตอบที่ล้าสมัย:

ในกรณีการใช้งานส่วนใหญ่ViewPagerจะแสดงเฉพาะหนึ่งหน้าในเวลา แต่ชิ้นส่วนที่เก็บในแคชล่วงหน้านอกจากนี้ยังจะนำไป "มองเห็น" ของรัฐ (ที่มองไม่เห็นจริง) ถ้าคุณกำลังใช้ในFragmentStatePagerAdapterAndroid Support Library pre-r11

ฉันแทนที่:

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

ในการจับภาพสถานะโฟกัสของแฟรกเมนต์ซึ่งฉันคิดว่าเป็นสถานะที่เหมาะสมที่สุดของ "การมองเห็น" คุณหมายถึงเนื่องจากมีเพียงหนึ่งแฟรกเมนต์ใน ViewPager ที่สามารถวางรายการเมนูร่วมกับรายการกิจกรรมหลักได้


73
ใช้ความระมัดระวังในการใช้ getActivity () ในการเริ่มต้นครั้งแรกมันจะเป็นโมฆะ
vovkab

10
โปรดทราบว่า setUserVisibleHint () ทำงานได้อย่างถูกต้องใน FragmentPagerAdapter
louielouie

8
วิธีแก้ปัญหานี้ (และ gorn's) ก็ค่อนข้างล่าช้าเล็กน้อย ในกรณีที่ viewpager ถูกกวาดอย่างรวดเร็วและเวลา mutliple วิธีการนี้จะถูกเรียกด้วยความล่าช้า
AsafK

3
ฉันได้รับtruegetUserVisibleHint () เมื่อonCreateOptionsMenuถูกเรียกและเมื่อsetUserVisibleHintถูกเรียกเมนูยังไม่ได้ถูกสร้างขึ้นดูเหมือนว่า ในที่สุดฉันได้รับสองตัวเลือกเมนูเพิ่มเมื่อฉันต้องการเมนูจากส่วนที่มองเห็นได้ ข้อเสนอแนะใด ๆ ในเรื่องนี้?
cYrixmorten

13
setUserVisibleHintได้เลิกใช้
SR

143

สิ่งนี้ดูเหมือนว่าจะกู้คืน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
}

4
สิ่งที่ฉันกำลังมองหา แก้ปัญหาด้วยsetUserVisibleHintการถูกเรียกก่อนหน้านี้onCreateViewและsetUserVisibleHintไม่ได้รับการเรียกถ้าแอปทำงานเป็นแบ็คกราวน์และพื้นหน้า ! น่ากลัว ขอบคุณ!
อเล็กซ์

11
ฉันคิดว่าการโทรหาสมมติว่าตัวเองเป็นความคิดที่ไม่ดีนัก แต่อย่างอื่นทุกสิ่งที่คุณต้องตอบคำถามของโพสต์นี้อยู่ในฟังก์ชั่น setUserVisibleHint!
Quentin G.

4
ฉันควรจะใช้onVisibleToUser()วิธีการธรรมดาและมีการเรียกจากonResume()และจากsetUserVisibleHint(boolean)แทนที่จะเรียกonResume()ตัวเองและรบกวนการเรียกกลับวงจรชีวิต มิฉะนั้นฉันคิดว่าวิธีนี้ใช้งานได้ดีขอบคุณ!
Stephan Henningsen

1
ใช่ฉันเห็นด้วยกับความคิดเห็นข้างต้น โดยทั่วไปแล้วพยายามหลีกเลี่ยงการเรียกวิธีการสาธารณะจากวิธีสาธารณะในชั้นเรียนของคุณ สร้างวิธีการส่วนตัวและเรียกใช้จากทั้งวิธีสาธารณะ
Johan Franzén

4
setUserVisibleHint เลิกใช้แล้ว!
ฮามิดเรซา

70

นี่เป็นอีกวิธีใช้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

  }
});

1
วิธีนี้ใช้ได้ดีขอบคุณ นี่ควรเป็นโซลูชันที่แนะนำสำหรับสิ่งปลูกสร้างสำหรับแพลตฟอร์มเก่าที่ใช้แพคเกจการสนับสนุนชิ้นส่วน (v4)
Frank Yin

7
เป็นเรื่องน่าสังเกตว่าสิ่งนี้ไม่สามารถให้ผลลัพธ์ที่คาดหวังถ้าคุณใช้ FragmentStatePagerAdapter และอินสแตนซ์ Fragment อินสแตนซ์ใหม่ใน getItem () เนื่องจากคุณจะตั้งค่าสถานะในแฟรกเมนต์ใหม่เท่านั้นและไม่ใช่อันที่ viewpager ได้
Ben Pearson

1
วิธีนี้เป็นวิธีที่ดีถ้าคุณต้องการที่จะทำอะไรบางอย่างเมื่อเห็นชิ้นส่วน มันไม่รักษาสถานะที่มองเห็นได้ของชิ้นส่วน (หมายถึงมันไม่ทราบว่าเมื่อใดที่มองไม่เห็นชิ้นส่วน)
AsafK

ฉันเป็นปัญหาประเภทเดียวกัน โปรดช่วยฉันแก้ปัญหาด้วย โพสต์ของฉัน: stackoverflow.com/questions/23115283/ …
Jeeten Parmar

นี่คือการให้ข้อยกเว้นตัวชี้โมฆะสำหรับอะแดปเตอร์ที่ฉันใช้ในวิธีการ iamVisible ของส่วน ฉันกำลังพยายามตั้งค่าข้อมูลที่ได้รับจากเครือข่ายก็ต่อเมื่อสามารถมองเห็นชิ้นส่วนได้
zaphod100.10

58

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

UPDATE:ฉันยังคงmyUIUpdate()รับรู้ถึงการถูกเรียกสองครั้งเหตุผลคือถ้าคุณมี 3 แท็บและรหัสนี้อยู่บนแท็บที่ 2 เมื่อคุณเปิดแท็บที่ 1 เป็นครั้งแรกแท็บที่ 2 จะถูกสร้างขึ้นแม้ว่าจะมองไม่เห็นและmyUIUpdate()ถูกเรียก จากนั้นเมื่อคุณปัดไปที่แท็บที่ 2 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()ที่เรียกว่าเฉพาะเมื่อชิ้นส่วนถูกสร้างขึ้นและมองเห็นได้ในงาน นอกจากนี้ยังแก้ปัญหาเมื่อคุณอยู่ที่แท็บที่ 1 แท็บที่ 2 ถูกสร้างขึ้นแม้ว่าจะมองไม่เห็น วิธีนี้จะช่วยแก้ปัญหานั้นและตรวจสอบว่าหน้าจอชิ้นส่วนสามารถมองเห็นได้เมื่อonCreateใด

fragmentOnCreated: ทำให้แน่ใจว่าไม่สามารถมองเห็นชิ้นส่วนและไม่ถูกเรียกเมื่อคุณสร้างชิ้นส่วนเป็นครั้งแรก ดังนั้นตอนนี้ถ้าประโยคเพียงได้รับการเรียกเมื่อคุณปัดออกจากส่วน

อัปเดต คุณสามารถใส่รหัสนี้ทั้งหมดในBaseFragmentรหัสเช่นนี้และวิธีการแทนที่


1
วิธีการแก้ปัญหาของคุณทำงานได้อย่างสมบูรณ์ยกเว้นกับสถานการณ์หนึ่งเมื่อชิ้นส่วนถูกเปิดตามปกติแล้วคุณคลิกที่ปุ่มบางอย่างที่จะแทนที่ด้วยส่วนอื่นแล้วคลิกกลับไปปรากฏชิ้นใหม่จาก backstack ก็กรณีที่setUserVisibleHintไม่ได้รับสาย .. ! และonCreateViewวิธีการภายในfragmentVisibleคือfalse! ดังนั้นชิ้นส่วนจึงว่างเปล่า .. ! ความคิดใด ๆ
Alaa AbuZarifa

28

ในการตรวจสอบFragmentในViewPagerที่มองเห็นผมค่อนข้างแน่ใจว่าเป็นเพียงการใช้ setUserVisibleHintไม่เพียงพอ
นี่คือวิธีแก้ไขปัญหาของฉันเพื่อตรวจสอบว่ามีชิ้นส่วนที่มองเห็นหรือมองไม่เห็น ก่อนอื่นเมื่อเรียกใช้ viewpager ให้สลับระหว่างเพจไปที่กิจกรรมอื่น / แฟรกเมนต์ / พื้นหลัง / โฟร์กราวน์ `

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

โครงการสาธิตที่นี่

หวังว่ามันจะช่วย


1
มันไม่ทำงานเมื่อแฟรกเมนต์มีเพจเจอร์มุมมองอื่นซึ่งประกอบด้วยแฟรกเมนต์
Shihab Uddin

ขอโทษฉันไม่สามารถโพสต์คำตอบในเวลานี้เพราะผมไม่ได้มีเวลามากพอที่ถ้าเป็นไปได้โปรดดูคอมไพล์ของฉันเกี่ยวกับปัญหาของคุณที่นี่github.com/PhanVanLinh/AndroidViewPagerSkeleton/tree/master จะใช้สำหรับการตรวจสอบSubChildContainerFragment a fragment has another view pager which also consists fragmentคุณสามารถผสมSubChildContainerFragmentและChildContainerFragmentเป็น 1 คลาสได้ หวังว่ามันจะช่วย ฉันจะโพสต์คำตอบที่สมบูรณ์ในภายหลัง
Phan Van Linh

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

2
นี่ควรเป็นคำตอบที่ยอมรับได้ ทำงานร่วมกับ android.app.Fragment ได้เช่นกันซึ่งเป็นโบนัสมาก
RajV

setUserVisibleHintเลิกใช้แล้ว
ekashking

23

ใน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)

หมายเหตุ: setUserVisibleHint()เมธอดและตัวFragmentPagerAdapterสร้างที่มีหนึ่งพารามิเตอร์เลิกใช้แล้วใน Fragment เวอร์ชันใหม่จาก android jetpack


1
ขอบคุณสำหรับทางออกที่ดี ฉันกำลังมองหาสิ่งนี้มาเป็นเวลานาน Great Work
Anurag Srivastava

1
สำหรับ ViewPager2 ตัวเลือก BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT ไม่ใช่พฤติกรรมเริ่มต้นคุณต้องตั้งค่าด้วยตนเอง เมื่อตั้งค่าตัวเลือกนี้โซลูชันของคุณใช้ได้สำหรับฉัน ขอบคุณ
driAn

1
ในฐานะที่เป็นของเมษายน 2020 นี้เป็นโซลูชั่นที่ปรับปรุงและทำงานเหมือนมีเสน่ห์
Prakash

1
@ 4ntoine โปรดพิจารณายอมรับคำตอบนี้เป็นคำตอบที่ถูกต้องเนื่องจาก ณ ตอนนี้คำตอบนี้ถูกต้องและคำตอบส่วนใหญ่ใช้วิธี setUserVisibleHint ที่คัดค้านแล้ว
Jorn Rigter

16

แทนที่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();
    }
}

1
เกือบจะทำงานได้ แต่มีปัญหากับ NPE และถูกเรียกหลายครั้ง โพสต์ทางเลือกอื่นด้านล่าง!
Gober


@Gober การบันทึกวัตถุวัตถุในครั้งแรกและตรวจสอบและละเว้นการเรียกติดตามด้วยวัตถุวัตถุเดียวกันเช่น FragmentStateAdapter ทำใน setPrimaryItem () วิธีการ
wanglugao

12

แทนที่Fragment.onHiddenChanged()สำหรับสิ่งนั้น

public void onHiddenChanged(boolean hidden)

เรียกว่าเมื่อสถานะที่ซ่อนอยู่ (ตามที่ส่งคืนโดยisHidden()) ของชิ้นส่วนมีการเปลี่ยนแปลง เศษเล็กเศษน้อยเริ่มออกไม่ได้ซ่อนอยู่ สิ่งนี้จะถูกเรียกเมื่อใดก็ตามที่ชิ้นส่วนเปลี่ยนสถานะจากนั้น

พารามิเตอร์
hidden- boolean: จริงถ้าชิ้นส่วนถูกซ่อนอยู่ตอนนี้เท็จถ้าไม่สามารถมองเห็นได้


3
วิธีนี้เลิกใช้แล้วในขณะนี้และไม่สามารถใช้งานได้อีกต่อไป
bluewhile

4
@bluewhile คุณเห็นว่าเลิกใช้ที่ไหน developer.android.com/reference/android/app/…
Cel

1
ฉันเพิ่งใช้มันกับความสำเร็จและเอกสารดูเหมือนจะไม่ได้ระบุว่ามันเลิกใช้แล้ว
เทรเวอร์

1
@bluewhile ใน 4.1 (api 16) ยังไม่เลิกใช้
แดน

8
ฉันใช้ API ระดับ 19 และสามารถยืนยันได้ว่าแม้ว่านี่จะไม่คัดค้าน แต่ก็ใช้งานไม่ได้ตามที่โฆษณาไว้ onHiddenChanged ไม่ถูกเรียกเมื่อแฟรกเมนต์ถูกซ่อนโดยแฟรกเมนต์อื่นตัวอย่างเช่น

4

ฉันคิดออกว่าonCreateOptionsMenuและonPrepareOptionsMenuวิธีการที่เรียกว่าเฉพาะในกรณีของชิ้นส่วนที่มองเห็นได้จริงๆ ฉันไม่พบวิธีการใด ๆ ที่มีพฤติกรรมเช่นนี้ฉันพยายามด้วยOnPageChangeListenerแต่ก็ไม่ได้ผลสำหรับสถานการณ์เช่นฉันต้องการตัวแปรที่เริ่มต้นด้วยonCreateวิธี

ดังนั้นวิธีการทั้งสองนี้สามารถใช้สำหรับปัญหานี้เป็นวิธีแก้ปัญหาเฉพาะสำหรับงานน้อยและสั้น

ฉันคิดว่านี่เป็นทางออกที่ดีกว่า แต่ไม่ใช่สิ่งที่ดีที่สุด ฉันจะใช้มัน แต่รอวิธีแก้ปัญหาที่ดีกว่าในเวลาเดียวกัน

ความนับถือ.


2

โซลูชันอื่นที่โพสต์ที่นี่แทนที่ setPrimaryItem ในเพจเจอร์อะแดปเตอร์โดย 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();
        }
    }
}

2

เพิ่มรหัสต่อไปนี้ภายในชิ้นส่วน

@Override
public void setMenuVisibility(final boolean visible) 
 {
    super.setMenuVisibility(visible);
    if (visible && isResumed()) 
     {

     }
}

ใช้งานได้กับ ViewPagerFragments เท่านั้น ไม่ใช่สำหรับชิ้นส่วนในกิจกรรมปกติ
AndroidGuy

2

ฉันพบปัญหาเดียวกันขณะทำงานกับFragmentStatePagerAdaptersและ 3 แท็บ ฉันต้องแสดง Dilaog ทุกครั้งที่มีการคลิกแท็บที่ 1 และซ่อนไว้เมื่อคลิกที่แท็บอื่น

การเอาชนะsetUserVisibleHint()เพียงอย่างเดียวไม่ได้ช่วยให้พบชิ้นส่วนที่มองเห็นได้ในปัจจุบัน

เมื่อคลิกจากแท็บที่ 3 -----> แท็บที่ 1 มันทริกเกอร์สองครั้งสำหรับแฟรกเมนต์ที่ 2 และสำหรับแฟรกเมนต์ที่ 1 ฉันรวมกับวิธี 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.
    }

}

2

เรามีกรณีพิเศษกับ MVP fragment.onAttach()ที่ส่วนจะต้องมีการแจ้งให้ผู้นำเสนอมุมมองที่ได้กลายเป็นที่มองเห็นและพรีเซนเตอร์จะถูกฉีดโดยกริช

setUserVisibleHint()ไม่เพียงพอเราตรวจพบ 3 กรณีที่แตกต่างกันซึ่งจำเป็นต้องได้รับการแก้ไข ( onAttach()ถูกกล่าวถึงเพื่อให้คุณทราบเมื่อมีผู้นำเสนอ):

  1. แฟรกเมนต์เพิ่งถูกสร้างขึ้น ระบบโทรออกดังต่อไปนี้:

    setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
    onAttach()
    ...
    onResume()
  2. สร้างแฟรกเมนต์แล้วและกดปุ่มโฮม เมื่อเรียกคืนแอพให้เบื้องหน้าสิ่งนี้จะเรียกว่า:

    onResume()
  3. การวางแนวการเปลี่ยนแปลง:

    onAttach() // presenter available
    onResume()
    setUserVisibleHint()

เราต้องการให้คำใบ้มองเห็นได้ไปที่ผู้นำเสนอเพียงครั้งเดียวดังนั้นนี่คือวิธีที่เราทำ:

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View root = inflater.inflate(R.layout.fragment_list, container, false);
    setHasOptionsMenu(true);

    if (savedInstanceState != null) {
        lastOrientation = savedInstanceState.getInt(STATE_LAST_ORIENTATION,
              getResources().getConfiguration().orientation);
    } else {
        lastOrientation = getResources().getConfiguration().orientation;
    }

    return root;
}

@Override
public void onResume() {
    super.onResume();
    presenter.onResume();

    int orientation = getResources().getConfiguration().orientation;
    if (orientation == lastOrientation) {
        if (getUserVisibleHint()) {
            presenter.onViewBecomesVisible();
        }
    }
    lastOrientation = orientation;
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (presenter != null && isResumed() && isVisibleToUser) {
        presenter.onViewBecomesVisible();
    }
}

@Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt(STATE_LAST_ORIENTATION, lastOrientation);
}

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

1

ฉันพบปัญหานี้เมื่อฉันพยายามจับเวลาเพื่อให้ชิ้นส่วนใน viewpager ปรากฏบนหน้าจอเพื่อให้ผู้ใช้เห็น

ตัวจับเวลาเริ่มต้นเสมอก่อนที่ผู้ใช้จะเห็นแฟรกเมนต์ นี่เป็นเพราะonResume()วิธีการในส่วนที่เรียกว่าก่อนที่เราจะเห็นชิ้นส่วน

ทางออกของฉันคือการตรวจสอบในonResume()วิธีการ ฉันต้องการเรียกเมธอด 'foo ()' บางอย่างเมื่อแฟรกเมนต์ 8 เป็นมุมมองเพจเจอร์แฟรกเมนต์ปัจจุบัน

@Override
public void onResume() {
    super.onResume();
    if(viewPager.getCurrentItem() == 8){
        foo();
        //Your code here. Executed when fragment is seen by user.
    }
}

หวังว่านี่จะช่วยได้ ฉันเคยเห็นปัญหานี้ปรากฏขึ้นมากมาย นี่น่าจะเป็นทางออกที่ง่ายที่สุดที่ฉันเคยเห็น ผู้อื่นจำนวนมากเข้ากันไม่ได้กับ API ที่ต่ำกว่า ฯลฯ


1

ฉันมีปัญหาเดียวกัน ViewPagerเรียกใช้เหตุการณ์วงจรชีวิตส่วนอื่น ๆ และฉันไม่สามารถเปลี่ยนพฤติกรรมดังกล่าวได้ ฉันเขียนวิทยุติดตามตัวง่ายๆโดยใช้ชิ้นส่วนและภาพเคลื่อนไหวที่มี SimplePager



1

ฉันสนับสนุน 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;
    }
}

ลืมพูดถึงเมื่อคุณเพิ่มชิ้นส่วนย่อยของเด็กในชุดแฟรกเมนต์หลักชุดแฟรกเมนต์ setUserVisibleHint (จริง);
Andoctorey

และอย่าลืมใช้ getChildFragmentManager () แทน getFragmentManager () เพื่อเพิ่มชิ้นส่วนย่อย
Andoctorey

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

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

หากต้องการรีเซ็ตการเลือกเป็นตัวเลือกปัจจุบันให้เรียกใช้รหัสด้านล่าง

        navigationView.getMenu().getItem(0).setChecked(true);

-4

ฉันลบล้างวิธีการนับของ FragmentStatePagerAdapter ที่เกี่ยวข้องและให้มันคืนจำนวนรวมลบจำนวนหน้าเพื่อซ่อน:

 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 และควรแสดงเฉพาะ 2 แรกแรกเท่านั้นจนกว่าจะตรงตามเงื่อนไขบางอย่างแทนที่การนับหน้าด้วยการตั้งค่า TrimmedPages เป็น 1 และควรแสดงสองหน้าแรกเท่านั้น

วิธีนี้ใช้งานได้ดีสำหรับหน้าเว็บในตอนท้าย แต่จะไม่ช่วยให้หน้าเริ่มต้นหรือกลางจริง ๆ (แม้ว่าจะมีวิธีการทำเช่นนี้มากมาย)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.