Android: Saya tidak dapat memiliki ViewPager WRAP_CONTENT

258

Saya telah menyiapkan ViewPager sederhana yang memiliki ImageView dengan ketinggian 200dp pada setiap halaman.

Ini pager saya:

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

Meskipun tingginya diatur sebagai wrap_content, pager selalu mengisi layar meskipun imageview hanya 200dp. Saya mencoba mengganti ketinggian pager dengan "200" tetapi itu memberi saya hasil yang berbeda dengan beberapa resolusi. Saya tidak dapat menambahkan "dp" ke nilai itu. Bagaimana cara menambahkan 200dp ke tata letak pager?

Adam
sumber
1
tolong beri tanda bintang pada code.google.com/p/android/issues/detail?id=54604
Christ

Jawaban:

408

Mengesampingkan Pengukuran Anda ViewPagersebagai berikut akan membuatnya mendapatkan ketinggian anak terbesar yang saat ini dimiliki.

@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);
}
Daniel López Lacalle
sumber
24
Ini datang paling dekat dengan apa yang saya butuhkan, tetapi ada dua hal untuk ditambahkan: 1. ViewPager hanya mengubah ukuran menjadi yang terbesar dari anak-anak yang sebenarnya, yaitu, hanya item yang terlihat saat ini dan yang berbatasan langsung. Memanggil setOffscreenPageLimit (jumlah total anak-anak) pada ViewPager menyelesaikan ini dan menghasilkan ViewPager yang ukurannya diatur ke terbesar dari semua itemnya dan tidak pernah mengubah ukuran. 2. WebViews memiliki beberapa masalah aneh ketika mencoba mengukurnya. Memanggil requestLayout () di WebView setelah memuat sesuatu menyelesaikannya.
0101100101
3
Hanya ada masalah kecil yang akan saya perbaiki: jika viewPager memiliki visibilitas ke GONE dan Anda mengaturnya agar terlihat, onMeasure dipanggil sebelum fragmennya dibuat. Jadi akhirnya akan memiliki ketinggian 0. Jika ada yang punya ide, dia dipersilakan. Saya pikir saya akan menggunakan callback ketika fragmen dibuat
edoardotognoni
4
Ini tidak akan berfungsi jika Anda memiliki tampilan dekorasi anak - ini karena ViewPager.onMeasure () mengukur tampilan dekorasi dan mengalokasikan ruang terlebih dahulu untuk mereka, lalu memberikan sisa ruang tersebut kepada anak-anak yang tidak dekorasi. Meskipun demikian, sejauh ini solusi yang paling tidak salah di sini jadi saya terbalik;)
Benjamin Dobell
3
Aku tetap kembali ke ini setiap kali saya menggunakan ViewPager
ono
7
getChildCount () dapat mengembalikan 0 saat Anda sudah melakukan setAdapter () di ViewPager! Panggilan populate () aktual (yang menciptakan tampilan) terjadi di dalam super.onMeasure (widthMeasureSpec, heightMeasureSpec); panggilan. Melakukan panggilan super.onMeasure () ekstra di awal fungsi ini berhasil. Juga periksa stackoverflow.com/questions/38492210/…
southerton
106

Solusi lain yang lebih umum adalah wrap_contentuntuk hanya bekerja.

Saya telah memperluas ViewPageruntuk menimpa onMeasure(). Ketinggiannya melingkari pandangan anak pertama. Ini dapat menyebabkan hasil yang tidak terduga jika tampilan anak tidak persis sama tingginya. Untuk itu kelas dapat diperpanjang dengan mudah, katakanlah beranimasi dengan ukuran tampilan / halaman saat ini. Tapi aku tidak butuh itu.

Anda dapat menggunakan ViewPager ini dalam tata letak XML Anda seperti halnya ViewPager asli:

<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"/>

Keuntungan: Pendekatan ini memungkinkan penggunaan ViewPager dalam tata letak apa pun termasuk RelativeLayout untuk melapisi elemen ui lainnya.

Satu kelemahan tetap: Jika Anda ingin menggunakan margin, Anda harus membuat dua tata letak bersarang dan memberikan margin dalam yang diinginkan.

