【问题标题】:Dynamic height viewpager动态高度查看器
【发布时间】:2015-11-26 15:11:07
【问题描述】:

我正在尝试在自定义滚动视图中创建一个自定义视图分页器,动态包裹当前孩子的高度。

package com.example.vihaan.dynamicviewpager;

import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.ScrollView;

    /**
     * Created by vihaan on 1/9/15.
     */
    public class CustomScrollView extends ScrollView {

        private GestureDetector mGestureDetector;

        public CustomScrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
            mGestureDetector = new GestureDetector(context, new YScrollDetector());
            setFadingEdgeLength(0);
        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return super.onInterceptTouchEvent(ev)
                    && mGestureDetector.onTouchEvent(ev);
        }

        // Return false if we're scrolling in the x direction
        class YScrollDetector extends GestureDetector.SimpleOnGestureListener {
            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                    float distanceX, float distanceY) {
                return (Math.abs(distanceY) > Math.abs(distanceX));
            }
        }
    }

自定义页面

/**
 * Created by vihaan on 1/9/15.
 */
public class CustomPager extends ViewPager {

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

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

    @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);
        int width = getMeasuredWidth();
        int tabHeight = tab.getMeasuredHeight();

        if (wrapHeight) {
            // Keep the current measured width.
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
        }

        int fragmentHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, getCurrentItem())).getView());
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(tabHeight + fragmentHeight + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics()), MeasureSpec.AT_MOST);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    public int measureFragment(View view) {
        if (view == null)
            return 0;

        view.measure(0, 0);
        return view.getMeasuredHeight();
    }
}

MyPagerAdapter

public class MyPagerAdapter extends FragmentPagerAdapter {

    private List<Fragment> fragments;

    public MyPagerAdapter(FragmentManager fm) {
        super(fm);
        this.fragments = new ArrayList<Fragment>();
        fragments.add(new FirstFragment());
        fragments.add(new SecondFragment());
        fragments.add(new ThirdFragment());
        fragments.add(new FourthFragment());
    }

    @Override
    public Fragment getItem(int position) {
        return fragments.get(position);
    }

    @Override
    public int getCount() {
        return fragments.size();
    }
}

我希望这会环绕当前片段的高度,但它只考虑第一个孩子的高度。

示例 github 项目:https://github.com/VihaanVerma89/DynamicViewPager

