Android: ฉันไม่สามารถมี ViewPager WRAP_CONTENT


258

ฉันติดตั้ง ViewPager ง่าย ๆ ที่มี ImageView ที่มีความสูง 200dp ในแต่ละหน้า

นี่คือวิทยุติดตามตัวของฉัน:

pager = new ViewPager(this);
pager.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
pager.setBackgroundColor(Color.WHITE);
pager.setOnPageChangeListener(listener);
layout.addView(pager);

แม้จะตั้งความสูงไว้เป็น wrap_content แต่เพจเจอร์เติมหน้าจอเสมอแม้ว่าภาพจะมีขนาดเพียง 200dp ฉันพยายามแทนที่ความสูงของเพจเจอร์ด้วย "200" แต่นั่นให้ผลลัพธ์ที่แตกต่างกับความละเอียดหลายระดับ ฉันไม่สามารถเพิ่ม "dp" ให้กับค่านั้น ฉันจะเพิ่ม 200dp ลงในเค้าโครงของเพจเจอร์ได้อย่างไร


คำตอบ:


408

การเอาชนะการวัดของคุณViewPagerดังต่อไปนี้จะทำให้เด็ก ๆ มีความสูงมากที่สุดในปัจจุบัน

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int height = 0;
    for(int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        int h = child.getMeasuredHeight();
        if(h > height) height = h;
    }

    if (height != 0) {
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
    }

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

24
สิ่งนี้ใกล้เคียงกับสิ่งที่ฉันต้องการ แต่มีสองสิ่งที่จะเพิ่ม: 1. ViewPager จะปรับขนาดเฉพาะเด็กที่ใหญ่ที่สุดของจริงนั่นคือเฉพาะรายการที่มองเห็นได้ในปัจจุบันและที่อยู่ติดกันโดยตรง การเรียกใช้ setOffscreenPageLimit (จำนวนเด็กทั้งหมด) บน ViewPager แก้ปัญหานี้และส่งผลให้ ViewPager มีขนาดที่ใหญ่ที่สุดของรายการทั้งหมดและไม่เคยปรับขนาด 2. WebViews มีปัญหาแปลก ๆ เมื่อพยายามวัด การร้องขอ RequestLayout () บน WebView หลังจากโหลดบางอย่างจะช่วยแก้ปัญหานั้น
0101100101

3
มีเพียงปัญหาเล็ก ๆ ที่ฉันจะแก้ไข: ถ้า viewPager มีการแสดงผลเป็น GONE และคุณตั้งให้มองเห็นได้ onMeasure จะถูกเรียกก่อนที่จะมีการสร้างชิ้นส่วน ดังนั้นจะมีความสูงเป็น 0 ถ้าใครมีความคิดเขายินดีต้อนรับ ฉันคิดว่าฉันจะไปด้วยการโทรกลับเมื่อชิ้นส่วนถูกสร้างขึ้น
edoardotognoni

4
สิ่งนี้จะไม่ทำงานหากคุณมีมุมมองเด็กที่มีการประดับ - นี่เป็นเพราะ ViewPager.onMeasure () วัดมุมมองการตกแต่งและจัดสรรพื้นที่ให้กับพวกเขาก่อนจากนั้นให้พื้นที่ที่เหลือให้กับเด็ก ๆ อย่างไรก็ตามนี้ไกลโดยวิธีการแก้ปัญหาที่ไม่ถูกต้องอย่างน้อยที่นี่ดังนั้นฉันได้ upvoted;)
เบนจามิน Dobell

3
ฉันกลับมาที่นี่ทุกครั้งที่ใช้ ViewPager
ono

7
getChildCount () อาจกลับ 0 ในขณะที่คุณได้ดำเนินการ setAdapter () ใน ViewPager! การเรียก populate () จริง (ที่สร้างมุมมอง) เกิดขึ้นภายใน super.onMeasure (widthMeasureSpec, heightMeasureSpec); โทร. การวางสาย super.onMeasure พิเศษ () ที่จุดเริ่มต้นของฟังก์ชั่นนี้ได้หลอกลวง ตรวจสอบstackoverflow.com/questions/38492210/…
southerton

106

อีกวิธีแก้ปัญหาทั่วไปเพิ่มเติมคือการwrap_contentไปทำงาน

เราได้ขยายไปแทนที่ViewPager onMeasure()ความสูงจะถูกล้อมรอบมุมมองลูกแรก สิ่งนี้อาจนำไปสู่ผลลัพธ์ที่ไม่คาดคิดหากมุมมองของเด็กไม่สูงเท่ากันทุกประการ สำหรับชั้นเรียนที่สามารถขยายได้อย่างง่ายดายเพื่อสมมติว่าเคลื่อนไหวกับขนาดของมุมมอง / หน้าปัจจุบัน แต่ฉันไม่ต้องการมัน

คุณสามารถใช้ ViewPager นี้ในรูปแบบ XML ของคุณเช่นเดียวกับ ViewPager ดั้งเดิม:

<view
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    class="de.cybergen.ui.layout.WrapContentHeightViewPager"
    android:id="@+id/wrapContentHeightViewPager"
    android:layout_alignParentBottom="true"
    android:layout_alignParentLeft="true"/>

ข้อได้เปรียบ: วิธีการนี้อนุญาตให้ใช้ ViewPager ในเค้าโครงใด ๆ รวมถึง RelativeLayout เพื่อวางซ้อนองค์ประกอบ ui อื่น ๆ

ข้อเสียเปรียบประการหนึ่งยังคงอยู่: หากคุณต้องการใช้ระยะขอบคุณจะต้องสร้างเค้าโครงที่ซ้อนกันสองรูปแบบและกำหนดระยะขอบภายในที่ต้องการ

นี่คือรหัส:

public class WrapContentHeightViewPager extends ViewPager {

    /**
     * Constructor
     *
     * @param context the context
     */
    public WrapContentHeightViewPager(Context context) {
        super(context);
    }

    /**
     * Constructor
     *
     * @param context the context
     * @param attrs the attribute set
     */
    public WrapContentHeightViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // find the first child view
        View view = getChildAt(0);
        if (view != null) {
            // measure the first child view with the specified measure spec
            view.measure(widthMeasureSpec, heightMeasureSpec);
        }

        setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, view));
    }

    /**
     * Determines the height of this view
     *
     * @param measureSpec A measureSpec packed into an int
     * @param view the base view with already measured height
     *
     * @return The height of the view, honoring constraints from measureSpec
     */
    private int measureHeight(int measureSpec, View view) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            // set the height from the base view if available
            if (view != null) {
                result = view.getMeasuredHeight();
            }
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

}

34
ใครมีหน้าว่างข้างรายการปัจจุบันเมื่อผู้ดูทำลายและเปิดอีกครั้ง?
Zyoo

1
ฉันมีหน้าว่างด้วย
อากาศ

10
คุณเพียงแค่ต้องรวมสองคำตอบด้านบนของคำถามนี้ตามที่อธิบายไว้ในบล็อกของฉัน: pristalovpavel.wordpress.com/2014/12/26/…
anil

4
เพียงแทนที่รหัสของวิธีการ 'onMeasure' ด้วยคำตอบที่ได้รับจาก 'Daniel López Lacalle'
Yog Guru

1
ที่ดี .. ! ใช้งานได้สำหรับฉัน .. @cybergen ขอบคุณมากคุณบันทึกวันของฉัน .. !
Dnyanesh M

