안드로이드 : ViewPager WRAP_CONTENT를 가질 수 없습니다


258

각 페이지에서 높이가 200dp 인 ImageView가있는 간단한 ViewPager를 설정했습니다.

내 호출기는 다음과 같습니다.

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

wrap_content로 설정된 높이에도 불구하고 페이저는 이미지 뷰가 200dp에 불과하더라도 항상 화면을 채 웁니다. 호출기의 높이를 "200"으로 바꾸려고했지만 여러 해상도로 다른 결과를 얻습니다. 해당 값에 "dp"를 추가 할 수 없습니다. 호출기의 레이아웃에 200dp를 어떻게 추가합니까?


답변:


408

ViewPager다음과 같이 측정을 재정의 하면 현재 가장 큰 자녀의 키를 얻습니다.

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

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

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

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

24
이것은 내가 필요한 것에 가장 가깝지만 추가해야 할 두 가지가 있습니다. 1. ViewPager는 실제 자식 중 가장 큰 것, 즉 현재 보이는 항목과 바로 인접한 항목으로 만 크기를 조정합니다. ViewPager에서 setOffscreenPageLimit (총 자식 수)를 호출하면이 문제가 해결되고 크기가 모든 항목 중 가장 크게 설정되고 크기가 조정되지 않는 ViewPager가 생성됩니다. 2. WebViews를 측정 할 때 이상한 문제가 있습니다. 무언가를로드 한 후 WebView에서 requestLayout ()을 호출하면 문제가 해결됩니다.
0101100101

3
내가 고칠 작은 문제가 있습니다 : viewPager가 GONE에 대한 가시성을 가지고 가시로 설정하면 조각이 생성되기 전에 onMeasure가 호출됩니다. 그래서 그것은 0의 높이를 갖게 될 것입니다. 누군가 아이디어가 있다면, 그는 환영합니다. 조각이 만들어 졌을 때 콜백을 할 것이라고 생각합니다
edoardotognoni

4
장식 하위 뷰가있는 경우 작동하지 않습니다 .ViewPager.onMeasure ()는 장식 뷰를 측정하고 공간을 먼저 할당 한 다음 나머지 공간을 비 장식 하위에 제공하기 때문입니다. 그럼에도 불구하고, 이것은 지금까지 가장 잘못된 해결책이므로 여기에 내가 찬성했습니다.)
Benjamin Dobell

3
나는이 다시에게 나는 ViewPager를 사용할 때마다 계속오고
오노

7
ViewPager에서 이미 setAdapter ()를 수행하는 동안 getChildCount ()가 0을 반환 할 수 있습니다! 뷰를 생성하는 실제 채우기 () 호출은 super.onMeasure (widthMeasureSpec, heightMeasureSpec) 내부에서 발생합니다. 요구. 이 함수의 시작 부분에 여분의 super.onMeasure () 호출을 넣으면 트릭이 발생했습니다. 또한 stackoverflow.com/questions/38492210/…를
southerton

106

더 일반적인 또 다른 솔루션은 wrap_content바로 작동하는 것입니다.

ViewPager재정의하도록 확장 했습니다 onMeasure(). 높이는 첫 번째 자식 뷰를 둘러싸고 있습니다. 자식 뷰의 높이가 정확히 같지 않으면 예기치 않은 결과가 발생할 수 있습니다. 이를 위해 클래스를 쉽게 확장하여 현재 뷰 / 페이지의 크기에 애니메이션을 적용 할 수 있습니다. 그러나 나는 그것을 필요로하지 않았다.

원본 ViewPager와 마찬가지로이 ViewPager를 XML 레이아웃에서 사용할 수 있습니다.

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

장점 :이 방법을 사용하면 RelativeLayout을 포함한 모든 레이아웃에서 ViewPager를 사용하여 다른 UI 요소를 오버레이 할 수 있습니다.

한 가지 단점이 남아 있습니다. 여백을 사용하려면 중첩 된 레이아웃 두 개를 만들고 내부 레이아웃에 원하는 여백을 지정해야합니다.

코드는 다음과 같습니다.