Ini kodenya:

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

}
cybergen
sumber
34
adakah yang punya halaman kosong di sebelah item saat ini ketika viewpager dihancurkan dan dibuka lagi?
Zyoo
1
Saya punya halaman kosong juga.
aeren
10
Anda hanya perlu menggabungkan dua jawaban teratas dari pertanyaan ini sebagaimana dijelaskan di blog saya: pristalovpavel.wordpress.com/2014/12/26/…
anil
4
Cukup ganti kode metode 'onMeasure' dengan jawaban yang diberikan oleh 'Daniel López Lacalle'.
Yog Guru
1
Bagus..! Bekerja untuk saya .. @cybergen Terima kasih banyak, saya menyelamatkan hari saya ..!
Dnyanesh M
59

Saya mendasarkan jawaban saya pada Daniel López Lacalle dan posting ini http://www.henning.ms/2013/09/09/viewpager-that-simply-dont-measure-up/ . Masalah dengan jawaban Daniel adalah bahwa dalam beberapa kasus anak-anak saya memiliki tinggi nol. Solusinya adalah mengukur dua kali.

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

Ini juga memungkinkan Anda mengatur ketinggian pada ViewPager jika Anda mau atau hanya wrap_content.

Tuan Putra
sumber
Saya memiliki masalah yang sama dan menyelesaikannya dengan jawaban Anda, terima kasih. Tetapi ada penjelasan mengapa?
Bart Burg
Saya pikir mereka tidak berniat untuk membungkus konten untuk didukung karena saya tidak berpikir mereka pikir itu adalah kasus penggunaan normal. Untuk mendukungnya kita harus mengukur diri kita sendiri setelah anak-anak kita diukur sehingga kita dapat membungkus konten.
MinceMan
Mengapa gambar di ViewPager ini sebenarnya lebih pendek daripada di ImageView yang menggunakan yang sama scaleTypedan sama, layout_width=match_parentserta layout_height=wrap_content? ada seperti 20dp yang hilang di sana.
Hiu
Shark, saya benar-benar tidak yakin. Itu mungkin ada hubungannya dengan apa yang sebenarnya dilakukan oleh jenis skala Anda. Mungkin ingin mencoba mengatur ketinggian.
MinceMan
1
AKU TIDAK BISA FRIKIN PERCAYA! Saya menghabiskan 2 hari menyatukan viewpager kustom saya dan terjebak pada masalah, ketika tampilan awal saya tidak muncul dan saya tidak tahu mengapa! // super has to be called in the beginning so the child views can be initialized.<----- ITULAH alasannya, harus menyebutnya di awal dan di akhir fungsi onMeasure. Yippiii, balita tinggi virtual pada saya hari ini!
Starwave
37

Saya baru saja menjawab pertanyaan yang sangat mirip tentang ini, dan kebetulan menemukan ini ketika mencari tautan untuk mendukung klaim saya, sangat beruntung Anda :)

Jawaban saya yang lain:
ViewPager tidak mendukung wrap_contentkarena (biasanya) tidak pernah memuat semua anak-anaknya secara bersamaan, dan karena itu tidak dapat memperoleh ukuran yang sesuai (pilihannya adalah memiliki pager yang mengubah ukuran setiap kali Anda beralih halaman).

Namun Anda dapat mengatur dimensi yang tepat (misalnya 150dp) dan match_parentberfungsi juga.
Anda juga dapat memodifikasi dimensi secara dinamis dari kode Anda dengan mengubah height-atribusi di dalamnya LayoutParams.

Untuk kebutuhan Anda, Anda dapat membuat ViewPager dalam file xml-nya sendiri, dengan layout_height diatur ke 200dp, dan kemudian dalam kode Anda, daripada membuat ViewPager baru dari awal, Anda dapat mengembang file xml itu:

LayoutInflater inflater = context.getLayoutInflater();
inflater.inflate(R.layout.viewpagerxml, layout, true);
Jave
sumber
3
Jawaban yang bagus, agak menyebalkan bahwa perilaku default adalah "melakukan sesuatu yang agak tidak bisa dimengerti." Terima kasih untuk penjelasannya.
Chris Vandevelde
8
@ChrisVandevelde ini tampaknya menjadi penyewa umum dari beberapa perpustakaan android. Segera setelah Anda mempelajari dasar-dasarnya, Anda menyadari tidak ada yang mengikuti mereka
CQM
1
Tapi @Jave, mengapa viewpager tidak bisa menyesuaikan ketinggiannya setiap kali anak-anaknya dimuat?
Diffy
@CQM memang! Pustaka ViewPagerIndicator memiliki masalah yang sama dengan layout_heightset ke wrap_content, tetapi bahkan lebih buruk karena solusi sederhana untuk mengaturnya ke jumlah yang tetap tidak berfungsi.
Giulio Piancastelli
20

Menggunakan jawaban Daniel López Localle , saya membuat kelas ini di Kotlin. Semoga ini menghemat lebih banyak waktu

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)
}}
Felipe Castilhos
sumber
16

Saya telah menghadapi masalah ini di beberapa proyek dan tidak pernah memiliki solusi yang lengkap. Jadi saya membuat proyek github WrapContentViewPager sebagai pengganti di tempat untuk ViewPager.

https://github.com/rnevet/WCViewPager

Solusi ini terinspirasi oleh beberapa jawaban di sini, tetapi meningkatkan pada:

  • Mengubah ketinggian ViewPager secara dinamis sesuai dengan Tampilan saat ini termasuk saat menggulir.
  • Mempertimbangkan ketinggian tampilan "dekorasi" seperti PagerTabStrip.
  • Mempertimbangkan semua Padding.

Diperbarui untuk dukungan perpustakaan versi 24 yang melanggar implementasi sebelumnya.

Raanan
sumber
@mai dapatkah Anda membuka masalah, atau bercabang dan memodifikasi contoh aplikasi?
Raanan
1
Saya menemukan RecyclerView memiliki beberapa masalah wrap_content juga; ini berfungsi jika Anda menggunakan LinearLayoutManager kustom, seperti ini . Jadi tidak ada yang salah dengan perpustakaan Anda.
natario
1
Yang masih harus diperbaiki adalah penggunaannya dengan FragmentStatePagerAdapter. Sepertinya itu mengukur anak-anak sebelum fragmen diletakkan, sehingga memberikan ketinggian yang lebih kecil. Apa yang berhasil untuk saya adalah jawaban @logan, meskipun saya masih mengerjakannya. Anda mungkin ingin mencoba menggabungkan pendekatan itu ke perpustakaan Anda. Saya tidak terbiasa dengan github, maaf.
natario
Terima kasih, saya akan memeriksanya.
Raanan
1
Bagi siapa pun yang bertanya-tanya bagaimana cara membuat ini bekerja dengan FragmentPagerAdapter, buat Adaptor Anda mengimplementasikan ObjectAtPositionInterface dengan menyimpan daftar Fragmen secara internal, sehingga dapat mengembalikan Fragmen yang sesuai dari metode getObjectAtPosition.
Pablo
15

Saya baru saja bertemu dengan masalah yang sama. Saya punya ViewPager dan saya ingin menampilkan iklan di tombol itu. Solusi yang saya temukan adalah memasukkan pager ke dalam RelativeView dan mengatur layout_above ke id tampilan yang ingin saya lihat di bawahnya. itu berhasil untuk saya.

di sini adalah tata letak XML saya:

  <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>
Idan
sumber
4
hanya untuk referensi, Anda tidak perlu xmlns: android = " schemas.android.com/apk/res/android " di keduanya, hanya di yang pertama.
Martin Marconcini
2
Masalah Anda sama sekali tidak sama. Tata letak Anda berfungsi dengan baik dengan ViewPager diatur ke match_parent - OP memiliki situasi di mana ia ingin ViewPager membungkus dengan isinya.
k2col
9

Saya juga mengalami masalah ini, tetapi dalam kasus saya saya punya FragmentPagerAdapteryang memasok ViewPagerdengan halaman-halamannya. Masalah yang saya miliki adalah bahwa onMeasure()dari yang ViewPagerdipanggil sebelum salah satu Fragmentstelah dibuat (dan karena itu tidak dapat mengukur sendiri dengan benar).