59

ฉันตามคำตอบของฉันต่อ Daniel López Lacalle และโพสต์นี้http://www.henning.ms/2013/09/09/viewpager-that-simply-dont-measure-up/ ปัญหากับคำตอบของ Daniel คือในบางกรณีลูก ๆ ของฉันมีความสูงเป็นศูนย์ การแก้ปัญหาคือการวัดสองครั้งอย่างน่าเสียดาย

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int mode = MeasureSpec.getMode(heightMeasureSpec);
    // Unspecified means that the ViewPager is in a ScrollView WRAP_CONTENT.
    // At Most means that the ViewPager is not in a ScrollView WRAP_CONTENT.
    if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
        // super has to be called in the beginning so the child views can be initialized.
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            int h = child.getMeasuredHeight();
            if (h > height) height = h;
        }
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
    }
    // super has to be called again so the new specs are treated as exact measurements
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

นอกจากนี้ยังช่วยให้คุณสามารถตั้งค่าความสูงของ ViewPager ได้หากคุณต้องการหรือเพียงแค่ wrap_content


ฉันมีปัญหาเดียวกันและแก้ไขด้วยคำตอบของคุณขอบคุณ แต่คำอธิบายใด ๆ ว่าทำไม?
Bart Burg

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

ทำไมภาพใน ViewPager นี้จริงสั้นกว่าผู้ที่อยู่ใน ImageView ที่ใช้ในการเดียวกันscaleTypeและในทำนองเดียวกันlayout_width=match_parentเช่นเดียวกับlayout_height=wrap_content? มีเหมือน 20dp หายไปที่นั่น
ฉลาม

ฉลามฉันไม่แน่ใจจริงๆ ซึ่งอาจมีบางอย่างเกี่ยวกับสิ่งที่ประเภทเครื่องชั่งของคุณกำลังทำอยู่ อาจต้องการลองตั้งค่าความสูง
MinceMan

1
ฉันไม่สามารถเชื่อมันได้! ฉันใช้เวลา 2 วันในการผสานกันกับผู้ดูเพจเจอร์ที่กำหนดเองของฉันและติดอยู่กับปัญหาเมื่อมุมมองเริ่มต้นของฉันจะไม่ปรากฏขึ้นและฉันก็ไม่สามารถคิดออกว่าทำไม! // super has to be called in the beginning so the child views can be initialized.<----- นั่นคือเหตุผลที่ต้องเรียกมันตอนเริ่มต้นและท้ายฟังก์ชั่น onMeasure Yippiii, fives สูงเสมือนจริงกับฉันวันนี้!
Starwave

37

ฉันเพียงแค่ตอบคำถามที่คล้ายกันมากเกี่ยวกับเรื่องนี้และเกิดขึ้นเมื่อพบการเชื่อมโยงเพื่อสำรองการเรียกร้องของฉันโชคดีมาก :)

คำตอบอื่น ๆ ของฉัน:
ViewPager ไม่รองรับwrap_contentเนื่องจาก (โดยปกติ) ไม่เคยโหลดลูกทั้งหมดในเวลาเดียวกันดังนั้นจึงไม่สามารถรับขนาดที่เหมาะสมได้ (ตัวเลือกจะมีเพจเจอร์ที่เปลี่ยนขนาดทุกครั้งที่คุณสลับ หน้า).

อย่างไรก็ตามคุณสามารถตั้งค่ามิติที่แม่นยำ (เช่น 150dp) และmatch_parentทำงานได้ดี
นอกจากนี้คุณยังสามารถปรับเปลี่ยนขนาดแบบไดนามิกจากรหัสของคุณโดยการเปลี่ยนheight-attribute LayoutParamsในของมัน

สำหรับความต้องการของคุณคุณสามารถสร้าง ViewPager ในไฟล์ xml ของตัวเองโดยกำหนด layout_height เป็น 200dp จากนั้นในรหัสของคุณแทนที่จะสร้าง ViewPager ใหม่ตั้งแต่ต้นคุณสามารถขยายไฟล์ xml นั้นได้:

LayoutInflater inflater = context.getLayoutInflater();
inflater.inflate(R.layout.viewpagerxml, layout, true);

3
คำตอบที่ดีน่ารำคาญที่พฤติกรรมเริ่มต้นคือ "ทำสิ่งที่เข้าใจยาก" ขอบคุณสำหรับคำอธิบาย
Chris Vandevelde

8
@ChrisVandevelde นี่ดูเหมือนจะเป็นผู้เช่าทั่วไปของห้องสมุด Android บางแห่ง ทันทีที่คุณเรียนรู้พื้นฐานคุณจะไม่เข้าใจสิ่งใดเลยพวกเขา
CQM

1
แต่ @Jave ทำไมผู้ดูจึงไม่สามารถปรับความสูงได้ทุกครั้งที่ลูกโหลด?
Diffy

@CQM แน่นอน! ไลบรารี ViewPagerIndicator มีปัญหาเหมือนกันกับการlayout_heightตั้งค่าwrap_contentแต่มันยิ่งแย่ลงเพราะวิธีแก้ปัญหาง่าย ๆ ในการตั้งค่าเป็นจำนวนคงที่ไม่ทำงาน
Giulio Piancastelli

20

ใช้คำตอบของDaniel López Localleฉันสร้างคลาสนี้ใน Kotlin หวังว่าจะช่วยให้คุณประหยัดเวลามากขึ้น

class DynamicHeightViewPager @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : ViewPager(context, attrs) {

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    var heightMeasureSpec = heightMeasureSpec

    var height = 0
    for (i in 0 until childCount) {
        val child = getChildAt(i)
        child.measure(widthMeasureSpec, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
        val h = child.measuredHeight
        if (h > height) height = h
    }

    if (height != 0) {
        heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
    }

    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}}

16

ฉันประสบกับปัญหานี้ในหลายโครงการแล้วและไม่เคยมีวิธีการแก้ปัญหาที่สมบูรณ์ ดังนั้นฉันจึงสร้างโปรเจ็กต์ WrapContentViewPager github เป็นการแทนที่แบบแทนที่สำหรับ ViewPager

https://github.com/rnevet/WCViewPager

โซลูชันได้รับแรงบันดาลใจจากคำตอบบางส่วนที่นี่ แต่ปรับปรุงเมื่อ:

  • เปลี่ยนความสูงของ ViewPager ตามมุมมองปัจจุบันรวมถึงขณะเลื่อน
  • พิจารณาความสูงของมุมมอง "การตกแต่ง" เช่น PagerTabStrip
  • พิจารณาการเสริมความแข็งแรงทั้งหมด

อัปเดตสำหรับไลบรารีสนับสนุนรุ่น 24 ซึ่งทำให้การติดตั้งก่อนหน้านี้ไม่ราบรื่น


@mvai คุณสามารถเปิดปัญหาหรือแยกและแก้ไขแอปตัวอย่างได้หรือไม่
Raanan

1
ฉันพบว่า RecyclerView มีปัญหา wrap_content ด้วยเช่นกัน มันทำงานได้ถ้าคุณใช้ LinearLayoutManager ที่กำหนดเองเช่นนี้ ไม่มีอะไรผิดปกติกับห้องสมุดของคุณ
นาตาริโอ

