Android: Nie mogę mieć ViewPager WRAP_CONTENT

258

Zainstalowałem prosty ViewPager, który ma ImageView o wysokości 200dp na każdej stronie.

Oto mój pager:

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

Pomimo wysokości ustawionej jako wrap_content, pager zawsze wypełnia ekran, nawet jeśli podgląd obrazu wynosi tylko 200dp. Próbowałem zamienić wysokość pagera na „200”, ale daje to różne wyniki przy wielu rozdzielczościach. Nie mogę dodać „dp” do tej wartości. Jak dodać 200dp do układu pagera?

Adam
źródło

Odpowiedzi:

408

Przesłonięcie opcji Twój pomiar w ViewPagernastępujący sposób sprawi, że uzyska wysokość największego dziecka, jakie aktualnie ma.

@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
źródło
24
Jest to najbardziej zbliżone do tego, czego potrzebuję, ale należy dodać dwie rzeczy: 1. ViewPager zmienia rozmiar tylko na największe ze swoich rzeczywistych elementów potomnych, to znaczy tylko aktualnie widoczny element i bezpośrednio przylegające. Wywołanie setOffscreenPageLimit (całkowita liczba dzieci) w ViewPager rozwiązuje ten problem i powoduje powstanie ViewPager, którego rozmiar jest ustawiony na największy ze wszystkich jego elementów i nigdy się nie zmienia. 2. WebViews mają dziwne problemy podczas próby ich zmierzenia. Wywołanie requestLayout () w WebView po załadowaniu czegoś rozwiązuje ten problem.
0101100101
3
Jest tylko mały problem, który zamierzam rozwiązać: jeśli viewPager ma widoczność GONE i ustawisz go jako widoczny, onMeasure zostanie wywołany przed utworzeniem jego fragmentu. Ostatecznie osiągnie wysokość 0. Jeśli ktoś ma pomysł, jest mile widziany. Myślę, że pójdę z oddzwanianiem, gdy fragment zostanie utworzony
edoardotognoni
4
To nie zadziała, jeśli masz widoki potomne dekorów - dzieje się tak, ponieważ ViewPager.onMeasure () mierzy widoki dekorów i najpierw przydziela im miejsce, a następnie przekazuje pozostałą część miejsca potomkom, które nie są dekorami. Niemniej jednak jest to zdecydowanie najmniej błędne rozwiązanie, więc głosowałem;)
Benjamin Dobell
3
Wracam do tego za każdym razem, gdy korzystam z ViewPagera
ono
7
getChildCount () może zwrócić 0, gdy już wykonałeś setAdapter () w ViewPager! Rzeczywiste wywołanie populate () (które tworzy widoki) dzieje się wewnątrz super.onMeasure (widthMeasureSpec, heightMeasureSpec); połączenie. Umieszczenie dodatkowego wywołania super.onMeasure () na początku tej funkcji załatwiło sprawę. Sprawdź także stackoverflow.com/questions/38492210/…
southerton
106

Kolejnym bardziej ogólnym rozwiązaniem jest wrap_contentpo prostu praca.

Rozszerzyłem, ViewPagerby zastąpić onMeasure(). Wysokość jest owinięta wokół pierwszego widoku dziecka. Może to prowadzić do nieoczekiwanych rezultatów, jeśli widoki potomne nie będą dokładnie tej samej wysokości. W tym celu można łatwo rozszerzyć klasę, aby na przykład ożywić rozmiar bieżącego widoku / strony. Ale nie potrzebowałem tego.

Możesz używać tego ViewPager w swoich układach XML, tak jak oryginalny 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"/>

Zaleta: To podejście pozwala na użycie ViewPager w dowolnym układzie, w tym RelativeLayout, do nakładania innych elementów interfejsu użytkownika.

Pozostaje jedna wada: jeśli chcesz użyć marginesów, musisz utworzyć dwa zagnieżdżone układy i nadać wewnętrznemu pożądane marginesy.