public class WrapContentHeightViewPager extends ViewPager {

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

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

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

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

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

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

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

}

34
viewpager가 파괴되어 다시 열 때 현재 항목 옆에 빈 페이지가 있습니까?
Zyoo

1
빈 페이지도 있습니다.
aeren

10
내 블로그에 설명 된 대로이
anil

4
'onMeasure'메소드의 코드를 'Daniel López Lacalle'의 답변으로 바꾸십시오.
Yog Guru 2016

1
큰..! 나를 위해 일했다 .. @cybergen 고마워 u는 내 하루를 구했다 ..!
Dnyanesh M

59

나는 Daniel López Lacalle과이 게시물 http://www.henning.ms/2013/09/09/viewpager-that-simply-dont-measure-up/ 에 대한 답변을 기반으로했습니다 . 다니엘의 대답의 문제는 어떤 경우에는 내 아이들의 키가 0이라는 것입니다. 해결책은 불행히도 두 번 측정하는 것이 었습니다.

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

또한 wrap_content를 원하거나 ViewPager에서 높이를 설정할 수도 있습니다.


나는 같은 문제가 있었고 당신의 대답으로 해결했습니다. 감사합니다. 그러나 왜 그런지에 대한 설명이 있습니까?
Bart Burg

랩핑 컨텐츠가 일반적인 사용 사례라고 생각하지 않기 때문에 랩 컨텐츠를 지원하지 않을 것이라고 생각합니다. 이를 지원하기 위해 우리는 자녀를 측정 한 후에 내용을 감쌀 수 있도록 자아를 다시 측정해야합니다.
MinceMan

왜이 ViewPager의 이미지는 동일한 사용하는 이미지 뷰에 비해 actualy 짧고 scaleType, 유사 및 layout_width=match_parent뿐만 아니라 layout_height=wrap_content? 거기에 20dp가 빠진 것 같습니다.
상어

상어, 잘 모르겠습니다. 스케일 유형이 실제로 수행하는 작업과 관련이있을 수 있습니다. 높이를 설정하려고 할 수 있습니다.
MinceMan

1
나는 그것을 믿을 수 없다! 나는 2 일 동안 내 맞춤 뷰 페이지를 붙이고 초기 뷰가 표시되지 않고 문제가 발생했을 때 문제가 발생했습니다. // super has to be called in the beginning so the child views can be initialized.<----- 그 이유는 onMeasure 함수의 시작과 끝에 호출해야했습니다. Yippiii, 오늘 저의 가상 파이브!
Starwave

37

나는 이것에 대해 매우 비슷한 질문에 대답하고 있었고 내 주장을 뒷받침하는 링크를 찾을 때 이것을 찾았습니다.

내 다른 대답 :
ViewPager는 wrap_content(보통) 모든 자식을 동시에로드하지 않으므로 적절한 크기를 얻을 수 없으므로 지원하지 않습니다 (옵션은 전환 할 때마다 크기를 변경하는 호출기를 갖는 것입니다) 페이지).

그러나 정확한 치수 (예 : 150dp)를 설정할 수 있으며 match_parent작동합니다.
에서 height-attribute를 변경하여 코드에서 치수를 동적으로 수정할 수도 있습니다 LayoutParams.

필요 에 따라 layout_height를 200dp로 설정 한 다음 코드에서 ViewPager를 새로 만든 다음 처음부터 새 ViewPager를 만들지 않고 해당 xml 파일을 부 풀릴 수 있습니다.

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

3
좋은 대답은, 기본 행동이 "약간 이해할 수없는 무언가를한다"는 것입니다. 설명 주셔서 감사합니다.
Chris Vandevelde

8
@ChrisVandevelde 이것은 일부 안드로이드 라이브러리의 일반적인 테넌트 인 것 같습니다. 기초를 배우 자마자 그 기초를 따르는 것이 아무것도 없다는 것을 깨닫게됩니다
CQM

1
그러나 @Jave, 왜 자식이로드 될 때마다 뷰 페이지가 높이를 조정할 수 없습니까?
Diffy