1
สิ่งที่ยังต้องแก้ไขคือการใช้กับ FragmentStatePagerAdapter ดูเหมือนว่าจะเป็นการวัดเด็กก่อนที่จะวางเศษดังนั้นจึงให้ความสูงขนาดเล็ก สิ่งที่ได้ผลสำหรับฉันคือคำตอบของ @logan แม้ว่าฉันจะยังคงทำมันอยู่ คุณอาจต้องการลองผสานวิธีการนี้เข้ากับห้องสมุดของคุณ ฉันไม่คุ้นเคยกับ GitHub ขออภัย
นาตาริโอ

ขอบคุณฉันจะดูที่
Raanan

1
สำหรับทุกคนที่สงสัยว่าจะใช้งาน FragmentPagerAdapter ได้อย่างไรให้อแดปเตอร์ของคุณใช้ ObjectAtPositionInterface โดยเก็บรายชื่อของ Fragments ไว้ภายในเพื่อให้มันสามารถคืนค่า Fragment ที่สอดคล้องกันจากเมธอด getObjectAtPosition
Pablo

15

ฉันเพิ่งชนเป็นปัญหาเดียวกัน ฉันมี ViewPager และฉันต้องการแสดงโฆษณาที่ปุ่มของมัน วิธีแก้ปัญหาที่ฉันพบคือการนำเพจเจอร์เข้าสู่ RelativeView และตั้งค่าเป็น layout_above กับ id มุมมองที่ฉันต้องการดูด้านล่าง ที่ทำงานให้ฉัน

นี่คือเค้าโครง XML ของฉัน:

  <RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:id="@+id/AdLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="vertical" >
    </LinearLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/mainpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/AdLayout" >
    </android.support.v4.view.ViewPager>
</RelativeLayout>

4
เพียงเพื่อการอ้างอิงคุณไม่จำเป็นต้องใช้ xmlns: android = " schemas.android.com/apk/res/android " ทั้งคู่เฉพาะในอันแรก
Martin Marconcini

2
ปัญหาของคุณไม่เหมือนกันเลย เลย์เอาต์ของคุณทำงานได้ดีกับ ViewPager ที่กำหนดให้เป็น match_parent - OP มีสถานการณ์ที่เขาต้องการให้ ViewPager ห่อเนื้อหาของมัน
k2col

9

ฉันพบปัญหานี้ด้วยเช่นกัน แต่ในกรณีของฉันฉันได้FragmentPagerAdapterรับข้อความแจ้งViewPagerจากหน้าเว็บ ปัญหาที่ผมมีก็คือว่าonMeasure()ของViewPagerถูกเรียกว่าก่อนที่จะมีการFragmentsได้รับการสร้างขึ้น (และดังนั้นจึงไม่สามารถขนาดตัวเองอย่างถูกต้อง)

หลังจากการทดลองใช้และข้อผิดพลาดเล็กน้อยฉันพบว่าfinishUpdate()วิธีการของ FragmentPagerAdapter นั้นถูกเรียกใช้หลังจากที่Fragmentsถูกเตรียมใช้งาน (จากinstantiateItem()ในFragmentPagerAdapter) และหลังจาก / ระหว่างการเลื่อนหน้า ฉันสร้างส่วนต่อประสานขนาดเล็ก:

public interface AdapterFinishUpdateCallbacks
{
    void onFinishUpdate();
}

ซึ่งฉันผ่านเข้ามาFragmentPagerAdapterและโทรของฉัน:

@Override
public void finishUpdate(ViewGroup container)
{
    super.finishUpdate(container);

    if (this.listener != null)
    {
        this.listener.onFinishUpdate();
    }
}

ซึ่งในทางกลับกันทำให้ฉันสามารถโทรหาการดำเนินการsetVariableHeight()ของฉันCustomViewPager:

public void setVariableHeight()
{
    // super.measure() calls finishUpdate() in adapter, so need this to stop infinite loop
    if (!this.isSettingHeight)
    {
        this.isSettingHeight = true;

        int maxChildHeight = 0;
        int widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
        for (int i = 0; i < getChildCount(); i++)
        {
            View child = getChildAt(i);
            child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED));
            maxChildHeight = child.getMeasuredHeight() > maxChildHeight ? child.getMeasuredHeight() : maxChildHeight;
        }

        int height = maxChildHeight + getPaddingTop() + getPaddingBottom();
        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);

        super.measure(widthMeasureSpec, heightMeasureSpec);
        requestLayout();

        this.isSettingHeight = false;
    }
}

ฉันไม่แน่ใจว่ามันเป็นวิธีที่ดีที่สุดจะรักความคิดเห็นถ้าคุณคิดว่ามันเป็นสิ่งที่ดี / ไม่ดี / ความชั่ว แต่ดูเหมือนว่าจะทำงานได้ดีในการใช้งานของฉัน :)

หวังว่านี่จะช่วยให้ใครบางคนที่นั่น!

แก้ไข:ฉันลืมที่จะเพิ่มrequestLayout()หลังจากโทรsuper.measure()(มิฉะนั้นจะไม่วาดมุมมองใหม่)

ฉันลืมเพิ่มช่องว่างภายในของผู้ปกครองลงในความสูงสุดท้าย

ฉันยังลดขนาดการวัดความกว้าง / ความสูงดั้งเดิมไว้ด้วยเพื่อสร้างใหม่ตามที่ต้องการ ได้ปรับปรุงรหัสตาม

ผมมีปัญหาอีกประการหนึ่งคือว่ามันจะไม่ได้ขนาดตัวเองอย่างถูกต้องในScrollViewและพบว่าผู้กระทำผิดได้รับการวัดเด็กที่มีแทนMeasureSpec.EXACTLY MeasureSpec.UNSPECIFIEDอัปเดตเพื่อสะท้อนถึงสิ่งนี้

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


ทำไมคุณไม่เพิ่มที่คุณลืมรหัส
hasan

@ ฮาซานฉันทำไปแล้วขอโทษด้วยความสับสน! จะอัปเดตคำตอบเพื่อบอกว่า
โลแกน

! น่ากลัว ดีใจที่มันช่วย :)
โลแกน

8

อีกวิธีคือการปรับปรุงViewPagerความสูงตามความสูงของหน้าปัจจุบันในPagerAdapterนั้น สมมติว่าคุณกำลังสร้างViewPagerหน้าของคุณด้วยวิธีนี้:

@Override
public Object instantiateItem(ViewGroup container, int position) {
  PageInfo item = mPages.get(position);
  item.mImageView = new CustomImageView(container.getContext());
  item.mImageView.setImageDrawable(item.mDrawable);
  container.addView(item.mImageView, 0);
  return item;
}

ที่ไหนmPagesคือรายการภายในของPageInfoโครงสร้างที่เพิ่มเข้ากับแบบไดนามิกPagerAdapterและCustomImageViewเป็นเพียงปกติImageViewด้วยonMeasure()วิธีการoverriden ที่ตั้งค่าความสูงตามความกว้างที่ระบุและรักษาอัตราส่วนภาพ

คุณสามารถบังคับViewPagerความสูงด้วยsetPrimaryItem()วิธีการ:

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
  super.setPrimaryItem(container, position, object);

  PageInfo item = (PageInfo) object;
  ViewPager pager = (ViewPager) container;
  int width = item.mImageView.getMeasuredWidth();
  int height = item.mImageView.getMeasuredHeight();
  pager.setLayoutParams(new FrameLayout.LayoutParams(width, Math.max(height, 1)));
}