Oto kod:

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
źródło
34
ktoś inny dostał pustą stronę obok bieżącego elementu, gdy przeglądarka zniszczyła się i otworzyła ponownie?
Zyoo
1
Mam też puste strony.
aeren
10
Musisz tylko połączyć dwie najlepsze odpowiedzi na to pytanie, jak opisano na moim blogu: pristalovpavel.wordpress.com/2014/12/26/...
anil
4
Wystarczy zastąpić kod metody „onMeasure” odpowiedzią udzieloną przez „Daniela Lópeza Lacalle”.
Yog Guru,
1
Wspaniały..! Pracował dla mnie .. @cybergen Dzięki bardzo uratowałeś mi dzień ..!
Dnyanesh M
59

Swoją odpowiedź oparłem na Danielu Lópezie Lacalle i tym poście http://www.henning.ms/2013/09/09/viewpager-that-simply-dont-measure-up/ . Problem z odpowiedzią Daniela polega na tym, że w niektórych przypadkach moje dzieci miały wysokość zero. Rozwiązaniem było niestety zmierzenie dwa razy.

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

Pozwala to również ustawić wysokość w ViewPager, jeśli chcesz lub po prostu wrap_content.

MinceMan
źródło
Miałem ten sam problem i rozwiązałem go z twoją odpowiedzią, dzięki. Ale jakieś wyjaśnienie, dlaczego?
Bart Burg
Myślę, że nie zamierzali obsługiwać treści zawijania, ponieważ nie sądzę, aby uważali, że to normalny przypadek użycia. Aby to wesprzeć, musimy zmierzyć siebie po tym, jak nasze dzieci są zmierzone, abyśmy mogli zawinąć treść.
MinceMan,
Dlaczego obrazy w tym ViewPager są w rzeczywistości krótsze niż te w ImageView, który używa tego samego scaleTypei podobnie, layout_width=match_parenta także layout_height=wrap_content? brakuje tam 20dp.
Shark
Shark, naprawdę nie jestem pewien. To może mieć coś wspólnego z tym, co robi Twój typ skali. Może chcesz spróbować ustawić wysokość.
MinceMan
1
Nie mogę tego uwierzyć! Spędziłem 2 dni na sklejaniu mojej niestandardowej przeglądarki i utknąłem w problemie, gdy mój początkowy widok nie pojawił się i po prostu nie mogłem zrozumieć, dlaczego! // super has to be called in the beginning so the child views can be initialized.<----- To był powód, musiał wywołać go na początku i na końcu funkcji onMeasure. Yippiii, wirtualne piątki na mnie dzisiaj!
Starwave
37

Właśnie odpowiadałem na bardzo podobne pytanie na ten temat i znalazłem to, gdy szukałem linku, aby poprzeć moje roszczenia, więc mam szczęście :)

Moja inna odpowiedź:
ViewPager nie obsługuje, wrap_contentponieważ (zwykle) nigdy nie ma załadowanych wszystkich swoich dzieci w tym samym czasie, a zatem nie może uzyskać odpowiedniego rozmiaru (opcją byłoby mieć pager, który zmienia rozmiar przy każdym przełączeniu strona).

Możesz jednak ustawić dokładny wymiar (np. 150dp) i również match_parentdziała.
Możesz także dynamicznie modyfikować wymiary ze swojego kodu, zmieniając w nim heightatrybut -attribute LayoutParams.

W zależności od potrzeb możesz utworzyć ViewPager we własnym pliku xml, z ustawieniem layout_height na 200dp, a następnie w swoim kodzie, zamiast tworzyć nowy ViewPager od zera, możesz napompować ten plik xml:

LayoutInflater inflater = context.getLayoutInflater();
inflater.inflate(R.layout.viewpagerxml, layout, true);
Jave
źródło
3
Dobra odpowiedź, trochę irytujące, że domyślnym zachowaniem jest „rób coś niezrozumiałego”. Dziękuję za wyjaśnienie.
Chris Vandevelde
8
@ChrisVandevelde wydaje się, że jest to wspólny dzierżawca niektórych bibliotek Androida. Gdy tylko poznasz podstawy, zdasz sobie sprawę, że nic ich nie przestrzega
CQM
1
Ale @Jave, dlaczego nie mogę dostosowywać wysokości za każdym razem, gdy dzieci są ładowane?
Diffy
@CQM rzeczywiście! Biblioteka ViewPagerIndicator ma ten sam problem z layout_heightustawieniem na wrap_content, ale jest jeszcze gorsza, ponieważ proste obejście ustawiania na stałą kwotę nie działa.
Giulio Piancastelli,
20

Korzystając z odpowiedzi Daniela Lópeza Localle , stworzyłem tę klasę w Kotlin. Mam nadzieję, że zaoszczędzisz więcej czasu

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
źródło
16

Problem ten napotkałem już w kilku projektach i nigdy nie miałem kompletnego rozwiązania. Więc stworzyłem projekt github WrapContentViewPager jako zamiennik w miejscu dla ViewPager.

https://github.com/rnevet/WCViewPager

Rozwiązanie zostało zainspirowane niektórymi odpowiedziami tutaj, ale poprawia:

  • Dynamicznie zmienia wysokość ViewPager zgodnie z bieżącym widokiem, w tym podczas przewijania.
  • Bierze pod uwagę wysokość widoków „dekoracyjnych”, takich jak PagerTabStrip.
  • Uwzględnia wszystkie wypełnienia.

Zaktualizowano dla biblioteki pomocy technicznej w wersji 24, która przerwała poprzednie wdrożenie.

Raanan
źródło
@mvai możesz otworzyć problem, rozwidlić go i zmodyfikować przykładową aplikację?
Raanan,
1
Dowiedziałem się, że RecyclerView ma również pewne problemy z opakowaniem; działa, jeśli używasz niestandardowego LinearLayoutManager, takiego jak ten . Więc nic złego w twojej bibliotece.
natario
1
To, co wciąż musi zostać naprawione, to jego użycie z FragmentStatePagerAdapter. Wygląda na to, że mierzy dzieci przed ułożeniem fragmentów, co daje mniejszą wysokość. To, co zadziałało, to odpowiedź @logan, chociaż wciąż nad tym pracuję. Możesz spróbować połączyć to podejście ze swoją biblioteką. Przepraszam, nie znam githuba.
natario
Dzięki, przyjrzę się temu.
Raanan
1
Dla każdego, kto zastanawia się, jak to zrobić za pomocą FragmentPagerAdapter, spraw, aby Twój adapter zaimplementował ObjectAtPositionInterface, przechowując wewnętrznie listę Fragmentów, aby mógł zwrócić odpowiedni Fragment z metody getObjectAtPosition.
Pablo
15

Właśnie wpadłem na ten sam problem. Miałem ViewPager i chciałem wyświetlić reklamę za jego przyciskiem. Rozwiązaniem, które znalazłem, było przeniesienie pagera do RelativeView i ustawienie jego layout_above na identyfikator widoku, który chcę zobaczyć poniżej. to działało dla mnie.

oto mój układ 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>
Idan
źródło
4
tylko dla odniesienia, nie potrzebujesz xmlns: android = " schemas.android.com/apk/res/android " w obu, tylko w pierwszym.
Martin Marconcini,
2
Twój problem wcale nie był taki sam. Twój układ działa dobrze z ViewPager ustawionym na match_parent - OP miał sytuację, w której chciał, aby ViewPager zawinął się w jego zawartość.
k2col
9

Natknąłem się również na ten problem, ale w moim przypadku miałem taki, FragmentPagerAdapterktóry dostarczał ViewPagerswoje strony. Problem, który miałem, polegał na tym, onMeasure()że ViewPagerzostał wywołany przed utworzeniem któregokolwiek z Fragmentsnich (i dlatego nie mógł się odpowiednio dopasować).