@CQM 참으로! ViewPagerIndicator 라이브러리는로 layout_height설정된 것과 같은 문제가 wrap_content있지만 고정 된 양으로 설정하는 간단한 해결 방법이 작동하지 않기 때문에 훨씬 더 나쁩니다.
Giulio Piancastelli

20

Daniel López Localle 답변을 사용하여 Kotlin에서이 클래스를 만들었습니다. 더 많은 시간을 절약하기를 바랍니다.

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

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

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

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

    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}}

16

나는 이미 여러 프로젝트 에서이 문제에 직면했으며 완전한 해결책을 찾지 못했습니다. 그래서 ViewPager를 대신하여 WrapContentViewPager github 프로젝트를 만들었습니다.

https://github.com/rnevet/WCViewPager

이 솔루션은 여기에 대한 답변 중 일부에서 영감을 얻었지만 다음과 같이 향상되었습니다.

  • 스크롤하는 동안 포함하여 현재 뷰에 따라 ViewPager 높이를 동적으로 변경합니다.
  • PagerTabStrip과 같은 "장식"뷰의 높이를 고려합니다.
  • 모든 패딩을 고려합니다.

이전 구현을 중단 한 지원 라이브러리 버전 24로 업데이트되었습니다.


@mvai 이슈를 열거 나 포크하여 샘플 앱을 수정할 수 있습니까?
Raanan

1
RecyclerView에 wrap_content 문제도 있음을 알았습니다. 이처럼 사용자 정의 LinearLayoutManager를 사용하면 작동합니다 . 도서관에는 아무 문제가 없습니다.
natario

1
여전히 수정해야 할 것은 FragmentStatePagerAdapter와 함께 사용하는 것입니다. 파편이 배치되기 전에 자녀를 측정하여 높이가 더 작은 것 같습니다. 나를 위해 일한 것은 @logan의 답변이지만 여전히 노력하고 있습니다. 해당 접근법을 라이브러리에 병합하려고 할 수 있습니다. github에 익숙하지 않습니다. 죄송합니다.
natario

고마워, 내가 살펴볼 게
Raanan

1
FragmentPagerAdapter를 사용하여이 작업을 수행하는 방법을 궁금해하는 사람은 내부적으로 Fragments 목록을 유지하여 어댑터가 ObjectAtPositionInterface를 구현하도록하여 getObjectAtPosition 메소드에서 해당 Fragment를 리턴 할 수 있도록하십시오.
Pablo

15

나는 방금 같은 문제에 부딪쳤다. ViewPager가 있는데 버튼에 광고를 표시하고 싶었습니다. 내가 찾은 해결책은 호출기를 RelativeView로 가져 와서 layout_above를 아래에서보고 싶은보기 ID로 설정하는 것이 었습니다. 그것은 나를 위해 일했다.

여기 내 레이아웃 XML이 있습니다.

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

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

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

4
참고로 xmlns : android = " schemas.android.com/apk/res/android " 가 필요하지 않으며 첫 번째 항목에만 필요합니다.
Martin Marconcini

2
당신의 문제는 전혀 같지 않았습니다. ViewPager가 match_parent로 설정되어 있으면 레이아웃이 제대로 작동합니다. OP에는 ViewPager가 내용으로 래핑되기를 원하는 상황이있었습니다.
k2col

9

나는 또한이 문제에 부딪 쳤지 만 내 경우에는 페이지를 FragmentPagerAdapter제공 하는 것이 있었다 ViewPager. 내가 가진 문제는 것이 었 onMeasure()ViewPager의 어떤 전에 호출 된 Fragments생성 (따라서 수없는 크기 자체가 제대로)되었다.

시행 착오를 조금 후에, 나는 것을 발견 finishUpdate()이 후 FragmentPagerAdapter의 메소드가 호출 Fragments(에서 초기화 된 instantiateItem()에서 FragmentPagerAdapter), 또한 후 / 페이지 스크롤시. 작은 인터페이스를 만들었습니다.

public interface AdapterFinishUpdateCallbacks
{
    void onFinishUpdate();
}

내가 전달 FragmentPagerAdapter하고 전화 :

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

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

