首页 文章

Android:我无法拥有ViewPager WRAP_CONTENT

提问于
浏览
208

我已经设置了一个简单的ViewPager,每页都有一个高度为200dp的ImageView .

这是我的寻呼机:

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,但即使imageview仅为200dp,寻呼机也会填充屏幕 . 我试图用“200”替换寻呼机的高度,但这给了我多种分辨率的不同结果 . 我无法将“dp”添加到该值 . 如何在寻呼机的布局中添加200dp?

22 回答

  • 2

    我已经在几个项目中遇到过这个问题,从来没有一个完整的解决方案 . 所以我创建了一个WrapContentViewPager github项目作为ViewPager的就地替代品 .

    https://github.com/rnevet/WCViewPager

    解决方案的灵感来自于此处的一些答案,但改进了:

    • 根据当前视图动态更改ViewPager高度,包括滚动时 .

    • 考虑像PagerTabStrip这样的"decor"视图的高度 .

    • 考虑所有Padding .

    Updated for support library version 24 which broke previous implementation.

  • 1

    我也遇到了这个问题,但在我的情况下,我有一个 FragmentPagerAdapter ,它正在为 ViewPager 提供其页面 . 我遇到的问题是 ViewPageronMeasure() 在创建任何 Fragments 之前被调用(因此无法正确调整大小) .

    经过一些试验和错误后,我发现在 Fragments 初始化之后(从 FragmentPagerAdapter 中的 instantiateItem() )以及页面滚动之后/期间调用了FragmentPagerAdapter的 finishUpdate() 方法 . 我做了一个小界面:

    public interface AdapterFinishUpdateCallbacks
    {
        void onFinishUpdate();
    }
    

    我传入我的 FragmentPagerAdapter 并致电:

    @Override
    public void finishUpdate(ViewGroup container)
    {
        super.finishUpdate(container);
    
        if (this.listener != null)
        {
            this.listener.onFinishUpdate();
        }
    }
    

    这反过来允许我在我的 CustomViewPager 实现上调用 setVariableHeight()

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

    我不确定这是最好的方法,如果你认为好/坏/邪恶会喜欢评论,但它似乎在我的实现中工作得很好:)

    希望这有助于那里的人!

    EDIT: 我忘了在调用 super.measure() 之后添加 requestLayout() (否则它不会重绘视图) .

    我也忘了将父级的填充添加到最终高度 .

    我还保留原始宽度/高度MeasureSpecs,有利于根据需要创建一个新的 . 已相应更新了代码 .

    我遇到的另一个问题是它在 ScrollView 中无法正确调整大小,并发现罪魁祸首是用 MeasureSpec.EXACTLY 而不是 MeasureSpec.UNSPECIFIED 测量孩子 . 更新以反映这一点 .

    这些更改都已添加到代码中 . 如果需要,您可以查看历史记录以查看旧版本(不正确) .

  • 37

    我遇到了同样的问题,当用户在页面之间滚动时,我还必须使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显示的当前页面的高度 .

  • 3

    我的情况添加android:fillViewport =“true”解决了这个问题

  • 7

    对于有这个问题并在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计算不正确 .

  • 3

    在我的情况下添加 clipToPadding 解决了问题 .

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

    干杯!

  • 4

    另一种解决方案是根据 PagerAdapter 中的当前页面高度更新 ViewPager 高度 . 假设您以这种方式创建 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 是动态添加到 PagerAdapterPageInfo 结构的内部列表,而 CustomImageView 只是常规的 ImageView ,其覆盖 onMeasure() 方法根据指定的宽度设置其高度并保持图像宽高比 .

    您可以在 setPrimaryItem() 方法中强制 ViewPager 高度:

    @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 没有更新显示页面(显示为空白)的烦人的错误,当前一页面具有零高度(即 CustomImageView 中为null drawable)时,每个奇数在两页之间来回滑动 .

  • 8
    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);
    }
    
  • 15

    我有一个版本的WrapContentHeightViewPager,它在API 23之前正常工作,它将在所选的当前子视图上调整父视图的高度基础 .

    升级到API 23后,它停止工作 . 事实证明,旧的解决方案是使用 getChildAt(getCurrentItem()) 来获取当前子视图以测量哪些不起作用 . 请在此处查看解决方案:https://stackoverflow.com/a/16512217/1265583

    以下适用于API 23:

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

    我刚刚碰到了同样的问题 . 我有一个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>
    
  • 16

    使用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)
    }}
    
  • 3

    在viewpager中使用静态内容并且您不希望使用任何花哨的动画时,您可以使用以下视图寻呼机

    public class HeightWrappingViewPager extends ViewPager {
    
      public HeightWrappingViewPager(Context context) {
        super(context);
      }
    
      public HeightWrappingViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
      }
    
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)   {
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
          View firstChild = getChildAt(0);
          firstChild.measure(widthMeasureSpec, heightMeasureSpec);
          super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(firstChild.getMeasuredHeight(), MeasureSpec.EXACTLY));
      }
    }
    
  • 0

    从爆米花时间android应用程序的源代码我发现这个解决方案动态调整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);
        }
    }
    
  • 2

    另一个更通用的解决方案是让 wrap_content 正常工作 .

    我已经扩展 ViewPager 以覆盖 onMeasure() . 高度围绕第一个子视图 . 如果子视图的高度不完全相同,这可能会导致意外结果 . 为此,可以轻松扩展该类,让's say animate to the size of the current view/page. But I didn' t需要它 .

    您可以像原始ViewPager一样在您的XML布局中使用此ViewPager:

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

    优点:此方法允许在包括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;
        }
    
    }
    
  • 1

    我的答案基于DanielLópezLacalle和这篇文章http://www.henning.ms/2013/09/09/viewpager-that-simply-dont-measure-up/ . Daniel的答案的问题在于,在某些情况下,我的孩子的身高为零 . 不幸的是,解决方案是两次测量 .

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

    如果您想要或只是wrap_content,这也允许您在ViewPager上设置高度 .

  • -1

    上面没有任何建议对我有用 . 我的用例是在 ScrollView 中有4个自定义ViewPagers . 其中顶部是根据纵横比测量的,其余部分只有 layout_height=wrap_content . 我试过cybergenDaniel López Lacalle解决方案 . 他们都不能完全为我工作 .

    我的猜测为什么 cybergen 在页面> 1上不起作用是因为它根据第1页计算寻呼机的高度,如果你进一步滚动则隐藏它 .

    在我的情况下, cybergenDaniel López Lacalle 建议都有奇怪的行为:3个中的2个加载正常,1个随机高度为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 的布局参数中保存第一页的计算高度 . 将't forget to set fragment'的布局高度设置为 wrap_content 否则可以获得height = 0 . 我用过这个:

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

    Please note that this solution works great if all of your pages have same height . 否则,您需要根据当前活动的子项重新计算 ViewPager 高度 . 我不需要它,但如果你建议解决方案,我很乐意更新答案 .

  • 93

    我刚刚回答了一个非常相似的问题,并且在寻找支持我的索赔的链接时碰巧找到了这个,所以很幸运你:)

    我的另一个答案:
    ViewPager不支持 wrap_content ,因为它(通常)不会同时加载所有子项,因此无法获得适当的大小(选项是每次切换页面时都有一个更改大小的寻呼机) .

    但是,您可以设置精确尺寸(例如150dp)和 match_parent 也可以 .
    您还可以通过更改 LayoutParams 中的 height -attribute来动态修改维度 .

    根据您的需要,您可以在自己的xml文件中创建ViewPager,并将layout_height设置为200dp,然后在代码中,而不是从头开始创建新的ViewPager,您可以为该xml文件充气:

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

    我有一个类似的(但更复杂的情况) . 我有一个包含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();
    }
    

    非常感谢@Rananan测量视图和测量装饰高度的代码 . 我遇到了他的图书馆的问题 - 动画结结巴巴,我认为当对话框的高度足够短以至于需要时,我的ScrollView不会滚动 .

  • 8

    如果你正在使用的 ViewPagerScrollView 的孩子并且有一个 PagerTitleStrip 孩子,你需要稍微修改已经提供的好答案 . 作为参考,我的XML看起来像这样:

    <ScrollView
        android:id="@+id/match_scroll_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="@color/white">
    
        <LinearLayout
            android:id="@+id/match_and_graphs_wrapper"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
            <view
                android:id="@+id/pager"
                class="com.printandpixel.lolhistory.util.WrapContentHeightViewPager"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <android.support.v4.view.PagerTitleStrip
                    android:id="@+id/pager_title_strip"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="top"
                    android:background="#33b5e5"
                    android:paddingBottom="4dp"
                    android:paddingTop="4dp"
                    android:textColor="#fff" />
            </view>
        </LinearLayout>
    </ScrollView>
    

    在你的 onMeasure 中你必须添加 PagerTitleStrip 的measuredHeight(如果找到一个) . 否则,它的高度将不会被视为所有孩子的最大高度,即使它占用额外的空间 .

    希望这有助于其他人 . 对不起,这有点像黑客......

    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

    如果您需要 adjust its size to every child 的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();
                }
            });
        }
    }
    
  • 46

    我找到了一个解决方案,有点像合并这里提到的一些解决方案 .

    我们的想法是测量ViewPager的当前视图 .

    扩展ViewPager:

    class CalendarViewPager : ViewPager {
        constructor(context: Context) : super(context) {}
    
        constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}
    
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
            val adapter = adapter as MyPagerAdapter?
            val currentView = adapter?.currentView
            if (currentView != null) {
                currentView.measure(widthMeasureSpec, heightMeasureSpec)
                super.onMeasure(widthMeasureSpec, View.MeasureSpec.makeMeasureSpec(currentView.measuredHeight, View.MeasureSpec.EXACTLY))
                return
            }
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        }
    
    }
    

    在ViewPager适配器中:

    var currentView: View? = null
    
    override fun setPrimaryItem(container: ViewGroup, position: Int, obj: Any) {
        super.setPrimaryItem(container, position, obj)
        currentView = // get the view out of the obj , depending on your code
    }
    

    如果您使用RecyclerPagerAdapter library,获取"currentView"的方法是从您设置的pager-view-holder获取它:

    val item = obj as PagerViewHolder
        currentView = item.itemView
    
  • -1

    按照以下方式覆盖 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);
    }
    

相关问题