Po kilku próbach i błędach odkryłem, że finishUpdate()metoda FragmentPagerAdapter jest wywoływana po Fragmentszainicjowaniu (od instantiateItem()w FragmentPagerAdapter), a także po / podczas przewijania strony. Zrobiłem mały interfejs:

public interface AdapterFinishUpdateCallbacks
{
    void onFinishUpdate();
}

które przechodzę do mojego FragmentPagerAdapteri wołam:

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

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

co z kolei pozwala mi wezwać setVariableHeight()moją CustomViewPagerimplementację:

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

Nie jestem pewien, czy to najlepsze podejście, chętnie komentuję, jeśli uważasz, że jest dobre / złe / złe, ale wydaje się, że działa całkiem dobrze w mojej realizacji :)

Mam nadzieję, że to pomoże komuś tam!

EDYCJA: Zapomniałem dodać requestLayout()po wywołaniu super.measure()(inaczej nie przerysuje widoku).

Zapomniałem również dodać podkładki rodzica do ostatecznej wysokości.

Porzuciłem również zachowanie oryginalnej szerokości / wysokości MeasureSpecs na korzyść stworzenia nowej w razie potrzeby. Zaktualizowałem odpowiednio kod.

Innym problemem, jaki miałem, było to, że nie zmieściłoby się ono prawidłowo w a ScrollViewi stwierdził, że sprawca mierzył dziecko MeasureSpec.EXACTLYzamiast MeasureSpec.UNSPECIFIED. Zaktualizowano, aby to odzwierciedlić.

Wszystkie zmiany zostały dodane do kodu. Możesz sprawdzić historię, aby zobaczyć stare (niepoprawne) wersje, jeśli chcesz.

logan
źródło
Dlaczego nie dodajesz zapomnianych do kodu?
hasan
@hasan już to zrobiłem, przepraszam za jakiekolwiek zamieszanie! Zaktualizuje odpowiedź, aby również to powiedzieć
logan
Niesamowite! Cieszę się, że pomogło :)
logan
8

Innym rozwiązaniem jest aktualizacja ViewPagerwysokości zgodnie z bieżącą wysokością strony PagerAdapter. Zakładając, że tworzysz swoje ViewPagerstrony w ten sposób:

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

Gdzie mPagesjest wewnętrzna lista PageInfostruktur dodawanych dynamicznie do PagerAdapteri CustomImageViewjest po prostu regularna ImageViewz onMeasure()metodą przesłonięcia , która ustawia wysokość zgodnie z określoną szerokością i zachowuje proporcje obrazu.

Możesz wymusić ViewPagerwysokość w setPrimaryItem()metodzie:

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

Uwaga Math.max(height, 1). To naprawia irytujący błąd, ViewPagerktóry nie aktualizuje wyświetlanej strony (pokazuje, że jest pusta), gdy poprzednia strona ma zerową wysokość (tzn. Można ją narysować w wartości zero CustomImageView), każde nieparzyste przesunięcie w obie strony między dwiema stronami.

Blackhex
źródło
wydaje mi się, że podążam właściwą ścieżką, ale musiałem reklamować, item.mImageView.measure(..)aby uzyskać właściwe wymiary getMeasuredXXX()metod.
Gianluca P.
6

Podczas korzystania z treści statycznych w przeglądarce i nie chcesz żadnych fantazyjnych animacji, możesz użyć następującego pagera

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
źródło
To działa dobrze. Rozszerzyłem go, zapętlając dzieci i biorąc ten o maksymalnej wysokości.
Javier Mendonça
Działa dobrze nawet pod widokiem recyklera
kanudo
Otrzymuję ten wyjątek - java.lang.NullPointerException: Próba wywołania metody wirtualnej „void android.view.View.measure (int, int)” w odwołaniu do obiektu o wartości null
PJ2104
Ale przyjęcie pierwszego elementu może być niewłaściwe.
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
źródło
4