Setelah sedikit trial and error, saya menemukan bahwa finishUpdate()metode FragmentPagerAdapter dipanggil setelah Fragmentsdiinisialisasi (dari instantiateItem()dalam FragmentPagerAdapter), dan juga setelah / selama pengguliran halaman. Saya membuat antarmuka kecil:

public interface AdapterFinishUpdateCallbacks
{
    void onFinishUpdate();
}

yang saya sampaikan ke FragmentPagerAdapterdan menelepon saya :

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

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

yang pada gilirannya memungkinkan saya untuk memanggil implementasi setVariableHeight()saya 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;
    }
}

Saya tidak yakin itu adalah pendekatan terbaik, akan suka komentar jika Anda pikir itu baik / buruk / jahat, tetapi tampaknya bekerja dengan sangat baik dalam implementasi saya :)

Semoga ini bisa membantu seseorang di luar sana!

Suntingan: Saya lupa menambahkan requestLayout()setelah menelepon super.measure()(jika tidak mengubah tampilan).

Saya juga lupa menambahkan padding orang tua ke ketinggian akhir.

Saya juga menjatuhkan menjaga lebar / tinggi asli MeasureSpecs demi menciptakan yang baru seperti yang diperlukan. Telah memperbarui kode yang sesuai.

Masalah lain yang saya miliki adalah bahwa ukurannya tidak benar dalam ScrollViewdan menemukan pelakunya mengukur anak dengan MeasureSpec.EXACTLYbukannya MeasureSpec.UNSPECIFIED. Diperbarui untuk mencerminkan ini.

Semua perubahan ini telah ditambahkan ke kode. Anda dapat memeriksa riwayat untuk melihat versi lama (salah) jika Anda mau.

logan
sumber
Mengapa Anda tidak menambahkan yang Anda lupa kode.
hasan
@hasan saya sudah melakukannya, maaf atas kebingungan! Akan memperbarui jawaban untuk mengatakan itu juga
logan
Luar biasa! Senang membantu :)
logan
8

Solusi lain adalah memperbarui ViewPagerketinggian sesuai dengan tinggi halaman saat ini di PagerAdapter. Dengan asumsi bahwa Anda membuat ViewPagerhalaman Anda dengan cara ini:

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

Di mana mPagesdaftar internal PageInfostruktur ditambahkan secara dinamis ke PagerAdapterdan CustomImageViewhanya biasa ImageViewdengan onMeasure()metode overriden yang mengatur ketinggiannya sesuai dengan lebar yang ditentukan dan menjaga rasio aspek gambar.

Anda dapat memaksa ViewPagerketinggian dengan setPrimaryItem()metode:

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

Perhatikan Math.max(height, 1). Itu memperbaiki bug yang mengganggu yang ViewPagertidak memperbarui halaman yang ditampilkan (menunjukkannya kosong), ketika halaman sebelumnya memiliki tinggi nol (yaitu null dapat ditarik di CustomImageView), masing-masing menggesek bolak-balik antara dua halaman.

Blackhex
sumber
menurut saya jalan yang benar untuk diikuti tetapi saya perlu iklan item.mImageView.measure(..)untuk mendapatkan dimensi yang benar dalam getMeasuredXXX()metode.
Gianluca P.
6

Saat menggunakan konten statis di dalam viewpager dan Anda tidak menginginkan animasi mewah, Anda dapat menggunakan pager tampilan berikut

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));
  }
}
Kirill Kulakov
sumber
Ini berfungsi dengan baik. Saya memperpanjangnya dengan memutar melalui anak-anak dan mengambil yang dengan tinggi maksimal.
Javier Mendonça
Bekerja dengan baik bahkan di bawah tampilan pendaur ulang
kanudo
Saya mendapatkan pengecualian ini - java.lang.NullPointerException: Mencoba untuk memanggil metode virtual 'void android.view.View.measure (int, int)' pada referensi objek nol
PJ2104
Tetapi mengambil elemen pertama mungkin salah.
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);
}
hasanul hakim
sumber
4