Math.max(height, 1)หมายเหตุ ที่แก้ไขข้อผิดพลาดที่น่ารำคาญที่ViewPagerไม่อัปเดตหน้าแสดง (แสดงว่าง) เมื่อหน้าก่อนหน้ามีความสูงเป็นศูนย์ (เช่น null drawable ในCustomImageView) แต่ละหน้าจอจะปัดไปมาระหว่างสองหน้า


ดูเหมือนว่าฉันจะเป็นเส้นทางที่ถูกต้องในการติดตาม แต่ฉันต้องการโฆษณาitem.mImageView.measure(..)เพื่อให้ได้มิติที่ถูกต้องในgetMeasuredXXX()วิธีการ
Gianluca P.

6

เมื่อใช้เนื้อหาแบบคงที่ภายใน viewpager และคุณไม่ต้องการให้มีแอนิเมชั่นแฟนซีคุณสามารถใช้เพจเจอร์มุมมองต่อไปนี้

public class HeightWrappingViewPager extends ViewPager {

  public HeightWrappingViewPager(Context context) {
    super(context);
  }

  public HeightWrappingViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)   {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      View firstChild = getChildAt(0);
      firstChild.measure(widthMeasureSpec, heightMeasureSpec);
      super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(firstChild.getMeasuredHeight(), MeasureSpec.EXACTLY));
  }
}

มันใช้งานได้ดี ฉันขยายมันโดยการวนลูปผ่านเด็ก ๆ และหยิบอันที่มีความสูงสูงสุด
Javier Mendonça

ทำงานได้ดีแม้อยู่ในมุมมองของนักรีไซเคิล
kanudo

ฉันได้รับข้อยกเว้นนี้ - java.lang.NullPointerException: พยายามเรียกใช้วิธีเสมือน 'void android.view.View.measure (int, int)' ในการอ้างอิงวัตถุ null
PJ2104

แต่การพิจารณาองค์ประกอบแรกอาจเป็นสิ่งที่ผิด
Tobias Reich

4
public CustomPager (Context context) {
    super(context);
}

public CustomPager (Context context, AttributeSet attrs) {
    super(context, attrs);
}

int getMeasureExactly(View child, int widthMeasureSpec) {
    child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
    int height = child.getMeasuredHeight();
    return MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;

    final View tab = getChildAt(0);
    if (tab == null) {
        return;
    }

    int width = getMeasuredWidth();
    if (wrapHeight) {
        // Keep the current measured width.
        widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
    }
    Fragment fragment = ((Fragment) getAdapter().instantiateItem(this, getCurrentItem()));
    heightMeasureSpec = getMeasureExactly(fragment.getView(), widthMeasureSpec);

    //Log.i(Constants.TAG, "item :" + getCurrentItem() + "|height" + heightMeasureSpec);
    // super has to be called again so the new specs are treated as
    // exact measurements.
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

4

จากซอร์สโค้ดของแอพ android ของ Popcorn time ฉันพบโซลูชันนี้ซึ่งปรับขนาดของ viewpager ด้วยแอนิเมชั่นที่ดีขึ้นอยู่กับขนาดของเด็กปัจจุบัน

https://git.popcorntime.io/popcorntime/android/blob/5934f8d0c8fed39af213af4512272d12d2efb6a6/mobile/src/main/java/pct/droid/widget/WrappingViewPager.java

public class WrappingViewPager extends ViewPager {

    private Boolean mAnimStarted = false;

    public WrappingViewPager(Context context) {
        super(context);
    }

    public WrappingViewPager(Context context, AttributeSet attrs){
        super(context, attrs);
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if(!mAnimStarted && null != getAdapter()) {
            int height = 0;
            View child = ((FragmentPagerAdapter) getAdapter()).getItem(getCurrentItem()).getView();
            if (child != null) {
                child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                height = child.getMeasuredHeight();
                if (VersionUtils.isJellyBean() && height < getMinimumHeight()) {
                    height = getMinimumHeight();
                }
            }

            // Not the best place to put this animation, but it works pretty good.
            int newHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
            if (getLayoutParams().height != 0 && heightMeasureSpec != newHeight) {
                    final int targetHeight = height;
                    final int currentHeight = getLayoutParams().height;
                    final int heightChange = targetHeight - currentHeight;

                    Animation a = new Animation() {
                        @Override
                        protected void applyTransformation(float interpolatedTime, Transformation t) {
                            if (interpolatedTime >= 1) {
                                getLayoutParams().height = targetHeight;
                            } else {
                                int stepHeight = (int) (heightChange * interpolatedTime);
                                getLayoutParams().height = currentHeight + stepHeight;
                            }
                            requestLayout();
                        }

                        @Override
                        public boolean willChangeBounds() {
                            return true;
                        }
                    };

                    a.setAnimationListener(new Animation.AnimationListener() {
                        @Override
                        public void onAnimationStart(Animation animation) {
                            mAnimStarted = true;
                        }

                        @Override
                        public void onAnimationEnd(Animation animation) {
                            mAnimStarted = false;
                        }

                        @Override
                        public void onAnimationRepeat(Animation animation) {
                        }
                    });

                    a.setDuration(1000);
                    startAnimation(a);
                    mAnimStarted = true;
            } else {
                heightMeasureSpec = newHeight;
            }
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

4

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

android: สนับสนุนธงminHeightด้วย

public class ChildWrappingAdjustableViewPager extends ViewPager {
    List<Integer> childHeights = new ArrayList<>(getChildCount());
    int minHeight = 0;
    int currentPos = 0;

    public ChildWrappingAdjustableViewPager(@NonNull Context context) {
        super(context);
        setOnPageChangeListener();
    }

    public ChildWrappingAdjustableViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        obtainMinHeightAttribute(context, attrs);
        setOnPageChangeListener();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {            
        childHeights.clear();

        //calculate child views
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            int h = child.getMeasuredHeight();
            if (h < minHeight) {
                h = minHeight;
            }
            childHeights.add(i, h);
        }

        if (childHeights.size() - 1 >= currentPos) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeights.get(currentPos), MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private void obtainMinHeightAttribute(@NonNull Context context, @Nullable AttributeSet attrs) {
        int[] heightAttr = new int[]{android.R.attr.minHeight};
        TypedArray typedArray = context.obtainStyledAttributes(attrs, heightAttr);
        minHeight = typedArray.getDimensionPixelOffset(0, -666);
        typedArray.recycle();
    }

    private void setOnPageChangeListener() {
        this.addOnPageChangeListener(new SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                currentPos = position;

                ViewGroup.LayoutParams layoutParams = ChildWrappingAdjustableViewPager.this.getLayoutParams();
                layoutParams.height = childHeights.get(position);
                ChildWrappingAdjustableViewPager.this.setLayoutParams(layoutParams);
                ChildWrappingAdjustableViewPager.this.invalidate();
            }
        });
    }
}

ดังนั้นอะแดปเตอร์นี้จึงมีปัญหาอย่างมากเมื่อจำนวนรายการในอะแดปเตอร์เปลี่ยนไป
jobbert

คุณสามารถชี้แจงแถลงการณ์ของคุณได้หรือไม่?
Phatee P

รหัสนี้อาจทำให้ตัวชี้โมฆะไม่ได้เพราะเด็กทุกคนจะถูกคำนวณเมื่อเริ่มต้น ลองใช้เค้าโครงแท็บและเลื่อนจาก 1 ถึง 5 หรือรหัสอย่างชาญฉลาดแล้วคุณจะเห็น
jobbert

4

ปรับปรุงคำตอบของDaniel López Lacalleซึ่งเขียนใหม่ในKotlin :

class MyViewPager(context: Context, attrs: AttributeSet): ViewPager(context, attrs) {
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val zeroHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)

        val maxHeight = children
            .map { it.measure(widthMeasureSpec, zeroHeight); it.measuredHeight }
            .max() ?: 0

        if (maxHeight > 0) {
            val maxHeightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY)
            super.onMeasure(widthMeasureSpec, maxHeightSpec)
            return
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }
}