Od czasu popcornowego kodu źródłowego aplikacji na Androida znalazłem to rozwiązanie, które dynamicznie dostosowuje rozmiar przeglądarki z ładną animacją w zależności od wielkości bieżącego dziecka.

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
źródło
4

Jeśli potrzebujesz programu ViewPager, który dostosuje jego rozmiar do każdego dziecka , nie tylko do największego, napisałem fragment kodu, który to robi. Pamiętaj, że po tej zmianie nie ma animacji (w moim przypadku nie jest to konieczne)

Android: flaga minHeight jest również obsługiwana.

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.
źródło
Tak więc ten adapter ma ogromny problem, gdy zmienia się ilość elementów w adapterze
jobbert
czy możesz wyjaśnić swoje oświadczenie?
Phatee P
Ten kod może powodować zerowanie, ponieważ nie każde dziecko jest obliczane na początku. Wypróbuj układ kart i przewiń od 1 do 5 lub kodem, a zobaczysz.
jobbert
4

Poprawiona odpowiedź Daniela Lópeza Lacalle'a , przepisana w Kotlinie :

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
źródło
3

Natknąłem się na ten sam problem, a także musiałem zmusić ViewPager do zawijania jego zawartości, gdy użytkownik przewijał strony. Korzystając z powyższej odpowiedzi cybergena, zdefiniowałem metodę onMeasure w następujący sposób:

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

W ten sposób metoda onMeasure ustala wysokość bieżącej strony wyświetlanej przez ViewPager.

avlacatus
źródło
Wraz z odpowiedzią pojawia się tylko najwyższa wysokość, druga treść jest znikana ...
Blaze Tama,
2

Nic z sugerowanych powyżej nie działało dla mnie. Mój przypadek użycia to 4 niestandardowe przeglądarki ViewPagers ScrollView. Najlepsze z nich są mierzone na podstawie współczynnika kształtu, a reszta ma layout_height=wrap_content. Próbowałem rozwiązań cybergen , Daniel López Lacalle . Żadne z nich nie działa dla mnie w pełni.

Domyślam się, że cybergen nie działa na stronie> 1, ponieważ oblicza wysokość pagera na podstawie strony 1, która jest ukryta, jeśli przewiniesz dalej.

Zarówno cybergen, jak i Daniel López Lacalle mają w moim przypadku dziwne zachowanie: 2 z 3 są załadowane ok, a 1 losowo ma wysokość 0. Wygląda na to, że wywołano jeonMeasure zanim dzieci zostały zaludnione. Więc wymyśliłem kombinację tych 2 odpowiedzi + moje własne poprawki:

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

Pomysł polega na ViewPagerobliczeniu wymiarów dzieci i zapisaniu obliczonej wysokości pierwszej strony w parametrach układu ViewPager. Nie zapomnij ustawić wysokości układu fragmentu, aby w wrap_contentprzeciwnym razie można uzyskać wysokość = 0. Użyłem tego:

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

Pamiętaj, że to rozwiązanie działa świetnie, jeśli wszystkie strony mają tę samą wysokość . W przeciwnym razie musisz ponownie obliczyć ViewPagerwysokość na podstawie bieżącego aktywnego dziecka. Nie potrzebuję tego, ale jeśli zaproponujesz rozwiązanie, chętnie zaktualizuję odpowiedź.

mente
źródło
Czy nadal możesz zaktualizować swoją odpowiedź po tylu latach? Pomógłby mi tona
Denny
2

Dla osób mających ten problem i kodujących dla Xamarin Android w C #, może to być również szybkie rozwiązanie:

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

Jest to szczególnie przydatne, jeśli widok dziecka jest tej samej wysokości. W przeciwnym razie będziesz musiał przechowywać jakąś wartość „minimumHeight” nad wszystkimi dziećmi, z którymi sprawdzasz, a nawet wtedy możesz nie chcieć, aby puste widoki były widoczne pod widokami mniejszych dzieci.