Dari kode sumber aplikasi Android Popcorn time saya menemukan solusi ini yang secara dinamis menyesuaikan ukuran viewpager dengan animasi yang bagus tergantung pada ukuran anak saat ini.

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);
    }
}
Vihaan Verma
sumber
4

Jika Anda membutuhkan ViewPager yang menyesuaikan ukurannya untuk setiap anak , bukan hanya yang terbesar, saya telah menulis sepotong kode yang melakukannya. Perhatikan bahwa tidak ada animasi pada perubahan itu (tidak perlu dalam kasus saya)

android: bendera minHeight juga didukung.

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();
            }
        });
    }
}
Phatee P
sumber
Jadi adaptor ini memiliki masalah besar ketika jumlah item dalam perubahan adaptor
jobbert
Bisakah Anda mengklarifikasi pernyataan Anda?
Phatee P
Kode ini dapat menyebabkan nullpointer karena tidak setiap anak dihitung pada awal. Coba tata letak tab dan gulir dari 1 ke 5 atau kode bijaksana dan Anda akan melihatnya.
jobbert
4

Jawaban Daniel López Lacalle yang ditingkatkan , ditulis ulang di 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)
    }
}
Wojciech Kulik
sumber
3

Saya bertemu dengan masalah yang sama, dan saya juga harus membuat ViewPager membungkus isinya ketika pengguna menggulir di antara halaman. Menggunakan jawaban cybergen di atas, saya mendefinisikan metode onMeasure sebagai berikut:

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

Dengan cara ini, metode onMeasure menetapkan ketinggian halaman saat ini yang ditampilkan oleh ViewPager.

avlacatus
sumber
Hanya konten yang paling tinggi muncul dengan jawaban Anda, konten lainnya menghilang ...
Blaze Tama
2

Tidak ada yang disarankan di atas bekerja untuk saya. Kasing penggunaan saya memiliki 4 ViewPager khusus di ScrollView. Atas mereka diukur berdasarkan aspek rasio dan sisanya baru saja layout_height=wrap_content. Saya sudah mencoba solusi cybergen , Daniel López Lacalle . Tak satu pun dari mereka bekerja sepenuhnya untuk saya.

Dugaan saya mengapa cybergen tidak berfungsi pada halaman> 1 adalah karena ia menghitung tinggi pager berdasarkan halaman 1, yang disembunyikan jika Anda menggulir lebih jauh.

Baik saran cybergen dan Daniel López Lacalle memiliki perilaku aneh dalam kasus saya: 2 dari 3 dimuat ok dan 1 tinggi acak adalah 0. Muncul yang onMeasuredipanggil sebelum anak-anak dihuni. Jadi saya datang dengan campuran 2 jawaban ini + perbaikan saya sendiri:

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

Ide adalah membiarkan ViewPagermenghitung dimensi anak-anak dan menyimpan tinggi halaman pertama yang dihitung dalam tata letak params ViewPager. Jangan lupa untuk mengatur ketinggian tata letak fragmen ke wrap_contentsebaliknya Anda bisa mendapatkan tinggi = 0. Saya sudah menggunakan yang ini:

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

Harap perhatikan bahwa solusi ini berfungsi baik jika semua halaman Anda memiliki ketinggian yang sama . Kalau tidak, Anda perlu menghitung ulang ViewPagertinggi berdasarkan anak aktif saat ini. Saya tidak membutuhkannya, tetapi jika Anda menyarankan solusinya, saya akan dengan senang hati memperbarui jawabannya.

mente
sumber
Bisakah Anda masih memperbarui jawaban setelah bertahun-tahun? Akan membantu saya satu ton
Denny
2

Untuk orang-orang yang memiliki masalah dan pengodean untuk Xamarin Android di C #, ini mungkin juga solusi cepat:

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

Ini terutama berguna jika pandangan anak Anda memiliki ketinggian yang sama. Kalau tidak, Anda akan diminta untuk menyimpan semacam nilai "minimumHeight" di atas semua anak yang Anda periksa, dan bahkan kemudian Anda mungkin tidak ingin memiliki ruang kosong terlihat di bawah tampilan anak kecil Anda.