3

ฉันชนกับปัญหาเดียวกันและฉันต้องทำให้ ViewPager ล้อมรอบเนื้อหาเมื่อผู้ใช้เลื่อนระหว่างหน้า ใช้คำตอบข้างต้นของ cybergen ฉันกำหนดวิธีการ onMeasure ดังต่อไปนี้:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    if (getCurrentItem() < getChildCount()) {
        View child = getChildAt(getCurrentItem());
        if (child.getVisibility() != GONE) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
                    MeasureSpec.UNSPECIFIED);
            child.measure(widthMeasureSpec, heightMeasureSpec);
        }

        setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, getChildAt(getCurrentItem())));            
    }
}

วิธีนี้วิธี onMeasure จะตั้งค่าความสูงของหน้าปัจจุบันที่แสดงโดย ViewPager


มีเพียงเนื้อหาส่วนสูงเท่านั้นที่ปรากฏพร้อมกับคำตอบของคุณเนื้อหาอื่น ๆ จะหายไป ...
Blaze Tama

2

ข้อเสนอแนะข้างต้นไม่เหมาะกับฉัน กรณีการใช้งานของฉันมี ViewPagers แบบกำหนดเอง 4 ScrollViewตัว layout_height=wrap_contentด้านบนของพวกเขานั้นวัดจากอัตราส่วนและส่วนที่เหลือก็มี ฉันได้พยายามcybergen , แดเนียลLópez Lacalleการแก้ปัญหา พวกเขาไม่ทำงานอย่างเต็มที่สำหรับฉัน

ฉันเดาว่าทำไมไซเบอร์เจนไม่ทำงานในหน้า> 1 เพราะมันคำนวณความสูงของเพจเจอร์ตามหน้า 1 ซึ่งจะถูกซ่อนหากคุณเลื่อนดูเพิ่มเติม

คำแนะนำทั้งไซเบอร์และDaniel López Lacalleมีพฤติกรรมแปลก ๆ ในกรณีของฉัน: โหลดได้ 2 จาก 3 ตัวและมีความสูงแบบสุ่ม 1 ตัวคือ 0 ปรากฏว่าonMeasureมีการเรียกก่อนที่เด็กจะมีประชากร ดังนั้นฉันจึงได้คำตอบ 2 ข้อ + การแก้ไขของฉัน:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
        // find the first child view
        View view = getChildAt(0);
        if (view != null) {
            // measure the first child view with the specified measure spec
            view.measure(widthMeasureSpec, heightMeasureSpec);
            int h = view.getMeasuredHeight();
            setMeasuredDimension(getMeasuredWidth(), h);
            //do not recalculate height anymore
            getLayoutParams().height = h;
        }
    }
}

ความคิดคือการปล่อยให้ViewPagerมิติคำนวณสำหรับเด็กและบันทึกความสูงคำนวณของหน้าแรกใน params ViewPagerรูปแบบของ อย่าลืมตั้งค่าความสูงของเลย์เอาต์เป็นwrap_contentมิฉะนั้นคุณสามารถรับ height = 0 ฉันเคยใช้อันนี้:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="wrap_content">
        <!-- Childs are populated in fragment -->
</LinearLayout>

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


คุณยังคงสามารถปรับปรุงคำตอบของคุณหลังจากทุกปี? จะช่วยฉันได้ไหม
Denny

2

สำหรับผู้ที่มีปัญหานี้และการเข้ารหัสสำหรับ Xamarin Android ใน C # นี่อาจเป็นวิธีแก้ปัญหาอย่างรวดเร็ว:

pager.ChildViewAdded += (sender, e) => {
    e.Child.Measure ((int)MeasureSpecMode.Unspecified, (int)MeasureSpecMode.Unspecified);
    e.Parent.LayoutParameters.Height = e.Child.MeasuredHeight;
};

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

วิธีแก้ปัญหานั้นไม่เพียงพอสำหรับฉัน แต่นั่นเป็นเพราะรายการลูกของฉันคือ listViews และการวัดความสูงของพวกเขาไม่ได้คำนวณอย่างถูกต้องดูเหมือนว่า


สิ่งนี้ใช้ได้สำหรับฉัน มุมมองลูกของฉันทั้งหมดใน viewpager มีความสูงเท่ากัน
มิทรี

2

ฉันมีเวอร์ชันของ WrapContentHeightViewPager ที่ทำงานอย่างถูกต้องก่อน API 23 ที่จะปรับขนาดฐานความสูงของมุมมองพาเรนต์ในมุมมองย่อยที่เลือกไว้

หลังจากอัปเกรดเป็น API 23 มันหยุดทำงาน ปรากฎว่าโซลูชันเดิมใช้getChildAt(getCurrentItem())เพื่อรับมุมมองลูกปัจจุบันเพื่อวัดว่าไม่ทำงาน ดูวิธีแก้ปัญหาที่นี่: https://stackoverflow.com/a/16512217/1265583

ด้านล่างใช้งานได้กับ API 23:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int height = 0;
    ViewPagerAdapter adapter = (ViewPagerAdapter)getAdapter();
    View child = adapter.getItem(getCurrentItem()).getView();
    if(child != null) {
        child.measure(widthMeasureSpec,  MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        height = child.getMeasuredHeight();
    }
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

ขอบคุณ!! ลองคำตอบมาหลายชั่วโมงแล้วและนี่ก็เป็นคำตอบเดียวที่เหมาะกับฉัน มันจะต้องรวมกับอะแดปเตอร์ที่กำหนดเองที่ 'setPrimaryItem () `เรียกใช้ฟังก์ชันในเพจเจอร์ที่เรียกrequestLayout()เพื่อปรับความสูงเมื่อเราไปจากแท็บหนึ่งไปยังอีก คุณจำได้ไหมว่าทำไมsuperต้องถูกเรียกสองครั้ง ฉันสังเกตเห็นว่ามันจะไม่ทำงานอย่างอื่น
M3RS

ใช้งานได้กับ API 28
Khalid Lakhani

2

โค้ดด้านล่างเป็นสิ่งเดียวที่ใช้ได้ผลสำหรับฉัน

1. ใช้คลาสนี้เพื่อประกาศ HeightWrappingViewPager:

 public class HeightWrappingViewPager extends ViewPager {

        public HeightWrappingViewPager(Context context) {
            super(context);
        }

        public HeightWrappingViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int mode = MeasureSpec.getMode(heightMeasureSpec);
            // Unspecified means that the ViewPager is in a ScrollView WRAP_CONTENT.
            // At Most means that the ViewPager is not in a ScrollView WRAP_CONTENT.
            if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
                // super has to be called in the beginning so the child views can be initialized.
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                int height = 0;
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                    int h = child.getMeasuredHeight();
                    if (h > height) height = h;
                }
                heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
            }
            // super has to be called again so the new specs are treated as exact measurements
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