Samo rozwiązanie nie jest jednak dla mnie wystarczające, ale dzieje się tak, ponieważ moje elementy potomne są listViews, a ich zmierzona wysokość nie jest poprawnie obliczana.

kompatybil
źródło
To zadziałało dla mnie. Wszystkie moje widoki potomne w przeglądarce są tej samej wysokości.
Dmitry
2

Mam wersję WrapContentHeightViewPager, która działała poprawnie przed API 23, która zmieni rozmiar podstawy wysokości widoku rodzica na wybranym wybranym widoku potomnym.

Po aktualizacji do API 23 przestał działać. Okazuje się, że stare rozwiązanie wykorzystywało getChildAt(getCurrentItem())bieżący widok potomny do pomiaru, który nie działa. Zobacz rozwiązanie tutaj: https://stackoverflow.com/a/16512217/1265583

Poniżej działa z 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
źródło
Dziękuję Ci!! Próbuję odpowiedzi od wielu godzin i to jest jedyna, która w pełni działa dla mnie. Musi być połączony z niestandardowym adapterem, w którym 'setPrimaryItem () `wywołuje funkcję w pager, która wywołuje, requestLayout()więc wysokość jest dostosowywana podczas przechodzenia od jednej karty do drugiej. Czy pamiętasz, dlaczego supertrzeba zadzwonić dwa razy? Zauważyłem, że inaczej to nie zadziała.
M3RS,
Działa z API 28.
Khalid Lakhani
2

Poniższy kod jest jedyną rzeczą, która zadziałała dla mnie

1. Użyj tej klasy do zadeklarowania programu 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. Wstaw pager widoku zawijania wysokości do pliku xml:

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

3. Zadeklaruj pager widoku:

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
źródło
Dzięki. To zadziałało. Ale dlaczego zespół Androida nie może mieć tego w swojej bazie kodu?
Mohanakrrishna
Jest to jedna z rzeczy, które musisz dostosować w zależności od potrzeb, Google wprowadził także viewPager2 w tym roku 2019 we / wy Google i jest zamiennikiem starego ViewPager, który został stworzony w 2011 roku, implementacja „androidx.viewpager2: viewpager2 : 1.0.0-alpha04 '
Hossam Hassan
2

Edytuję odpowiedź cybergen dla make viewpager, aby zmienić wysokość w zależności od wybranego elementu Klasa jest taka sama jak cybergena, ale dodałem wektor liczb całkowitych, który jest wszystkimi wysokościami widoków potomnych viewpager i możemy uzyskać do niego dostęp, gdy zmiana strony spowoduje aktualizację wysokości

To jest klasa:

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

Następnie w swojej działalności dodaj 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) {}
});

A oto xml:

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

W razie potrzeby popraw mój angielski

geggiamarti
źródło
To ma pewne problemy. heightsLista może zwiększyć nieskończoność.
rosuh
@rosuh Kiedy napotkałeś problem? Użyłem tego tylko w TabLayout z ViewPager, więc nie jestem pewien, czy działa dobrze wszędzie
geggiamarti
@geggiamarti Problem polega na tym, że niektóre strony zostaną poddane recyklingowi. I odtworzone, gdy użytkownik przeciągnie do nich, dlatego measurebędzie wywoływane wielokrotnie. Może zwiększyć listę wysokości. Inną sytuacją jest to, że użytkownik może wywołać requestLayout(lub setLayoutParamsmetodę, dokładnie tak jak to zrobiłeś) dla tego viewPager ręcznie, również będzie mierzył wiele razy.
rosuh
1

Jeśli ViewPagerużywasz dziecka ScrollView ORAZ ma PagerTitleStripdziecko, musisz użyć niewielkiej modyfikacji wspaniałych już udzielonych odpowiedzi. Dla odniesienia mój XML wygląda następująco:

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

