美文网首页常用收藏自定义控件
全解析布局神器vlayout

全解析布局神器vlayout

作者: Gugigugi | 来源:发表于2017-07-19 11:28 被阅读0次

    为什么要使用vlayout

    [vlayout]是对RecyclerView的LayoutManage的扩展,所以它接管了RecyclerView的整个布局方式,可以通过它实现各种各样的布局方式,非常的灵活。它提供的默认布局方式解耦所有的View和布局之间的关系: Linear, Grid, 吸顶, 浮动, 固定位置等,大致分为两类:一是非fix类型布局,像线性、Grid、栏格等,它们的特点是布局在整个页面流里,随页面滚动而滚动;另一类就是fix类型的布局,它们的子节点往往不随页面滚动而滚动。除了这个,它用来管理大的模块布局组合,使得同一RecyclerView内的组件可以复用,减少View的创建和销毁过程

    通过demo学习

    Paste_Image.png

    上面这张图是通过运行vlayout提供的demo得到的效果图,看起来挺多view,也挺复杂的,很难相信这只是一个RecyclerView,是不是也间接证明了vLayout的强大。

    下面来拆解下这么复杂的布局是如何在一个RecyclerView里实现的。

    开始使用

    基本用法

    final VirtualLayoutManager layoutManager = new VirtualLayoutManager(this);
    recyclerView.setLayoutManager(layoutManager);
    final DelegateAdapter delegateAdapter = new DelegateAdapter(layoutManager, true);
    recyclerView.setAdapter(delegateAdapter);
    final List<DelegateAdapter.Adapter> adapters = new LinkedList<>();
    //需要添加新的布局,就定义一个新的adapter
    delegateAdapter.setAdapters(adapters);
    

    需要定义DelegateAdapter.Adapter的子类,如下

    static class SubAdapter extends DelegateAdapter.Adapter<MainViewHolder> {
    
            private Context mContext;
    
            private LayoutHelper mLayoutHelper;
    
    
            private LayoutParams mLayoutParams;
            private int mCount = 0;
    
    
            public SubAdapter(Context context, LayoutHelper layoutHelper, int count) {
                this(context, layoutHelper, count, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300));
            }
    
            public SubAdapter(Context context, LayoutHelper layoutHelper, int count, @NonNull LayoutParams layoutParams) {
                this.mContext = context;
                this.mLayoutHelper = layoutHelper;
                this.mCount = count;
                this.mLayoutParams = layoutParams;
            }
    
            @Override
            public LayoutHelper onCreateLayoutHelper() {
                return mLayoutHelper;
            }
    
            @Override
            public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                return new MainViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, parent, false));
            }
    
            @Override
            public void onBindViewHolder(MainViewHolder holder, int position) {
                // only vertical
                holder.itemView.setLayoutParams(
                        new LayoutParams(mLayoutParams));
            }
    
    
            @Override
            protected void onBindViewHolderWithOffset(MainViewHolder holder, int position, int offsetTotal) {
                ((TextView) holder.itemView.findViewById(R.id.title)).setText(Integer.toString(offsetTotal));
            }
    
            @Override
            public int getItemCount() {
                return mCount;
            }
        }
    

    添加一个普通的布局:adapter

    adapters.add(new SubAdapter(this, new LinearLayoutHelper(), 1) {
    
                    @Override
                    public void onViewRecycled(MainViewHolder holder) {
                        if (holder.itemView instanceof ViewPager) {
                            ((ViewPager) holder.itemView).setAdapter(null);
                        }
                    }
    
                    @Override
                    public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                        if (viewType == 1)
                            return new MainViewHolder(
                                    LayoutInflater.from(VLayoutActivity.this).inflate(R.layout.view_pager, parent, false));
    
                        return super.onCreateViewHolder(parent, viewType);
                    }
    
                    @Override
                    public int getItemViewType(int position) {
                        return 1;
                    }
    
                    @Override
                    protected void onBindViewHolderWithOffset(MainViewHolder holder, int position, int offsetTotal) {
    
                    }
    
                    @Override
                    public void onBindViewHolder(MainViewHolder holder, int position) {
                        if (holder.itemView instanceof ViewPager) {
                            ViewPager viewPager = (ViewPager) holder.itemView;
    
                            viewPager.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200));
    
                            // from position to get adapter
                            viewPager.setAdapter(new PagerAdapter(this, viewPool));
                        }
                    }
                });
    

    看下效果图

    Paste_Image.png

    添加一个悬浮的布局:adapter

    FloatLayoutHelper layoutHelper = new FloatLayoutHelper();
                layoutHelper.setAlignType(FixLayoutHelper.BOTTOM_LEFT);
                layoutHelper.setDefaultLocation(100, 400);
                LayoutParams layoutParams = new LayoutParams(150, 150);
                adapters.add(new SubAdapter(this, layoutHelper, 1, layoutParams));
    

    效果图:

    Paste_Image.png

    分析:

    • FloatLayoutHelper是悬浮布局的实现类
    • setAlignType 有四种: TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT
    • setDefaultLocation: x, y 代表与边界的距离,和alignType有关系,比如:TOP_LEFT, x 是与左边的距离,y是与上边的距离
    • 可以随手势拖动

    添加 宽比高为2的布局

    LinearLayoutHelper layoutHelper1 = new LinearLayoutHelper();
    layoutHelper1.setAspectRatio(2.0f);
    adapters.add(new SubAdapter(this, layoutHelper1, 1));
    

    效果图:

    Paste_Image.png

    分析:
    setAspectRatio(2.0f)设置宽比高比例为2的布局

    添加子列表

    LinearLayoutHelper layoutHelper2 = new LinearLayoutHelper();
                layoutHelper2.setAspectRatio(4.0f);
                layoutHelper2.setDividerHeight(10);
                layoutHelper2.setMargin(10, 30, 10, 10);
                layoutHelper2.setPadding(10, 30, 10, 10);
                layoutHelper2.setBgColor(0xFFF5A623);
                adapters.add(new SubAdapter(this, layoutHelper2, 6) {
    
                    @Override
                    public void onBindViewHolder(MainViewHolder holder, int position) {
                        if (position % 2 == 0) {
                            LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300);
                            layoutParams.mAspectRatio = 5;
                            holder.itemView.setLayoutParams(layoutParams);
                        }
                    }
                });
    
    Paste_Image.png

    分析:
    我们看到的是一个垂直方向的列表,如果想要变成一个水平方向上的列表,需要修改VirtualLayoutManagerorientation

    layoutManager.setOrientation(VirtualLayoutManager.HORIZONTAL);
    

    效果图:

    Paste_Image.png

    添加一个sticky布局,黏住但不固定,手势之后会变化

    StickyLayoutHelper layoutHelper = new StickyLayoutHelper();
                layoutHelper.setOffset(100);
                layoutHelper.setAspectRatio(4);
                adapters.add(new SubAdapter(this, layoutHelper, 1, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100)));
    
    Paste_Image.png

    添加只有一个view的布局

                SingleLayoutHelper singleLayoutHelper = new SingleLayoutHelper();
                singleLayoutHelper.setBgColor(Color.rgb(135, 225, 90));
                singleLayoutHelper.setAspectRatio(4);
                singleLayoutHelper.setMargin(10, 20, 10, 20);
                singleLayoutHelper.setPadding(10, 10, 10, 10);
                adapters.add(new SubAdapter(this, singleLayoutHelper, 2, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100)));
    
    Paste_Image.png

    分析:
    当你使用SingleLayoutHelper时,如果SubAdapter的数量是多少,界面上只有显示一个view.

    添加一个表格布局

    ColumnLayoutHelper layoutHelper = new ColumnLayoutHelper();
                layoutHelper.setBgColor(0xff00f0f0);
                layoutHelper.setWeights(new float[]{40.0f, Float.NaN, 40});
                adapters.add(new SubAdapter(this, layoutHelper, 5) {
    
                    @Override
                    public void onBindViewHolder(MainViewHolder holder, int position) {
                        if (position == 0) {
                            LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300);
                            layoutParams.mAspectRatio = 4;
                            holder.itemView.setLayoutParams(layoutParams);
                        } else {
                            LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300);
                            layoutParams.mAspectRatio = Float.NaN;
                            holder.itemView.setLayoutParams(layoutParams);
                        }
                    }
    
                });
    
    Paste_Image.png

    分析:
    setWeights(new float[]{40.0f, Float.NaN, 40})确定了每列的比重权值,默认总权值是100,第一列和第三列是40,其他都是任意。我们看第一行为什么是这样的一个高度,因为下面的代码layoutParams.mAspectRatio = 4限制了宽高比是4。

    添加一个OnePlusNLayoutHelper(数量为2)

    需要先了解下它支持的布局方式有下面几种

     * 1 + 0
     * -------------------------
     * |           1           |
     * -------------------------
     *
     * 1 + 1
     * -------------------------
     * |           |           |
     * |     1     |     2     |
     * |           |           |
     * -------------------------
     *
     * 1 + 2
     * -------------------------
     * |           |     2     |
     * |     1     |-----------|
     * |           |     3     |
     * -------------------------
     *
     * 1 + 3
     * -------------------------
     * |           |     2     |
     * |     1     |-----------|
     * |           |  3  |  4  |
     * -------------------------
     *  1 + 4
     * -------------------------
     * |           |     2     |
     * |     1     |-----------|
     * |           | 3 | 4 | 5 |
     * -------------------------
    

    比如下面

    OnePlusNLayoutHelper helper = new OnePlusNLayoutHelper();
                helper.setBgColor(0xff876384);
                helper.setAspectRatio(4.0f);
                helper.setColWeights(new float[]{40f, 45f});
                helper.setMargin(10, 20, 10, 20);
                helper.setPadding(10, 10, 10, 10);
                adapters.add(new SubAdapter(this, helper, 2));
    
    Paste_Image.png

    分析:
    setAspectRatio(4.0f)设置整体布局的宽高比,setColWeights(new float[]{40f, 45f})设置列的权重,总权重是100,这里设置了第一列权重是40,第二列权重是45

    添加一个OnePlusNLayoutHelper(数量为4)

    OnePlusNLayoutHelper helper = new OnePlusNLayoutHelper();
                helper.setBgColor(0xffef8ba3);
                helper.setAspectRatio(2.0f);
                helper.setColWeights(new float[]{40f});
                helper.setRowWeight(30f);
                helper.setMargin(10, 20, 10, 20);
                helper.setPadding(10, 10, 10, 10);
                adapters.add(new SubAdapter(this, helper, 4) {
                    @Override
                    public void onBindViewHolder(MainViewHolder holder, int position) {
                        super.onBindViewHolder(holder, position);
                        LayoutParams lp = (LayoutParams) holder.itemView.getLayoutParams();
                        if (position == 0) {
                            lp.rightMargin = 1;
                        } else if (position == 1) {
    
                        } else if (position == 2) {
                            lp.topMargin = 1;
                            lp.rightMargin = 1;
                        }
                    }
                });
    
    Paste_Image.png

    分析:
    当数量为4,为什么是这样一个展示页面,参考上面的(1+3)
    setAspectRatio(2.0f)设置整个布局宽高比是2,setColWeights(new float[]{40f})设置第一列权重是40,
    setRowWeight(30f)设置第一行的权重是30,行总权重是100

    添加一个固定布局

    FixLayoutHelper layoutHelper = new FixLayoutHelper(FixLayoutHelper.TOP_RIGHT, 20, 20);
    
                adapters.add(new SubAdapter(this, layoutHelper, 1) {//29
                    @Override
                    public void onBindViewHolder(MainViewHolder holder, int position) {
                        super.onBindViewHolder(holder, position);
                        LayoutParams layoutParams = new LayoutParams(200, 200);
                        holder.itemView.setLayoutParams(layoutParams);
                    }
                });
    
    Paste_Image.png

    分析:

    • 固定类型(alignType)有四种:TOP_LEFT(左上角),TOP_RIGHT(右上角),BOTTOM_LEFT(左下角),BOTTOM_RIGHT(右下角)
    • 固定的边距,如上x=20, y=20, 所以就得到上面的固定样式
    • LayoutParams layoutParams = new LayoutParams(200, 200); 设置固定布局的子view的宽高尺寸

    添加一个方格

    GridLayoutHelper layoutHelper = new GridLayoutHelper(4);
                layoutHelper.setWeights(new float[]{20f, 26.665f});
                layoutHelper.setMargin(7, 0, 7, 0);
                layoutHelper.setHGap(3);
                adapters.add(new SubAdapter(this, layoutHelper, 8));
    
    Paste_Image.png

    分析:

    • GridLayoutHelper(4)类似于GridView,设置列数量位4
    • setWeights(new float[]{20f, 26.665f})设置第一列和第二列的权重分别是20,26.665,其他列均分,列总权重是100,计算一下,第三列和第四列的权重是26.6675
    • setHGap(3)类似于GridView,设置水平间距是3像素
    • new SubAdapter(this, layoutHelper, 8) 设置方格总数量是8

    添加一个Staggered布局

    final StaggeredGridLayoutHelper helper = new StaggeredGridLayoutHelper(2, 10);
                helper.setMargin(20, 10, 10, 10);
                helper.setPadding(10, 10, 20, 10);
                helper.setBgColor(0xFF86345A);
                adapters.add(new SubAdapter(this, helper, 27) {
                    @Override
                    public void onBindViewHolder(MainViewHolder holder, int position) {
                        super.onBindViewHolder(holder, position);
                        LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200);
                        if (position % 2 == 0) {
                            layoutParams.mAspectRatio = 1.0f;
                        } else {
                            layoutParams.height = 340 + position % 7 * 20;
                        }
                        holder.itemView.setLayoutParams(layoutParams);
                    }
                });
    
    Paste_Image.png

    分析:

    • StaggeredGridLayoutHelper(2, 10)设置列数量为2,间距为10像素
    • new SubAdapter(this, helper, 27) 设置子view总数量为27
    • 下面这段代码是设置每个子view的宽高尺寸,所以才形成了Staggered
    LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200);
                        if (position % 2 == 0) {
                            layoutParams.mAspectRatio = 1.0f;
                        } else {
                            layoutParams.height = 340 + position % 7 * 20;
                        }
    

    vLayout提供的默认布局

    Paste_Image.png

    一般情况下,这些都是够用的,如果出现不够的情况,可以参考上面的布局来实现自己的。

    源码分析其实现的原理

    因为篇幅有限,这里就直接定位到其实现的最底层代码,定位到VirtualLayoutManagerpublic void setLayoutHelpers(@Nullable List<LayoutHelper> helpers)方法,通过这个方法我们注意到上面我们所调用的helper,都已经转换成配置信息了,最后一段代码requestLayout();重新刷新布局,接下来会调用public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state),然后就会调用public void layoutChild(View child, int left, int top, int right, int bottom)对每个子view进行布局

    下面是我画的简单的调用示意图

    vlayout.jpg

    有兴趣交流和讨论的,请在下面发评论。

    有兴趣一起学习和讨论kotlin,请加Q群:248445350
    [vlayout]:https://github.com/alibaba/vlayout

    相关文章

      网友评论

        本文标题:全解析布局神器vlayout

        本文链接:https://www.haomeiwen.com/subject/mgbqkxtx.html