이것은 다시 전화하라고 할 수 있습니다 setVariableHeight()내에서 CustomViewPager구현 :

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

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

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

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

        this.isSettingHeight = false;
    }
}

나는 그것이 최선의 접근법인지 확신하지 못하고, 그것이 좋거나 나쁘거나 악하다고 생각하면 의견을 좋아할 것이지만, 그것은 내 구현에서 꽤 잘 작동하는 것 같습니다 :)

이것이 누군가를 도울 수 있기를 바랍니다!

편집 :requestLayout() 호출 한 후 추가하는 것을 잊었습니다 super.measure()(그렇지 않으면 뷰를 다시 그리지 않습니다).

또한 부모의 패딩을 최종 높이에 추가하는 것을 잊었습니다.

또한 필요에 따라 새 너비 / 높이 MeasureSpec을 유지하여 새 너비 / 높이 측정 사양을 유지하는 것을 중단했습니다. 이에 따라 코드를 업데이트했습니다.

내가 가진 또 다른 문제는 그것이 제대로 크기를 맞추지 못하고 ScrollView범인이 MeasureSpec.EXACTLY대신 아이를 측정한다는 것을 알았습니다 MeasureSpec.UNSPECIFIED. 이를 반영하여 업데이트되었습니다.

이러한 변경 사항이 모두 코드에 추가되었습니다. 원하는 경우 기록을 확인하여 이전 (잘못된) 버전을 볼 수 있습니다.


코드에 잊어 버린 것을 추가하지 않는 이유는 무엇입니까?
hasan

@hasan 나는 이미 혼란스러워 죄송합니다! 다음과 같은 답변을 업데이트합니다.
logan

대박! 도움이 된 것을 기쁘게 생각합니다 :)
logan

8

또 다른 해결책은 ViewPager의 현재 페이지 높이에 따라 높이 를 업데이트 하는 것 PagerAdapter입니다. ViewPager이 방법으로 페이지를 작성한다고 가정하십시오 .

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

여기서 mPages내부 목록을 PageInfo동적으로 추가 구조 PagerAdapterCustomImageView단지 규칙적 ImageView재정의로 onMeasure()지정된 폭에 따라 그 높이를 설정하고, 화상 비율을 유지하는 방법.

방법으로 ViewPager높이를 강제 할 수 있습니다 setPrimaryItem().

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

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

를 참고하십시오 Math.max(height, 1). ViewPager이전 페이지의 높이가 0 일 때 (즉,에서 null drawable CustomImageView) 두 페이지 사이에서 앞뒤로 스 와이프 하면 표시된 페이지를 업데이트하지 않는 성가신 버그가 수정 됩니다 (공백으로 표시 ).


그것은 따라야 할 올바른 길로 보이지만 방법 item.mImageView.measure(..)에서 올바른 치수를 얻으려면 광고 를해야했습니다 getMeasuredXXX().
Gianluca P.

6

viewpager 내에서 정적 컨텐츠를 사용하고 멋진 애니메이션을 원하지 않는 경우 다음 view pager를 사용할 수 있습니다

public class HeightWrappingViewPager extends ViewPager {

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

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

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

이것은 잘 작동합니다. 나는 아이들을 반복하고 최대 높이를 가진 아이를 가져 와서 확장했습니다.
Javier Mendonça

재활용보기에서도 잘 작동
Kanudo

이 예외가 발생합니다. java.lang.NullPointerException : 널 오브젝트 참조에서 가상 메소드 'void android.view.View.measure (int, int)'를 호출
하려고했습니다.

그러나 첫 번째 요소를 취하는 것이 잘못된 것일 수 있습니다.
Tobias Reich

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

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

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

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

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

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

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

4

Popcorn time 안드로이드 앱의 소스 코드에서 현재 아이의 크기에 따라 멋진 애니메이션으로 viewpager의 크기를 동적으로 조정하는이 솔루션을 발견했습니다.

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

public class WrappingViewPager extends ViewPager {