W swoim onMeasuremusisz DODAĆ zmierzone WysokośćPagerTitleStrip jeśli zostanie znaleziony. W przeciwnym razie jego wysokość nie będzie brana pod uwagę jako największa ze wszystkich dzieci, nawet jeśli zajmuje ona dodatkowe miejsce.

Mam nadzieję, że to pomaga komuś innemu. Przepraszam, że to trochę hack ...

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
źródło
1

Większość rozwiązań, które tu widzę, wydają się wykonywać podwójny pomiar: najpierw mierząc widoki dziecka, a następnie wywołując super.onMeasure()

Wymyśliłem niestandardowy, WrapContentViewPagerktóry jest bardziej wydajny, dobrze współpracuje z RecyclerView i Fragment

Możesz sprawdzić wersję demo tutaj:

github / ssynhtn / WrapContentViewPager

i kod klasy tutaj: WrapContentViewPager.java

ssynhtn
źródło
0

Mam podobny (ale bardziej złożony scenariusz). Mam okno dialogowe, które zawiera ViewPager.
Jedna ze stron podrzędnych jest krótka i ma wysokość statyczną.
Kolejna strona podrzędna powinna zawsze być tak wysoka, jak to możliwe.
Kolejna strona potomna zawiera ScrollView, a strona (a zatem całe okno dialogowe) powinna WRAP_CONTENT, jeśli zawartość ScrollView nie potrzebuje pełnej wysokości dostępnej dla okna dialogowego.

Żadna z istniejących odpowiedzi nie zadziałała całkowicie dla tego konkretnego scenariusza. Trzymaj się - to wyboista jazda.

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

Ogromne podziękowania dla @Raanan za kod do pomiaru widoków i pomiaru wysokości dekoru. Wystąpiły problemy z jego biblioteką - animacja się zacięła i myślę, że mój ScrollView nie przewinąłby się, gdy wysokość okna dialogowego była wystarczająco krótka, aby tego wymagać.

Patrick
źródło
0

w moim przypadku dodanie clipToPaddingrozwiązało problem.

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

Twoje zdrowie!

Mario
źródło
0

W moim przypadku dodanie Androida: fillViewport = "true" rozwiązało problem

hiten pannu
źródło
0

W moim przypadku potrzebowałem przeglądarki z zawartością wrap_content dla aktualnie wybranego elementu i animacji podczas stosowania rozmiaru. Poniżej możesz zobaczyć moją implementację. Czy ktoś może się przydać.

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

Dodaj attrs.xml w projekcie:

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

I użyć:

<one.xcorp.widget.ViewPager
    android:id="@+id/wt_content"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:enableAnimation="true" />
MAKS
źródło
0

ViewPager zmienia rozmiar tylko do aktualnie widocznych elementów potomnych (nie jest to największy z rzeczywistych elementów potomnych)

Pomysł z 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
źródło
0

Zmierz wysokość 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;
    }

}

wywołanie setPrimaryView (widok) :

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
źródło
0

Podaj układ nadrzędny ViewPager jako 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>

Nie zapomnij ustawić android:fillViewport="true"

Spowoduje to rozciągnięcie widoku przewijania i zawartości jego dziecka do wypełnienia okienka ekranu.

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

Kryszna
źródło
0

Możesz przejść do ViewPager2. Jest to zaktualizowana wersja ViewPager. Robi to samo co ViewPager, ale w bardziej inteligentny i wydajny sposób. ViewPager2 zawiera wiele nowych funkcji. Oczywiście problem zawijania treści został rozwiązany przez ViewPager2.

Z dokumentów Androida: „ViewPager2 zastępuje ViewPager, rozwiązując większość problemów związanych z jego poprzednikiem, w tym obsługę układu od prawej do lewej, orientację pionową, modyfikowalne kolekcje fragmentów itp.”

Polecam ten artykuł dla początkujących:

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

seyfullah.bilgin
źródło
Ten problem jest nadal dostępny. Sprawdź emitetracker.google.com/u/0/issues/143095219
Somesh Kumar