ViewPager的坑

前言

做程序开发,基础很重要。同样是拧螺丝人家拧出来的可以经久不坏,你拧出来的遇到点风浪就开始颤抖,可见基本功的重要性。此系列,专门收录一些看似基础,但是没那么简单的小细节,同时提供权威解决方案。喜欢的同志们点个赞就是对我最大的鼓励!先行谢过!

网上可能有一些其他文章,提供了解决方案,但是要么就是没有提供可运行demo,要么就是demo不够纯粹,让人探索起来受到其他代码因素的影响,无法专注于当前这个知识点(比如,我只是想了解Activity的生命周期,你把生命周期探究的过程混入到一个很复杂的大杂烩Demo中,让人一眼就没有了阅读Demo代码的欲望),所以我觉得有必要做一个专题,用最纯粹的方式展示一个的解决方案.

正文

ViewPager。老牌系统控件了,坑当然也有不少,今天先说第一个。

Github地址:https://github.com/18598925736/ViewPagerKeng
建议下载源码之后对照阅读

错误示范

很简单,activity_main.xml里面 一个ViewPager




    

    

然后:MainActivity.java 很简单的一个ViewPageradapter使用。

package study.hank.com.viewpager;

import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ViewPager vp = findViewById(R.id.vp);

        List list = new ArrayList<>();
        list.add(new Data("1"));
        list.add(new Data("2"));
        list.add(new Data("3"));
        vp.setAdapter(new MyPagerAdapter(this, list));
    }

    class MyPagerAdapter extends PagerAdapter {

        private Context context;
        private List dataList;

        public MyPagerAdapter(Context context, List dataList) {
            this.context = context;
            this.dataList = dataList;
        }

        @Override
        public int getCount() {
            return dataList == null ? 0 : dataList.size();
        }

        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container, int position) {
            View root = LayoutInflater.from(context).inflate(R.layout.vp_item, container, false);
            TextView tv = root.findViewById(R.id.tv);
            tv.setText(dataList.get(position).text);
            container.addView(root);
            return root;
        }

        @Override
        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
            container.removeView((View) object);
        }

        @Override
        public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
            return view == o;
        }

    }

    private class Data {
        String text;

        public Data(String text) {
            this.text = text;
        }
    }
}

但是请注意:adapter的item布局如下:




    



这个布局的根节点宽高为: wrap_content 以及 200dp
原本我的想法是,子view设置了自适应宽,和固定高度,那么viewPager作为父容器,也应该呈现相应的宽高。
但是打脸了。

WTF??
你居然默认充满了???

思考一下,决定一个View宽高的是什么? View的onMeasure方法。
于是看看ViewPageronMeasure,一看吓一跳:

ViewPager的坑_第1张图片

WTF? 难道我使用ViewPager就只能充满父容器么?万一我一个容器里要放多个ViewPager,那不是还要包一层布局?太low了。

解决方案

既然ViewPager的测量这么坑,那就不要用他自己的onMeasure了。我们帮它测。

package study.hank.com.viewpager;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class MyViewPager extends ViewPager {
    public MyViewPager(@NonNull Context context) {
        super(context);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int hSpec, wSpec;
        int finalH = 0, finalW = 0;
        final int count = getChildCount();
        View child;

        //我原本想直接使用子view测量之后的宽高,然后发现,尼玛,ViewPager根本就没有针对考虑子View的WrapContent
//        for (int i = 0; i < count; i++) {
//            //算出所有子view的最大宽度
//            child = getChildAt(i);
//            int thisH = child.getMeasuredHeight();
//            int thisW = child.getMeasuredWidth();
//            finalH = finalH > thisH ? finalH : thisH;
//            finalW = finalW > thisW ? finalW : thisW;
//        }
//
//        hSpec = MeasureSpec.makeMeasureSpec(finalH, MeasureSpec.EXACTLY);
//        wSpec = MeasureSpec.makeMeasureSpec(finalW, MeasureSpec.EXACTLY);

        // 那没办法了,那就再对子view进行一次常规测量
        // 坑并不只是 ViewPager没有对子进行测量,而是,它对于子的测量,也是使用的默认matchParent··使得子view充满父容器
        // 那我自己来考虑子view的LayoutParam来测量一次
        for (int i = 0; i < count; i++) {
            child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            int thisH = child.getMeasuredHeight();
            int thisW = child.getMeasuredWidth();
            finalH = finalH > thisH ? finalH : thisH;
            finalW = finalW > thisW ? finalW : thisW;
            Log.d("MeasureSpec ", "finalH:" + finalH + "/finalW:" + finalW);
        }

        hSpec = MeasureSpec.makeMeasureSpec(finalH, MeasureSpec.EXACTLY);//这里的模式。。emmm
        wSpec = MeasureSpec.makeMeasureSpec(finalW, MeasureSpec.EXACTLY);

        setMeasuredDimension(wSpec, hSpec);
    }
}

继承ViewPager,重写onMeasure,再次给他setMeasuredDimension.
我们自己给他的测量,考虑子View的宽高,并且得出子view的最大宽高,设置给它。

正确示范

使用我们自己的MyViewPager替换它。




    

    

现在的效果:

我们给子view设置的宽高生效了

抛砖引玉

案例确实很简单,但是如果我们遇到问题,不知道如何思考,只知道去找度娘的话,那永远是个lowB。或者 你用一层布局,把ViewPager包起来,那更是lowB。作为一个高级开(码)发(农),要学会从根本上解决问题,从源码层寻找解决方案。
例如今天这个案例,我的目的是,当ViewPager的item 是自适应宽高的时候,我怎么让外部的ViewPager也呈现出自适应呢? 它原本的宽高就是占满父容器。从源码中找到原因,是它的onMeasure出现 了奇葩的逻辑,
虽然不知道谷歌大佬为什么这么写,囧。重写onMeasure之后,解决了问题,满足了需求。
但是,如果有其他需求呢?比如,宽度要求充满,高度要自适应子item。或者高度要自适应,宽度要是精确值。原理相同,按照需求重写onMeasure即可。

今天小小案例,抛砖引玉,希望帮助有缘人打开思路,不要在CV的道路上越走越黑。。。(像是对我自己说的 - -!)

最后

如果你看到了这里,觉得文章写得不错就点个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

有一句老话说的好:**“比你优秀的对手在学习,你的仇人在磨刀,你的闺蜜在减肥,隔壁老王在练腰,我们必须不断学习,否则我们将被学习者超越。”**当然一个人学习是枯燥的,还需要一个良好的学习氛围,因此我组建了一个学习交流探讨的社群,欢迎大家一起来交流探讨共同进步。还有一些收集整理的资料,感兴趣的可以来一起学习,共同进步!

针对Android开发的同行,这边给大家整理了一些资料,其中分享内容包括但不限于**【高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术】**希望能帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也是可以分享给身边好友一起学习的!

希望读到这的您能转发分享关注一下我,以后还会更新技术干货,谢谢您的支持!

点赞+关注,关注微信公众号【Android开发之家】获取小编为大家收录的进阶资料和面试题库

转发+点赞,关注微信公众号【Android开发之家】获取小编为大家收录的进阶资料和面试题库

Android架构师之路很漫长,一起共勉吧!

你可能感兴趣的:(ViewPager的坑)