【问题讨论】:

    标签: android android-viewpager android-custom-view android-scrollview


    【解决方案1】:

    对您的代码进行了一些调整,现在可以正常工作了。

    1] onMeasure 功能不正确。使用以下逻辑

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mCurrentView == null) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }
        int height = 0;
        mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        int h = mCurrentView.getMeasuredHeight();
        if (h > height) height = h;
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
    
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    

    2] ViewPager 需要在每次更改页面时重新测量。这样做的好地方是setPrimaryItemPagerAdapter的函数

    @Override
        public void setPrimaryItem(ViewGroup container, int position, Object object) {
            super.setPrimaryItem(container, position, object);
            if (position != mCurrentPosition) {
                Fragment fragment = (Fragment) object;
                CustomPager pager = (CustomPager) container;
                if (fragment != null && fragment.getView() != null) {
                    mCurrentPosition = position;
                    pager.measureCurrentView(fragment.getView());
                }
            }
        }
    

    这里是带有这些调整的 GitHub 项目的链接: https://github.com/vabhishek/WrapContentViewPagerDemo

    【讨论】:

    • 嗨!我使用您的解决方案将我的 ViewPager 嵌套在 ScrollView 中。 ViewPager 的第一个片段的高度始终为 0,即使我在那里有内容。其他片段正常工作。当我滑动到第一个片段时,我可以看到其中的内容,但是一旦我完全位于该选项卡上,片段就会崩溃。我似乎无法弄清楚出了什么问题。
    • @NarayanAcharya 你能发布你的代码吗?最好作为一个新问题并将问题的链接放在这里。
    • 我在这个问题stackoverflow.com/questions/38349809/… 中添加了我的代码。请看一看。谢谢!
    • @NarayanAcharya 嘿,我知道有点晚了,但是使用 mCurrentPosition 就像 ... private int mCurrentPosition = -1; ...将其设置为 -1 很重要
    • 嗨@Bartando 感谢您的回复。我已将 mCurrentPosition 初始化为 -1。你可以在这里查看一个单独的问题stackoverflow.com/questions/38349809/…。我还没有找到解决方案:(
    【解决方案2】:

    @abhishek 的 ans 可以满足要求,但下面的代码还在高度变化期间添加了动画

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

    【讨论】:

    • 这不是非常滞后/不流畅吗?至少对我来说(在模拟器上)。
    • @Timmiej93 你可以在我的应用程序play.google.com/store/apps/details?id=com.mirraw.android 上实时查看。进入产品详情页面。它完美无瑕:)
    • 对不起,我可能(很可能)非常愚蠢,但我在哪里可以找到您的应用程序?我可以从 GitHub 上提取它,但那个版本没有变化(据我所知)。编辑:您刚刚在我输入此内容时添加了链接,谢谢!
    • “产品详细信息页面”是指带有[规格-运输-付款-退货]的部分吗?如果是这样,那确实非常顺利。我猜我的速度非常慢,因为我正在调整 AlertDialog 的大小。为什么我总是想要不可能的事,哈哈。
    • 什么是versionUtils
    【解决方案3】:

    以防万一其他人像我一样发现这篇文章。没有初始高度为零的错误的工作版本:

    public class DynamicHeightViewPager extends ViewPager {
        private View mCurrentView;
    
        public DynamicHeightViewPager(Context context) {
            super(context);
        }
    
        public DynamicHeightViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            if (mCurrentView != null) {
                mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
    
                int height = Math.max(0, mCurrentView.getMeasuredHeight());
                heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
    
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }
    
        public void measureCurrentView(View currentView) {
            mCurrentView = currentView;
            requestLayout();
        }
    }
    

    并在自定义FragmentPagerAdapter中使用,像这样

    public abstract class AutoheightFragmentPagerAdapter extends FragmentPagerAdapter {
        private int mCurrentPosition = -1;
    
        public AutoheightFragmentPagerAdapter(FragmentManager fm) {
            super(fm);
        }
    
        @Override
        public void setPrimaryItem(ViewGroup container, int position, Object object) {
            super.setPrimaryItem(container, position, object);
    
            if (position != mCurrentPosition && container instanceof DynamicHeightViewPager) {
                Fragment fragment = (Fragment) object;
                DynamicHeightViewPager pager = (DynamicHeightViewPager) container;
    
                if (fragment != null && fragment.getView() != null) {
                    mCurrentPosition = position;
                    pager.measureCurrentView(fragment.getView());
                }
            }
        }
    }
    

    【讨论】:

    • measureCurrentView 没有在任何地方使用?
    • @John61590 感谢您的评论,我添加了自定义适配器示例
    • 如果在您的 Fragment 的布局之一中使用 ConstraintLayout,它似乎不起作用。
    【解决方案4】:

    添加到@vihaan的解决方案,如果你有PagerTitleStrip或PagetTabStrip,你可以添加这个

    // Account for pagerTitleStrip or pagerTabStrip
    View tabStrip = getChildAt(0);
    if (tabStrip instanceof PagerTitleStrip) {
        tabStrip.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.UNSPECIFIED));
        height += tabStrip.getMeasuredHeight();
    }
    

    就在开始动画之前(在评论之前

     // 不是放置此动画的最佳位置,但效果很好。
    

    以便考虑条带的高度。

    【讨论】:

    • 非常有帮助,我的身高没有被正确计算,因为它将 TabLayout 计为 0 的孩子,所以取所有孩子的最大高度不是一个好的解决方案。我需要孩子的最大身高 + 身高为 0。谢谢伙计,你救了我的一天。
    【解决方案5】:
    public class WrapContentViewPager extends ViewPager {
    private Boolean mAnimStarted = false;
    
    public WrapContentViewPager(Context context) {
        super(context);
    }
    
    public WrapContentViewPager(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 = ((CommonViewPagerAdapter) getAdapter()).getItem(getCurrentItem()).getView();
            if (child != null) {
                child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                height = child.getMeasuredHeight();
                if (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(100);
                startAnimation(a);
                mAnimStarted = true;
            } else {
                heightMeasureSpec = newHeight;
            }
        }
    
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    }
    

    ================================

    wrapContentViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }
    
            @Override
            public void onPageSelected(int position) {
                wrapContentViewPager.measure(wrapContentViewPager.getMeasuredWidth(), wrapContentViewPager.getMeasuredHeight());
            }
    
            @Override
            public void onPageScrollStateChanged(int state) {
    
            }
        });
    

    【讨论】:

      【解决方案6】:

      这几行代码就能解决问题。

      为 viewpager 类创建一个自定义小部件。并在 xml 中将其用于 viewpager。

      public class DynamicHeightViewPager extends ViewPager {
      private View mCurrentView;
      
      public DynamicHeightViewPager(Context context) {
          super(context);
      }
      
      public DynamicHeightViewPager(Context context, AttributeSet attrs) {
          super(context, attrs);
      }
      
      @Override
      public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      
          if (mCurrentView != null) {
              mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
      
              int height = Math.max(0, mCurrentView.getMeasuredHeight());
              heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
      
              super.onMeasure(widthMeasureSpec, heightMeasureSpec);
          }
      }
      
      public void measureCurrentView(View currentView) {
          mCurrentView = currentView;
          requestLayout();
      }
      }
      

      然后在视图分页适配器中重写下面的方法并添加代码。

        @Override
      public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
          super.setPrimaryItem(container, position, object);
          if (container instanceof DynamicHeightViewPager) {
      // instead of card view give your root view from your item.xml file.
              CardView cardView = (CardView) object;
              ((DynamicHeightViewPager) container).measureCurrentView(cardView);
          }
      }
      

      在 xml 文件中设置您的视图分页器高度 wrap_content,以便您检查结果。

      【讨论】:

      • 这看起来与 ITurchenko 的答案几乎相同,但比它大 2 岁。目前尚不清楚这应该解决的 ITurchenko 解决方案的问题是什么。
      猜你喜欢
      • 2023-03-02
      • 1970-01-01
      • 2012-02-29
      • 1970-01-01
      • 2012-04-04
      • 1970-01-01
      • 1970-01-01
      • 2019-10-12
      • 1970-01-01
      相关资源
      最近更新 更多