    private Boolean mAnimStarted = false;

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

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

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

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

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

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

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

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

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

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

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

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

4

큰 페이지뿐만 아니라 모든 자식에 맞게 크기를 조정하는 ViewPager가 필요한 경우 코드를 작성했습니다. 해당 변경에 애니메이션이 없습니다 (필자의 경우 필요하지 않음)

android : minHeight 플래그도 지원됩니다.

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

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

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

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

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

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

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

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

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

따라서이 어댑터는 어댑터의 항목 수가 변경 될 때 큰 문제가 있습니다
jobbert

당신의 진술을 명확히 할 수 있습니까?
Phatee P

이 코드는 모든 자식이 시작시 계산되지 않으므로 널 포인터를 유발할 수 있습니다. 탭 레이아웃을 시도하고 1에서 5로 스크롤하거나 코드 방향으로 스크롤하면 알 수 있습니다.
jobbert

4

Kotlin으로 다시 작성된 Daniel López Lacalle 답변 개선 :

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

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

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

        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }
}

3

나는 같은 문제에 부딪 쳤으며 사용자가 페이지 사이를 스크롤 할 때 ViewPager가 내용을 감싸도록해야했습니다. 위의 Cybergen의 답변을 사용하여 onMeasure 방법을 다음과 같이 정의했습니다.

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

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

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

이런 식으로 onMeasure 메서드는 ViewPager에 의해 표시되는 현재 페이지의 높이를 설정합니다.


귀하의 답변에 가장 높은 높이의 콘텐츠 만 표시되고 다른 콘텐츠는 사라집니다 ...
Blaze Tama

2

위의 제안 사항 중 어느 것도 나를 위해 일하지 않았습니다. 내 유스 케이스에는에 4 개의 사용자 정의 ViewPagers가 ScrollView있습니다. 그들 중 상단은 종횡비를 기준으로 측정되며 나머지는 layout_height=wrap_content. 나는 Cybergen , Daniel López Lacalle 솔루션을 시도 했습니다 . 그들 중 누구도 나를 위해 완전히 작동하지 않습니다.

제 1 페이지에서 사이버 젠 이 작동하지 않는 이유 는 1 페이지를 기준으로 호출기의 높이를 계산하기 때문입니다. 더 스크롤하면 숨겨집니다.

cybergen다니엘 로페스 Lacalle 제안을 내 경우 이상한 동작을 3 : 2의 확인을로드하고 하나가 무작위로 높이가 0 나타납니다 onMeasure아이들이 채워지기 전에 불렀다. 그래서 나는이 두 가지 대답과 내 자신의 수정 사항을 혼합하여 생각해 냈습니다.

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

아이디어는 ViewPager어린이의 크기를 계산하고 첫 페이지의 계산 된 높이를의 레이아웃 매개 변수에 저장하는 것 ViewPager입니다. 조각의 레이아웃 높이를 설정 wrap_content해야 높이가 0이 될 수 있습니다. 나는 이것을 사용했다 :

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

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

모든 페이지의 높이가 같은 경우이 솔루션이 효과적 입니다. 그렇지 않으면 ViewPager현재 자식 활동을 기준으로 키 를 다시 계산해야합니다 . 나는 그것을 필요로하지 않지만 해결책을 제안하면 대답을 기꺼이 업데이트 할 것입니다.


몇 년이 지난 후에도 여전히 답변을 업데이트 할 수 있습니까? 나에게 도움이
Denny

2

이 문제가 있고 C #에서 Xamarin Android를 코딩하는 사람들에게는 빠른 해결책 일 수도 있습니다.

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

이것은 자녀의 조회 높이가 동일한 경우 주로 유용합니다. 그렇지 않으면 확인한 모든 자식에 대해 "minimumHeight"값을 저장해야하며, 심지어 작은 자식보기 아래에 빈 공간을 표시하지 않을 수도 있습니다.

솔루션 자체로는 충분하지 않지만 내 자식 항목이 listViews이고 MeasuredHeight가 올바르게 계산되지 않았기 때문입니다.


이것은 나를 위해 일했습니다. viewpager의 모든 자식 뷰의 높이는 동일합니다.
Dmitry

2