2. แทรกเพจเจอร์มุมมองการตัดความสูงลงในไฟล์ xml ของคุณ:

<com.project.test.HeightWrappingViewPager
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</com.project.test.HeightWrappingViewPager>

3. ประกาศเพจเจอร์มุมมองของคุณ:

HeightWrappingViewPager mViewPager;
mViewPager = (HeightWrappingViewPager) itemView.findViewById(R.id.pager);
CustomAdapter adapter = new CustomAdapter(context);
mViewPager.setAdapter(adapter);
mViewPager.measure(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);

ขอบคุณ สิ่งนี้ใช้ได้ผล แต่ทำไมทีม Android ถึงมีฐานรหัสไม่ได้
Mohanakrrishna

นี่คือหนึ่งในสิ่งที่คุณต้องปรับแต่งเองขึ้นอยู่กับความต้องการของคุณอีกทั้ง Google ได้เปิดตัว viewPager2 ในปี 2019 Google I / O และใช้ทดแทน ViewPager รุ่นเก่าซึ่งสร้างขึ้นในปี 2011 การนำ 'androidx.viewpager2: viewpager2 : 1.0.0-alpha04 '
Hossam Hassan

2

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

นี่คือคลาส:

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;

import java.util.Vector;

public class WrapContentHeightViewPager extends ViewPager {
    private Vector<Integer> heights = new Vector<>();

    public WrapContentHeightViewPager(@NonNull Context context) {
        super(context);
    }

    public WrapContentHeightViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        for(int i=0;i<getChildCount();i++) {
            View view = getChildAt(i);
            if (view != null) {
                view.measure(widthMeasureSpec, heightMeasureSpec);
                heights.add(measureHeight(heightMeasureSpec, view));
            }
        }
        setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, getChildAt(0)));
    }

    public int getHeightAt(int position){
        return heights.get(position);
    }

    private int measureHeight(int measureSpec, View view) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            if (view != null) {
                result = view.getMeasuredHeight();
            }
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }
}

จากนั้นในกิจกรรมของคุณให้เพิ่ม OnPageChangeListener

WrapContentHeightViewPager viewPager = findViewById(R.id.my_viewpager);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
     @Override
     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
     @Override
     public void onPageSelected(int position) {
         LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) viewPager.getLayoutParams();
         params.height = viewPager.getHeightAt(position);
         viewPager.setLayoutParams(params);
     }
     @Override
     public void onPageScrollStateChanged(int state) {}
});

และนี่คือ xml:

<com.example.example.WrapContentHeightViewPager
    android:id="@+id/my_viewpager"
    android:fillViewport="true"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

โปรดแก้ไขภาษาอังกฤษของฉันหากจำเป็น


มีปัญหาบางอย่าง heightsรายการอาจเพิ่มขึ้นอินฟินิตี้
rosuh

@rosuh คุณพบปัญหาเมื่อใด ฉันใช้สิ่งนี้เฉพาะใน TabLayout กับ ViewPager ดังนั้นฉันจึงไม่แน่ใจว่ามันใช้งานได้ดีทุกที่หรือไม่
geggiamarti

@geggiamarti ปัญหาคือบางหน้าจะถูกรีไซเคิล และสร้างใหม่เมื่อผู้ใช้ปัดไปที่พวกเขาดังนั้นmeasureจะมีการโทรหลายครั้ง มันอาจเพิ่มรายการความสูง สถานการณ์อื่นคือผู้ใช้อาจเรียกrequestLayout(หรือsetLayoutParamsวิธีการเช่นเดียวกับสิ่งที่คุณทำ) สำหรับ viewPager นี้ด้วยตนเองก็จะวัดหลายครั้ง
rosuh

1

หากViewPagerคุณกำลังใช้ลูกของScrollView AND และมีPagerTitleStripลูกคุณจะต้องใช้การแก้ไขเล็กน้อยของคำตอบที่ดีที่มีอยู่แล้ว สำหรับการอ้างอิง XML ของฉันมีลักษณะเช่นนี้:

<ScrollView
    android:id="@+id/match_scroll_view"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/white">

    <LinearLayout
        android:id="@+id/match_and_graphs_wrapper"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <view
            android:id="@+id/pager"
            class="com.printandpixel.lolhistory.util.WrapContentHeightViewPager"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <android.support.v4.view.PagerTitleStrip
                android:id="@+id/pager_title_strip"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="top"
                android:background="#33b5e5"
                android:paddingBottom="4dp"
                android:paddingTop="4dp"
                android:textColor="#fff" />
        </view>
    </LinearLayout>
</ScrollView>

ในของonMeasureคุณคุณต้องเพิ่มความสูงของPagerTitleStripถ้ามีใครพบ มิฉะนั้นความสูงของมันจะไม่ถูกพิจารณาเป็นความสูงที่ใหญ่ที่สุดของเด็กทุกคนแม้ว่าจะใช้พื้นที่เพิ่มขึ้น

หวังว่านี่จะช่วยคนอื่นได้ ขออภัยที่เป็นแฮ็ก ...

public class WrapContentHeightViewPager extends ViewPager {

    public WrapContentHeightViewPager(Context context) {
        super(context);
    }

    public WrapContentHeightViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int pagerTitleStripHeight = 0;
        int height = 0;
        for(int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            int h = child.getMeasuredHeight();
            if (h > height) {
                // get the measuredHeight of the tallest fragment
                height = h;
            }
            if (child.getClass() == PagerTitleStrip.class) {
                // store the measured height of the pagerTitleStrip if one is found. This will only
                // happen if you have a android.support.v4.view.PagerTitleStrip as a direct child
                // of this class in your XML.
                pagerTitleStripHeight = h;
            }
        }

        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height+pagerTitleStripHeight, MeasureSpec.EXACTLY);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

1

วิธีแก้ปัญหาส่วนใหญ่ที่ฉันเห็นที่นี่ดูเหมือนจะเป็นการวัดสองเท่า: การวัดมุมมองเด็กก่อนแล้วจึงเรียก super.onMeasure()

ฉันได้กำหนดแบบกำหนดเองWrapContentViewPagerที่มีประสิทธิภาพมากขึ้นทำงานได้ดีกับ RecyclerView และ Fragment

คุณสามารถตรวจสอบตัวอย่างได้ที่นี่:

GitHub / ssynhtn / WrapContentViewPager

และรหัสของคลาสที่นี่: WrapContentViewPager.java


0

ฉันมีสถานการณ์ที่คล้ายกัน (แต่มีความซับซ้อนมากกว่า) ฉันมีกล่องโต้ตอบซึ่งมี ViewPager
หน้าย่อยหนึ่งหน้ามีความสูงคงที่
หน้าย่อยอีกหน้าควรสูงที่สุดเท่าที่จะทำได้
หน้าย่อยอีกอันหนึ่งมี ScrollView และหน้า (รวมถึงไดอะล็อกทั้งหมด) ควร WRAP_CONTENT หากเนื้อหา ScrollView ไม่ต้องการความสูงเต็มพร้อมใช้งานสำหรับไดอะล็อก

คำตอบที่มีอยู่ไม่ทำงานอย่างสมบูรณ์สำหรับสถานการณ์เฉพาะนี้ กดค้างไว้ - มันเป็นการเดินทางที่เป็นหลุมเป็นบ่อ

