Android 实现网格效果

924 阅读5分钟

实现一个类似网格的效果,完成这个需知道Android View 动态加载以及其他的一些知识点,通过在网上的学习,学习到了一些知识点,这里做一下笔记,忘了的话方便回顾。

完成这个需知道Android View 动态加载,

加载View

用 LayoutInflater 的 inflate() 加载

先获取LayoutInflater

LayoutInflater inflater1 = getLayoutInflater(); //调用Activity的getLayoutInflater()
LayoutInflater inflater2 = LayoutInflater.from(context);
LayoutInflater inflater3 =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

这三种方法都可以,而且都是都会调用到如下方法,

这样有了LayoutInflater ,调用它的inflate() ,即可。

inflater3.inflate(R.layout.item_layout, mLinear);
inflater3.inflate(R.layout.item_layout,mLinear,true);

可见有两个参数的,也有三个参数的,那有什么不同呢?接下来我们来分析一下。

首先不难发现,两个参数的实际上是调用了是哪个参数的,那么我们弄明白三个参数的就行了。

看一下调用链,

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                  + Integer.toHexString(resource) + ")");
        }

        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

这里XmlResourceParser 当然是帮我们加载xml 文件的,我们这里不用管,那么自然要看后两个参数的作用了。

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            ...
                final String name = parser.getName();
        	...
            // Temp is the root view that was found in the xml
            final View temp = createViewFromTag(root, name, inflaterContext, attrs);

            ViewGroup.LayoutParams params = null;

            if (root != null) {
                if (DEBUG) {
                    System.out.println("Creating params from root: " +
                            root);
                }
                // Create layout params that match root, if supplied
                params = root.generateLayoutParams(attrs);
                if (!attachToRoot) {
                    // Set the layout params for temp if we are not
                    // attaching. (If we are, we use addView, below)
                    temp.setLayoutParams(params);
                }
            }
                    ...

            // Inflate all children under temp against its context.
            rInflateChildren(parser, temp, attrs, true);
                    ...

            // We are supposed to attach all the views we found (int temp)
            // to root. Do that now.
            if (root != null && attachToRoot) {
                root.addView(temp, params);
            }

            // Decide whether to return the root that was passed in or the
            // top view found in xml.
            if (root == null || !attachToRoot) {
                result = temp;
            }
        }
            ...

        return result;
    }

省略部分代码,可以看到, 先是获得View ;

然后判断root 是否为空,如果不会空,那么根据root 获取LayoutParams ,这个布局参数将会设置到该view 上;

接着判断attachToRoot ,true 说明要attch 到传进来的父布局上,那么会调用root.addView(temp, params); 来将添加view 到root 上,并设置布局参数;反之attachToRoot 为false 则不会添加到root 上,只是通过temp.setLayoutParams(params); 设置布局参数;

那么可以得出这样的结论

root 不为空,则会为加载的view 设置布局参数;那是否将该view 添加到root 上,则取决于attachToRoot (true 则会添加);

那么root 为空会怎样呢?

root 为空,加载的view ,但不会为该view设置布局参数,也不会把该View 添加到root;

由此可见三个参数的inflate 的优势是相当灵活,一些参数我们可以自己指定;

用View 的 inflate() 加载

View view = View.inflate(this, R.layout.item_layout, mLinear);
View view = View.inflate(this, R.layout.item_layout, null);

有什么不同呢?

看代码分析一下,

public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
    }

原来还是调用了LayoutInflater 的inflate 方法,

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

而且根据传进去的root 是不是空来决定attachToRoot 是否为true ;

所以理解了LayoutInflater 三个参数的inflate 方法,就可以了。

layout_weight

在父布局是LinearLayout 时,使用android:layout_weight 属性可以很好的实现权重(比例)进行划分,

通过做这个需求发现严格来说是剩余空间按权重划分,那么控件的宽或高就是控件自身的宽或高加上剩余空间按权重划分的大小;

举个例子,

``` ``` 效果如下, ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/59e9060eaa8e40a2aeed25fb3f2dc864~tplv-k3u1fbpfcp-zoom-1.image)

其实计算是这样的, 由于三个TextView 宽度都设置为0 ,所以剩余宽度=屏幕宽度, 屏幕宽度设为m ,那么每个TextView 的宽度就为0+m*1/3=m/3 ,所以就达到如图的平分的效果了。

如果我们将每个TextView 的宽度都设置成match_parent 达到的效果一样,还是平分,但计算过程如下,

屏幕宽度设为m ,

剩余空间 为 m - 3*m = -2m

那么每个TextView 的最终宽度为m + (-2m)*1/3 = m/3 ,所以达到的效果是平分的。

那如果我们将每个TextView 的宽度设置成wrap_content 那运行的效果不是平分的,如图,

明显一个x 的最小,三个x 的最大, 分析计算过程,这里一个x 的TextView按x来算,因为 wrap_content 就是围绕内容的,

那么剩余空间就是m - 3x ,

那第一个TextView 宽度为x + (m-3x)/3,

那第二个TextView 宽度为2x + (m-3x)/3,

那第三个TextView 宽度为3x + (m-3x)/3,

这样就不难理解两个x的比三个x的要宽一点,三个x的比两个x的要宽一点;

所以计算宽度就是 控制设置的宽度 + 剩余空间的宽度 * w1/(w1+w2+w3) ;

实现网格效果

有了上面两个知识点的积累,我们就可以实现网格效果了,

思路是向LinearLayout 里添加水平方向的LinearLayout 作为一行个水平方向的线性布局里根据列数添加item ,就这样添加完一行item 就再添加一个水平方向的LinearLayout 作为下一行,直到添加完item 视图为止,这样添加完就形成了一个网格效果。

效果如下,

代码如下,

private void refreshLayout(List<String> list, int rowCount, int columnCount) {
        for (int r = 0; r < rowCount; r++) {
            LinearLayout rowLayout = new LinearLayout(context);
            rowLayout.setLayoutParams(new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
            rowLayout.setOrientation(LinearLayout.HORIZONTAL);

            for (int c = 0; c < columnCount; c++) {
                int index = r * columnCount + c;

                if (index < list.size()) {
                    View itemView = LayoutInflater.from(this).inflate(R.layout.item_layout, null);
                    LinearLayout.LayoutParams itemLayoutParams = new LinearLayout.LayoutParams(
                            ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                    itemLayoutParams.weight = 1;

                    TextView title = itemView.findViewById(R.id.title);
                    title.setText(list.get(index));

                    rowLayout.addView(itemView, itemLayoutParams);
                } else {
                    View space = new View(context);
                    rowLayout.addView(space, new LinearLayout.LayoutParams(
                            ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
                }

                if (c < columnCount -1) {
                    View verticalLine = new View(context);
                    LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                            dp2px(context, 5), ViewGroup.LayoutParams.MATCH_PARENT);
                    rowLayout.addView(verticalLine, lp);
                }
            }
            mLinear.addView(rowLayout);
            View horizontalLine = new View(context);
            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, dp2px(context, 5));
            mLinear.addView(horizontalLine, lp);
        }
    }