Solusinya sendiri tidak cukup untuk saya, tetapi itu karena item anak saya listViews dan MeasuredHeight mereka tidak dihitung dengan benar, tampaknya.

compat
sumber
Ini berhasil untuk saya. Semua tampilan anak saya di viewpager memiliki ketinggian yang sama.
Dmitry
2

Saya memiliki versi WrapContentHeightViewPager yang berfungsi dengan benar sebelum API 23 yang akan mengubah ukuran basis ketinggian tampilan induk pada tampilan anak saat ini yang dipilih.

Setelah memutakhirkan ke API 23, itu berhenti bekerja. Ternyata solusi lama itu digunakan getChildAt(getCurrentItem())untuk mendapatkan tampilan anak saat ini untuk mengukur yang tidak berfungsi. Lihat solusinya di sini: https://stackoverflow.com/a/16512217/1265583

Di bawah ini berfungsi dengan 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);
}
Howard Lin
sumber
Terima kasih!! Telah mencoba jawaban selama berjam-jam dan ini adalah satu-satunya yang berfungsi penuh untuk saya. Perlu dikombinasikan dengan adaptor khusus di mana 'setPrimaryItem () `memanggil fungsi dalam pager yang memanggil requestLayout()begitu tingginya disesuaikan saat kita beralih dari satu tab ke tab lainnya. Apakah Anda ingat mengapa superperlu dipanggil dua kali? Saya perhatikan bahwa itu tidak akan berhasil sebaliknya.
M3RS
Bekerja dengan API 28.
Khalid Lakhani
2

Kode di bawah ini adalah satu-satunya yang berfungsi untuk saya

1. Gunakan kelas ini untuk mendeklarasikan 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. Masukkan pager tampilan pembungkus tinggi ke file xml Anda:

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

3. Nyatakan pager tampilan Anda:

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);
Hossam Hassan
sumber
Terima kasih. Ini berhasil. Tetapi mengapa tim android tidak dapat memiliki ini di basis kode mereka?
Mohanakrrishna
Ini adalah salah satu hal yang harus Anda sesuaikan sendiri tergantung pada kebutuhan Anda, juga google memperkenalkan viewPager2 di tahun 2019 ini Google I / O dan merupakan pengganti dari ViewPager lama, yang dibuat pada tahun 2011, implementasi 'androidx.viewpager2: viewpager2 : 1.0.0-alpha04 '
Hossam Hassan
2

Saya mengedit jawaban cybergen untuk membuat viewpager untuk mengubah ketinggian, tergantung pada item yang dipilih. Kelasnya sama dengan cybergen, tapi saya menambahkan Vector bilangan bulat yang semua ketinggian viewpager anak-anak viewpager dan kita dapat mengaksesnya ketika halaman berubah untuk memperbarui ketinggian.

Ini kelasnya:

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

Kemudian dalam aktivitas Anda tambahkan 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) {}
});

Dan di sini adalah xml:

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

Harap perbaiki bahasa Inggris saya jika perlu

geggiamarti
sumber
Ini memiliki beberapa masalah. The heightsDaftar dapat meningkatkan infinity.
rosuh
@rosuh Kapan Anda menemukan masalah ini? Saya menggunakan ini hanya di TabLayout dengan ViewPager, jadi saya tidak yakin apakah itu berfungsi dengan baik di mana saja
geggiamarti
@geggiamarti Masalahnya adalah beberapa halaman akan didaur ulang. Dan diciptakan kembali ketika pengguna menggesek ke mereka karena itu measureakan dipanggil berkali-kali. Ini dapat meningkatkan daftar ketinggian. Situasi lain adalah pengguna dapat memanggil requestLayout(atau setLayoutParamsmetode, seperti yang Anda lakukan) untuk viewPager ini secara manual, juga akan mengukur beberapa kali.
rosuh
1