void setupView() {
    final ViewPager.SimpleOnPageChangeListener pageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
            currentPagePosition = position;

            // Update the viewPager height for the current view

            /*
            Borrowed from https://github.com/rnevet/WCViewPager/blob/master/wcviewpager/src/main/java/nevet/me/wcviewpager/WrapContentViewPager.java
            Gather the height of the "decor" views, since this height isn't included
            when measuring each page's view height.
             */
            int decorHeight = 0;
            for (int i = 0; i < viewPager.getChildCount(); i++) {
                View child = viewPager.getChildAt(i);
                ViewPager.LayoutParams lp = (ViewPager.LayoutParams) child.getLayoutParams();
                if (lp != null && lp.isDecor) {
                    int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
                    boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
                    if (consumeVertical) {
                        decorHeight += child.getMeasuredHeight();
                    }
                }
            }

            int newHeight = decorHeight;

            switch (position) {
                case PAGE_WITH_SHORT_AND_STATIC_CONTENT:
                    newHeight += measureViewHeight(thePageView1);
                    break;
                case PAGE_TO_FILL_PARENT:
                    newHeight = ViewGroup.LayoutParams.MATCH_PARENT;
                    break;
                case PAGE_TO_WRAP_CONTENT:
//                  newHeight = ViewGroup.LayoutParams.WRAP_CONTENT; // Works same as MATCH_PARENT because...reasons...
//                  newHeight += measureViewHeight(thePageView2); // Doesn't allow scrolling when sideways and height is clipped

                    /*
                    Only option that allows the ScrollView content to scroll fully.
                    Just doing this might be way too tall, especially on tablets.
                    (Will shrink it down below)
                     */
                    newHeight = ViewGroup.LayoutParams.MATCH_PARENT;
                    break;
            }

            // Update the height
            ViewGroup.LayoutParams layoutParams = viewPager.getLayoutParams();
            layoutParams.height = newHeight;
            viewPager.setLayoutParams(layoutParams);

            if (position == PAGE_TO_WRAP_CONTENT) {
                // This page should wrap content

                // Measure height of the scrollview child
                View scrollViewChild = ...; // (generally this is a LinearLayout)
                int scrollViewChildHeight = scrollViewChild.getHeight(); // full height (even portion which can't be shown)
                // ^ doesn't need measureViewHeight() because... reasons...

                if (viewPager.getHeight() > scrollViewChildHeight) { // View pager too tall?
                    // Wrap view pager height down to child height
                    newHeight = scrollViewChildHeight + decorHeight;

                    ViewGroup.LayoutParams layoutParams2 = viewPager.getLayoutParams();
                    layoutParams2.height = newHeight;
                    viewPager.setLayoutParams(layoutParams2);
                }
            }

            // Bonus goodies :)
            // Show or hide the keyboard as appropriate. (Some pages have EditTexts, some don't)
            switch (position) {
                // This case takes a little bit more aggressive code than usual

                if (position needs keyboard shown){
                    showKeyboardForEditText();
                } else if {
                    hideKeyboard();
                }
            }
        }
    };

    viewPager.addOnPageChangeListener(pageChangeListener);

    viewPager.getViewTreeObserver().addOnGlobalLayoutListener(
            new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    // http://stackoverflow.com/a/4406090/4176104
                    // Do things which require the views to have their height populated here
                    pageChangeListener.onPageSelected(currentPagePosition); // fix the height of the first page

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                        viewPager.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    } else {
                        viewPager.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    }

                }
            }
    );
}


...

private void showKeyboardForEditText() {
    // Make the keyboard appear.
    getDialog().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
    getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);

    inputViewToFocus.requestFocus();

    // http://stackoverflow.com/a/5617130/4176104
    InputMethodManager inputMethodManager =
            (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
    inputMethodManager.toggleSoftInputFromWindow(
            inputViewToFocus.getApplicationWindowToken(),
            InputMethodManager.SHOW_IMPLICIT, 0);
}

...

/**
 * Hide the keyboard - http://stackoverflow.com/a/8785471
 */
private void hideKeyboard() {
    InputMethodManager inputManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);

    inputManager.hideSoftInputFromWindow(inputBibleBookStart.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}

...

//https://github.com/rnevet/WCViewPager/blob/master/wcviewpager/src/main/java/nevet/me/wcviewpager/WrapContentViewPager.java
private int measureViewHeight(View view) {
    view.measure(ViewGroup.getChildMeasureSpec(-1, -1, view.getLayoutParams().width), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
    return view.getMeasuredHeight();
}

ขอบคุณมากที่ @Rananan สำหรับโค้ดในการวัดมุมมองและวัดความสูงของการตกแต่ง ฉันพบปัญหากับห้องสมุดของเขา - ภาพเคลื่อนไหวติดขัดและฉันคิดว่า ScrollView ของฉันจะไม่เลื่อนเมื่อความสูงของกล่องโต้ตอบสั้นพอที่จะใช้งานได้




0

ในกรณีของฉันฉันต้องการ viewpager ที่มี wrap_content สำหรับองค์ประกอบและภาพเคลื่อนไหวที่เลือกในปัจจุบันเมื่อใช้ขนาด ด้านล่างคุณสามารถเห็นการใช้งานของฉัน ใครบางคนสามารถเข้ามามีประโยชน์

package one.xcorp.widget

import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import one.xcorp.widget.R
import kotlin.properties.Delegates.observable

class ViewPager : android.support.v4.view.ViewPager {

    var enableAnimation by observable(false) { _, _, enable ->
        if (enable) {
            addOnPageChangeListener(onPageChangeListener)
        } else {
            removeOnPageChangeListener(onPageChangeListener)
        }
    }

    private var animationDuration = 0L
    private var animator: ValueAnimator? = null

    constructor (context: Context) : super(context) {
        init(context, null)
    }

    constructor (context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(context, attrs)
    }

    private fun init(context: Context, attrs: AttributeSet?) {
        context.theme.obtainStyledAttributes(
            attrs,
            R.styleable.ViewPager,
            0,
            0
        ).apply {
            try {
                enableAnimation = getBoolean(
                    R.styleable.ViewPager_enableAnimation,
                    enableAnimation
                )
                animationDuration = getInteger(
                    R.styleable.ViewPager_animationDuration,
                    resources.getInteger(android.R.integer.config_shortAnimTime)
                ).toLong()
            } finally {
                recycle()
            }
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)

        val measuredHeight = if (heightMode == MeasureSpec.EXACTLY) {
            MeasureSpec.getSize(heightMeasureSpec)
        } else {
            val currentViewHeight = findViewByPosition(currentItem)?.also {
                measureView(it)
            }?.measuredHeight ?: 0

            if (heightMode != MeasureSpec.AT_MOST) {
                currentViewHeight
            } else {
                Math.min(
                    currentViewHeight,
                    MeasureSpec.getSize(heightMeasureSpec)
                )
            }
        }

        super.onMeasure(
            widthMeasureSpec,
            MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)
        )
    }

    private fun measureView(view: View) = with(view) {
        val horizontalMode: Int
        val horizontalSize: Int
        when (layoutParams.width) {
            MATCH_PARENT -> {
                horizontalMode = MeasureSpec.EXACTLY
                horizontalSize = this@ViewPager.measuredWidth
            }
            WRAP_CONTENT -> {
                horizontalMode = MeasureSpec.UNSPECIFIED
                horizontalSize = 0
            }
            else -> {
                horizontalMode = MeasureSpec.EXACTLY
                horizontalSize = layoutParams.width
            }
        }

        val verticalMode: Int
        val verticalSize: Int
        when (layoutParams.height) {
            MATCH_PARENT -> {
                verticalMode = MeasureSpec.EXACTLY
                verticalSize = this@ViewPager.measuredHeight
            }
            WRAP_CONTENT -> {
                verticalMode = MeasureSpec.UNSPECIFIED
                verticalSize = 0
            }
            else -> {
                verticalMode = MeasureSpec.EXACTLY
                verticalSize = layoutParams.height
            }
        }

        val horizontalMeasureSpec = MeasureSpec.makeMeasureSpec(horizontalSize, horizontalMode)
        val verticalMeasureSpec = MeasureSpec.makeMeasureSpec(verticalSize, verticalMode)

        measure(horizontalMeasureSpec, verticalMeasureSpec)
    }

    private fun findViewByPosition(position: Int): View? {
        for (i in 0 until childCount) {
            val childView = getChildAt(i)
            val childLayoutParams = childView.layoutParams as LayoutParams

            val childPosition by lazy {
                val field = childLayoutParams.javaClass.getDeclaredField("position")
                field.isAccessible = true
                field.get(childLayoutParams) as Int
            }

            if (!childLayoutParams.isDecor && position == childPosition) {
                return childView
            }
        }

        return null
    }

    private fun animateContentHeight(childView: View, fromHeight: Int, toHeight: Int) {
        animator?.cancel()

        if (fromHeight == toHeight) {
            return
        }

        animator = ValueAnimator.ofInt(fromHeight, toHeight).apply {
            addUpdateListener {
                measureView(childView)
                if (childView.measuredHeight != toHeight) {
                    animateContentHeight(childView, height, childView.measuredHeight)
                } else {
                    layoutParams.height = animatedValue as Int
                    requestLayout()
                }
            }
            duration = animationDuration
            start()
        }
    }

    private val onPageChangeListener = object : OnPageChangeListener {

        override fun onPageScrollStateChanged(state: Int) {
            /* do nothing */
        }

        override fun onPageScrolled(
            position: Int,
            positionOffset: Float,
            positionOffsetPixels: Int
        ) {
            /* do nothing */
        }

        override fun onPageSelected(position: Int) {
            if (!isAttachedToWindow) {
                return
            }

            findViewByPosition(position)?.let { childView ->
                measureView(childView)
                animateContentHeight(childView, height, childView.measuredHeight)
            }
        }
    }
}

