自定义容器实现网格(九宫格)布局效果

前言:在近期的项目开发中,需要在列表的item中实现网格布局的ui效果,考虑到列表的性能和布局嵌套的层次等因素,最后决定自己写一个支持网格布局效果的容器,直接在列表的item中使用该容器就可以支持网格(九宫格)的显示效果。避免了在列表中嵌套列表的实现方式。

下面的类FeedBaseGridView支持自定义列数、行数、不同行高的ui效果,需要实现类似效果的同学可以直接复制下面的类的代码到项目中,按照类的使用方式直接使用就行,简单方便。

下面直接展示代码:

abstract class FeedBaseGridView extends FrameLayout {

    /** context */
    private Context mContext;
    /** item的宽度 */
    private int mChildrenWidth;
    /** item集合 */
    private ArrayList<View> mChildrenList = new ArrayList<>();
    /** item的top值的对应集合 */
    private SparseIntArray mItemRowTopList = new SparseIntArray();

    public FeedBaseGridView(Context context) {
        this(context, null);
    }

    public FeedBaseGridView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FeedBaseGridView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
    }

    /**
     * 根据总view数获取总行数
     *
     * @return
     */
    private int getRows() {
        int remainder = mChildrenList.size() % getColumns();
        int integer = mChildrenList.size() / getColumns();
        return remainder > 0 ? integer + 1 : integer;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        onMeasureChildren(widthMeasureSpec, heightMeasureSpec);
    }

    /**
     * 测量子view的尺寸并更新view的top值与行数对照表的数据
     */
    private void onMeasureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        int rows = getRows();
        int columns = getColumns();
        // 第一行的top值直接为0
        mItemRowTopList.put(0, 0);
        for (int i = 1; i <= rows; i++) { // 此处让i <= rows 是为了当i=rows 时得到容器的高度
            int max = 0;
            // 计算当前行的view的top的值
            for (int i1 = 0; i1 < columns; i1++) {
                int index = (i - 1) * columns + i1;
                if (mChildrenList.size() > index) {
                    mChildrenList.get(index).measure(widthMeasureSpec, heightMeasureSpec);
                    max = Math.max(mChildrenList.get(index).getMeasuredHeight(), max);
                }
            }
            if (i < rows) {
                // 得到每一行的view的top值
                mItemRowTopList.put(i, max + getVerticalGap() + mItemRowTopList.get(i - 1));
            } else {
                // 此处是根据所有行数的高度总和 得到的容器的高度
                mItemRowTopList.put(i, max + mItemRowTopList.get(i - 1)); // 计算当前容器的高度
            }
        }
        // 设置容器的高度
        setMeasuredDimension(widthMeasureSpec, mItemRowTopList.get(getRows()));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 重写放置子view的位置
        layoutChildrenView();
    }

    /**
     * 布局所有子view的位置
     */
    private void layoutChildrenView() {
        if (mChildrenList.size() <= 0) {
            return;
        }
        int count = mChildrenList.size();
        for (int i = 0; i < count; i++) {
            // 左位置
            int left = (mChildrenWidth + getHorizontalGap()) * (i % getColumns());
            // 顶位置
            int top = mItemRowTopList.get(i / getColumns());
            // 重布局子view
            mChildrenList.get(i).layout(
                    left, top, left + mChildrenWidth, top + mChildrenList.get(i).getMeasuredHeight());
        }
    }

    /**
     * 子类在数据准备完毕以后 调用该方法更新页面
     */
    public void notifyChanged() {
        // 更新子view的宽度
        mChildrenWidth = getChildrenWidth();
        // 已有view的复用机制 提高性能
        int oldViewCount = mChildrenList.size();
        int newViewCount = getCounts();
        if (oldViewCount > newViewCount) {
            removeViews(newViewCount, oldViewCount - newViewCount);
            for (int i = oldViewCount - 1; i >= newViewCount; i--) {
                mChildrenList.remove(i);
            }
        } else if (oldViewCount < newViewCount) {
            for (int i = 0; i < newViewCount - oldViewCount; i++) {
                addChild();
            }
        }
        // 更新view的数据
        setChildInfo();
        // 刷新页面
        requestLayout();
    }

    /**
     * 更新子view的数据
     * 保证mChildrenList的大小和子view的数量匹配
     */
    private void setChildInfo() {
        for (int i = 0; i < mChildrenList.size(); i++) {
            updateChildrenView(mChildrenList.get(i), i);
        }
    }

    /**
     * 添加子view
     */
    private void addChild() {
        View view = createChildrenView();
        mChildrenList.add(view);
        addView(view);
    }

    /**
     * 切换日夜间的时候调用该方法
     *
     * @param isNightMode 是否是夜间模式
     */
    public void changedNightMode(boolean isNightMode) {
        for (View view : mChildrenList) {
            onNightModeChanged(view, isNightMode);
        }
    }

    /**
     * 子类重写此方法,用于相应日夜间切换的效果处理
     * @param view 需要处理的view
     * @param isNightMode 是否夜间模式
     */
    protected abstract void onNightModeChanged(View view, boolean isNightMode);

    /**
     * 由子类重写 获取view水平方向的间距
     * @return
     */
    public abstract int getHorizontalGap();

    /**
     * 由子类重写 获取每行的列数
     * 列数最少为1 使用时请确保 不要返回0
     * @return
     */
    public abstract int getColumns();

    /**
     * 由子类重写 获取view数
     * @return
     */
    public abstract int getCounts();

    /**
     * 由子类重写 获取view竖直方向的间距
     * @return
     */
    public abstract int getVerticalGap();

    /**
     * 由子类重写 获取view
     * @return
     */
    public abstract View createChildrenView();

    /**
     * 由子类重写 更新子view
     * @param position 当前view的位置
     * @param view 当前的view
     */
    public abstract void updateChildrenView(View view, int position);

    /**
     * 获取子view的宽度,此处提供默认的,如果需要修改可以重写
     * @return int 子view的宽度
     */
    protected int getChildrenWidth() {
        return (FeedTemplateUtil.getCalculateWidth(mContext)
                - DeviceUtil.ScreenInfo.dp2px(getContext(), 30)
                - getHorizontalGap() * (getColumns() - 1)) / getColumns();
    }
}

使用方式:

该类被定义为抽象容器类,使用此类需要新写容器类继承该类并按照规范实现以下抽象方法即可。

1.getHorizontalGap() :需要重写此方法提供每列之间的间距。

2.getColumns() :需要重写此方法提供每行的列数。注意:列数最少为1列,写代码时请确保不要返回0。

3.getCounts() :重写此方法提供网格的item的总数量。

4.getVerticalGap() :需要重写此方法提供每行之间的间距。

5.createChildrenView() :重写此方法提供需要显示的ui效果的每个item 的view,类似于adapter的onCreateViewHolder方法。

6.updateChildrenView(View view, int position) :重写此方法用于对item的view显示和数据更新,类似于adapter的onBindViewHolder方法。

最后在使用的类里面,调用 该类的notifyChanged() 方法触发九宫格效果的加载和view的显示更新。

实现就是如此简单,see you 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值