Jika yang ViewPagerAnda gunakan adalah anak ScrollView AND dan memiliki PagerTitleStripanak Anda harus menggunakan sedikit modifikasi dari jawaban-jawaban hebat yang telah disediakan. Untuk referensi XML saya terlihat seperti ini:

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

Dalam Anda, onMeasureAnda harus MENAMBAH yang diukur dari PagerTitleStripjika ditemukan. Kalau tidak, tingginya tidak akan dianggap sebagai ketinggian terbesar dari semua anak meskipun membutuhkan ruang tambahan.

Semoga ini bisa membantu orang lain. Maaf itu sedikit peretasan ...

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);
    }
}
alexgophermix
sumber
1

Sebagian besar solusi yang saya lihat di sini tampaknya melakukan pengukuran ganda: pertama mengukur pandangan anak, dan kemudian memanggilnya super.onMeasure()

Saya datang dengan kebiasaan WrapContentViewPager yang lebih efisien, bekerja dengan baik dengan RecyclerView dan Fragment

Anda dapat memeriksa demo di sini:

github / ssynhtn / WrapContentViewPager

dan kode kelas di sini: WrapContentViewPager.java

ssynhtn
sumber
0

Saya memiliki skenario yang serupa (tetapi lebih kompleks). Saya memiliki dialog, yang berisi ViewPager.
Salah satu halaman anak pendek, dengan tinggi statis.
Halaman anak lain harus selalu setinggi mungkin.
Halaman anak lain berisi ScrollView, dan halaman (dan dengan demikian seluruh dialog) harus WRAP_CONTENT jika konten ScrollView tidak memerlukan ketinggian penuh yang tersedia untuk dialog.

Tak satu pun dari jawaban yang ada bekerja sepenuhnya untuk skenario khusus ini. Tunggu - ini perjalanan yang bergelombang.

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

Terima kasih banyak kepada @Raanan untuk kode untuk mengukur tampilan dan mengukur tinggi dekorasi. Saya mengalami masalah dengan perpustakaannya - animasi tergagap, dan saya pikir ScrollView saya tidak akan bergulir ketika ketinggian dialog cukup pendek untuk membutuhkannya.

Patrick
sumber
0

dalam kasus saya menambahkan clipToPaddingmemecahkan masalah.

<android.support.v4.view.ViewPager
    ...
    android:clipToPadding="false"
    ...
    />

Bersulang!

Mario
sumber
0

Saya kasus saya menambahkan android: fillViewport = "true" menyelesaikan masalah

hiten pannu
sumber
0

Dalam kasus saya, saya memerlukan viewpager dengan wrap_content untuk elemen dan animasi yang dipilih saat menerapkan ukuran. Di bawah ini Anda dapat melihat implementasi saya. Adakah yang bisa berguna.

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

Tambahkan attrs.xml dalam proyek:

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

Dan gunakan:

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

ViewPager ini hanya mengubah ukuran ke anak yang terlihat saat ini (bukan yang terbesar dari anak yang sebenarnya)

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

}

Erfan Eghterafi
sumber
0

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

}

panggilan setPrimaryView (Lihat) :

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

}
wslaimin
sumber
0

Berikan tata letak induk ViewPager sebagai 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>

Jangan lupa mengatur android:fillViewport="true"

Ini akan memperluas scrollview dan konten anaknya untuk mengisi viewport.

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

Krishna
sumber
0

Anda dapat beralih ke ViewPager2. Ini adalah versi terbaru dari ViewPager. Itu melakukan hal yang sama seperti ViewPager tetapi dengan cara yang lebih cerdas dan efisien. ViewPager2 hadir dengan beragam fitur baru. Tentu saja masalah Bungkus Konten telah diselesaikan oleh ViewPager2.

Dari Android docs: "ViewPager2 menggantikan ViewPager, menangani sebagian besar titik nyeri pendahulunya, termasuk dukungan tata letak kanan-ke-kiri, orientasi vertikal, koleksi Fragmen yang dapat dimodifikasi, dll."

Saya merekomendasikan artikel ini untuk pemula:

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

seyfullah.bilgin
sumber
Masalah ini masih ada di sana, periksa issuetracker.google.com/u/0/issues/143095219
Somesh Kumar