เพิ่ม attrs.xml ในโครงการ:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ViewPager">
        <attr name="enableAnimation" format="boolean" />
        <attr name="animationDuration" format="integer" />
    </declare-styleable>
</resources>

และใช้:

<one.xcorp.widget.ViewPager
    android:id="@+id/wt_content"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:enableAnimation="true" />

0

ViewPager นี้ปรับขนาดเฉพาะเด็กที่มองเห็นในปัจจุบันเท่านั้น (ไม่ใช่เด็กที่ใหญ่ที่สุด)

แนวคิดจากhttps://stackoverflow.com/a/56325869/4718406

public class DynamicHeightViewPager extends ViewPager {

public DynamicHeightViewPager (Context context) {
    super(context);
    initPageChangeListener();
}

public DynamicHeightViewPager (Context context, AttributeSet attrs) {
    super(context, attrs);
    initPageChangeListener();
}



private void initPageChangeListener() {
    addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
            requestLayout();
        }
    });
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //View child = getChildAt(getCurrentItem());
    View child = getCurrentView(this);
    if (child != null) {
        child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, 
         MeasureSpec.UNSPECIFIED));
        int h = child.getMeasuredHeight();

        heightMeasureSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}


View getCurrentView(ViewPager viewPager) {
    try {
        final int currentItem = viewPager.getCurrentItem();
        for (int i = 0; i < viewPager.getChildCount(); i++) {
            final View child = viewPager.getChildAt(i);
            final ViewPager.LayoutParams layoutParams = (ViewPager.LayoutParams) 
             child.getLayoutParams();

            Field f = layoutParams.getClass().getDeclaredField("position"); 
            //NoSuchFieldException
            f.setAccessible(true);
            int position = (Integer) f.get(layoutParams); //IllegalAccessException

            if (!layoutParams.isDecor && currentItem == position) {
                return child;
            }
        }
    } catch (NoSuchFieldException e) {
        e.fillInStackTrace();
    } catch (IllegalArgumentException e) {
        e.fillInStackTrace();
    } catch (IllegalAccessException e) {
        e.fillInStackTrace();
    }
    return null;
}

}


0

วัดความสูงของ ViewPager:

public class WrapViewPager extends ViewPager {
    View primaryView;

    public WrapViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (primaryView != null) {
            int height = 0;
            for (int i = 0; i < getChildCount(); i++) {
                if (primaryView == getChildAt(i)) {
                    int childHeightSpec = MeasureSpec.makeMeasureSpec(0x1 << 30 - 1, MeasureSpec.AT_MOST);
                    getChildAt(i).measure(widthMeasureSpec, childHeightSpec);
                    height = getChildAt(i).getMeasuredHeight();
                }

            }

            setMeasuredDimension(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
        }
    }

    public void setPrimaryView(View view) {
        primaryView = view;
    }

}

โทร setPrimaryView (ดู):

public class ZGAdapter extends PagerAdapter {

    @Override
    public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        super.setPrimaryItem(container, position, object);
        ((WrapViewPager)container).setPrimaryView((View)object);
    }

}

0

มอบเค้าโครงของพาเรนต์ของ ViewPager เป็น NestedScrollView

   <androidx.core.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="5dp"
    android:paddingRight="5dp"
    android:fillViewport="true">
        <androidx.viewpager.widget.ViewPager
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        </androidx.viewpager.widget.ViewPager>
    </androidx.core.widget.NestedScrollView>

อย่าลืมตั้ง android:fillViewport="true"

สิ่งนี้จะขยาย scrollview และเนื้อหาของเด็กเพื่อเติมวิวพอร์ต

https://developer.android.com/reference/android/widget/ScrollView.html#attr_android:fillViewport


0

คุณสามารถเปลี่ยนเป็น ViewPager2 เป็นรุ่นที่ได้รับการปรับปรุงของ ViewPager มันทำสิ่งเดียวกันกับ ViewPager แต่อย่างชาญฉลาดและมีประสิทธิภาพ ViewPager2 มาพร้อมกับคุณสมบัติใหม่ที่หลากหลาย แน่นอนปัญหาการตัดเนื้อหาได้รับการแก้ไขโดย ViewPager2

จาก Android docs: "ViewPager2 แทนที่ ViewPager กล่าวถึงความเจ็บปวดของผู้บุกเบิกส่วนใหญ่รวมถึงการสนับสนุนเค้าโครงจากขวาไปซ้ายการวางแนวตั้งแนวตั้งคอลเลกชัน Fragment ที่สามารถแก้ไขได้ ฯลฯ "

ฉันขอแนะนำบทความนี้สำหรับผู้เริ่มต้น:

https://medium.com/google-developer-experts/exploring-the-view-pager-2-86dbce06ff71


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