선택한 현재 자식 뷰의 부모 뷰 높이를 조정하는 API 23 이전에 올바르게 작동하는 WrapContentHeightViewPager 버전이 있습니다.

API 23으로 업그레이드 한 후 작동이 중지되었습니다. 이전 솔루션 getChildAt(getCurrentItem())에서 현재 하위 뷰를 측정하여 작동하지 않는 것으로 나타났습니다 . 여기에 솔루션을 참조하십시오 : https://stackoverflow.com/a/16512217/1265583

아래는 API 23에서 작동합니다.

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

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

감사합니다!! 몇 시간 동안 답변을 시도해 왔으며 이것이 나를 위해 완전히 작동하는 유일한 방법입니다. 'setPrimaryItem ()`이 호출기의 함수를 호출하는 호출자를 사용자 정의 어댑터와 결합해야합니다. 호출하는 함수는 requestLayout()한 탭에서 다음 탭으로 갈 때 높이가 조정됩니다. 왜 super두 번 전화 해야하는지 기억하십니까 ? 그렇지 않으면 작동하지 않는 것으로 나타났습니다.
M3RS

API 28과 함께 작동합니다.
Khalid Lakhani

2

아래 코드는 나를 위해 일한 유일한 것입니다.

1.이 클래스를 사용하여 HeightWrappingViewPager를 선언하십시오.

 public class HeightWrappingViewPager extends ViewPager {

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

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

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

2. 높이 랩핑보기 호출기를 xml 파일에 삽입하십시오.

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

3.보기 호출기를 선언하십시오.

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

감사. 이것은 효과가 있었다. 그러나 안드로이드 팀이 코드베이스에 이것을 가질 수없는 이유는 무엇입니까?
Mohanakrrishna

이것은 사용자가 사용자 정의 해야하는 것 중 하나입니다 .Google은 2019 년 Google I / O에 viewPager2를 도입했으며 2011 년에 만들어진 이전 ViewPager를 대체하여 'androidx.viewpager2 : viewpager2 : 1.0.0-alpha04 '
Hossam Hassan

2

선택한 항목에 따라 높이를 변경하기 위해 viewpager를 만들기 위해 cybergen 답변을 편집합니다. 클래스는 cybergen과 동일하지만 모든 viewpager의 자식 뷰 높이 인 정수 벡터를 추가했으며 페이지를 변경하여 높이를 업데이트 할 때 액세스 할 수 있습니다

이것은 클래스입니다 :

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

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

import java.util.Vector;

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

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

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

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

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

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

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

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

그런 다음 활동에서 OnPageChangeListener를 추가하십시오.

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

그리고 여기 XML이 있습니다 :

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

필요한 경우 영어를 수정하십시오


이것은 몇 가지 문제가 있습니다. heights목록은 무한대를 증가시킬 수있다.
rosuh

@rosuh 언제 문제가 발생 했습니까? 나는 이것을 ViewPager와 함께 TabLayout에서만 사용했기 때문에 그것이 어디에서나 잘 작동하는지 확실하지 않습니다
geggiamarti

@geggiamarti 문제는 일부 페이지가 재활용된다는 것입니다. 사용자가 스 와이프하면 다시 생성 measure되어 여러 번 호출됩니다. 높이 목록이 늘어날 수 있습니다. 또 다른 상황은 사용자 가이 viewPager에 대해 수동으로 전화 requestLayout(또는 수행 한 setLayoutParams것과 같은 방법)하여 여러 번 측정 할 수 있다는 것입니다.
rosuh

1

경우 ViewPager사용중인이의 자식 ScrollView PagerTitleStrip아이를 이미 제공 한 큰 응답의 약간의 수정을 사용해야합니다. 참고로 내 XML은 다음과 같습니다.

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

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

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

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

귀하 의 경우 if 의 측정 된 높이onMeasure추가 해야합니다 PagerTitleStrip. 그렇지 않으면 추가 공간을 차지하더라도 높이가 모든 어린이의 가장 큰 높이로 간주되지 않습니다.

이것이 다른 누군가를 돕기를 바랍니다. 조금 해킹해서 죄송합니다 ...

public class WrapContentHeightViewPager extends ViewPager {

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

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

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

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

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

1

여기에 보이는 대부분의 솔루션은 이중 측정을 수행하는 것으로 보입니다. 먼저 자식 뷰를 측정 한 다음 super.onMeasure()

WrapContentViewPager더 효율적이고 RecyclerView 및 Fragment와 잘 작동 하는 사용자 정의 를 생각해 냈습니다.

여기서 데모를 확인할 수 있습니다.

github / ssynhtn / WrapContentViewPager

클래스의 코드는 다음과 같습니다. WrapContentViewPager.java


0

비슷한 (그러나 더 복잡한 시나리오) 있습니다. ViewPager가 포함 된 대화 상자가 있습니다.
자식 페이지 중 하나가 짧고 정적 높이입니다.
다른 자식 페이지는 항상 가능한 한 커야합니다.
다른 자식 페이지에는 ScrollView가 포함되어 있으며 ScrollView 내용에 대화 상자에서 사용 가능한 전체 높이가 필요하지 않은 경우 페이지 (및 전체 대화 상자)는 WRAP_CONTENT 여야합니다.

이 특정 시나리오에 대해 기존 답변 중 어느 것도 완벽하게 작동하지 않았습니다. 잠깐만 요. 울퉁불퉁합니다.

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

뷰를 측정하고 장식 높이를 측정하는 코드에 대한 @Raanan 덕분에 대단히 감사합니다. 그의 라이브러리에 문제가 생겼습니다. 애니메이션이 끊겼습니다. 대화 상자의 높이가 짧아서 스크롤을 할 때 ScrollView가 스크롤되지 않을 것이라고 생각합니다.


0

내 경우에 추가하면 clipToPadding문제가 해결되었습니다.

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

건배!



0

필자의 경우 크기를 적용 할 때 현재 선택한 요소 및 애니메이션에 대해 wrap_content가있는 viewpager가 필요했습니다. 아래에서 내 구현을 볼 수 있습니다. 누군가가 편리하게 올 수 있습니까?

package one.xcorp.widget

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        measure(horizontalMeasureSpec, verticalMeasureSpec)
    }

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

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

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

        return null
    }

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

        if (fromHeight == toHeight) {
            return
        }

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

    private val onPageChangeListener = object : OnPageChangeListener {

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

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

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

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

프로젝트에 attrs.xml을 추가하십시오.

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

그리고 사용하십시오 :

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

0

이 ViewPager는 현재 보이는 자식으로 만 크기가 조정됩니다 (실제 자식 중 가장 큰 것은 아님)

https://stackoverflow.com/a/56325869/4718406 의 아이디어

public class DynamicHeightViewPager extends ViewPager {

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

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



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

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

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


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

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

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

}


0

ViewPager의 높이를 측정하십시오.

public class WrapViewPager extends ViewPager {
    View primaryView;

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

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

            }

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

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

}

호출 setPrimaryView (View) :

public class ZGAdapter extends PagerAdapter {

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

}

0

ViewPager의 부모 레이아웃을 NestedScrollView

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

설정하는 것을 잊지 마십시오 android:fillViewport="true"

이렇게하면 스크롤보기와 자식 내용이 늘어나 뷰포트를 채 웁니다.

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


0

ViewPager2로 전환 할 수 있습니다. ViewPager의 업데이트 된 버전입니다. ViewPager와 동일하지만 더 똑똑하고 효율적인 방식으로 수행됩니다. ViewPager2는 다양한 새로운 기능을 제공합니다. 물론 랩 콘텐츠 문제는 ViewPager2에 의해 해결되었습니다.

Android 문서에서 : "ViewPager2는 ViewPager를 대체하여 오른쪽에서 왼쪽으로의 레이아웃 지원, 수직 방향, 수정 가능한 조각 모음 등을 포함하여 이전 버전의 대부분의 어려움을 해결합니다."

초보자 에게이 기사를 추천합니다.

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


이 문제는 여전히 남아 있습니다 issuetracker.google.com/u/0/issues/143095219
Somesh Kumar
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.