自定义九宫格控件(附源码地址)

发布于 2025-01-29 19:37:55 字数 5022 浏览 8 评论 0

最近公司在做个类似朋友圈的功能,需要一个九宫格控件,因为算是个常用控件,所以自己撸了一个。

职责分解

九宫格控件因为其需要承载其内部的 View,所以应该是一个 ViewGroup。每个子 View 基本上是相同的,但显示的内容不同,正好最近在看 RecyclerView,因此也就想到了可以使用 Adapter 来个性化每个子 View,正好通过 Adapter 模式来适配了子 View 与 ViewGroup。最后为了防止在创建、显示子 View 时的冗余操作,应该给子 View 增加一些属性,选用 ViewHolder 正好可以完成这个要求,这样也更方便以后的扩展。

ViewGroup、Adapter、ViewHolder 三者的具体职责

功能
ViewGroupAdapterViewHolder
Measure 子 View创建子 View 布局子 View 的获取
Layout 子 View子 View 的内容 displaydisplay 子 View 标志
add 子 View子 view 数量 
回收缓存数据变化通知 

ChildView 的测量

ChildView 测量所需要的数据主要来自于 ParentView 的测量数据,这里就是我们之前说的 ViewGroup 的 onMeasure 方法。

/**
 * 在这之前已经对 childView 的数量小于等于 0 或大于 9 的情况进行了处理
 * 这里讲显示情况分成三种进行测量:
 * 1、ChildView 数量为 1
 * 2、ChildView 数量为 2 或 4
 * 3、ChildView 数量的剩余类型
 */
if (adapter.getItemCount() > 1) {
    childSize = (width - border * 2) / 3;
    height = (int) (childSize * (int) Math.ceil(adapter.getItemCount() / 3.0) + border * (int) Math.ceil(adapter.getItemCount() / 3.0
    if (adapter.getItemCount() == 4 || adapter.getItemCount() == 2) {
        int currentWidth = childSize*2 + border;
        setMeasuredDimension(currentWidth + getPaddingLeft() + getPaddingRight(), height + getPaddingTop() + getPaddingBottom());
    }else {
        int currentWidth = childSize*3 + border*2;
        setMeasuredDimension(currentWidth + getPaddingLeft() + getPaddingRight(), height + getPaddingTop() + getPaddingBottom());
    }
} else {
    childSize = width/3;
    height = width/3;
    setMeasuredDimension(width + getPaddingLeft() + getPaddingRight(), height + getPaddingTop() + getPaddingBottom());
}

ChildView 的添加

在测量 ChildView 之后,将使用 addView 方法将其添加到 ViewGroup 中。

/**
 * 在增加 ChildView 之前,需要先行清除已经添加的所有 ChildView
 */
removeAllViews();
for (int i = 0; i < adapter.getItemCount(); i++) {
    addView(generateViewHolder(i).getItemView(),generateDefaultLayoutParams());
}

ChildView 的回收

generateViewHolder 是用于获取 ChildView 的方法,其中有一个简单的缓存列表

/**
 * 当需要添加的 ChildView 的 position 小于缓存列表大小时,直接从缓存列表中获取 ChildView
 * 否则,则调用 adapter.createView 方法,创建一个新的 ViewHolder,同时将其添加到缓存列表中
 */
private IKNinePhotoViewHolder generateViewHolder(int position){
    if (position < mRecyclerList.size()) {
        return mRecyclerList.get(position);
    } else {
        if (adapter != null){
            IKNinePhotoViewHolder holder = adapter.createView(IKNinePhotoView.this);
            if (holder == null){
                return null;
            }
            mRecyclerList.add(holder);
            return holder;
        } else
            return null;
    }
}

ChildView 的布局

在完成 ChildView 的测量和添加之后,需要对 ChildView 的位置进行确定。而这些数值来自 ParentView 的 onLayout 方法。

/**
 * 先对 ChildView 数量为 4 的特殊情况,进行处理,将其的列数确定为 2。
 */
int count = adapter.getItemCount();
int colNum = 3;
if (count == 4){
    colNum = 2;
}

/**
 * 便利每个 ChildView,对其进行布局
 */
for (int i = 0; i < count; i++) {
    View childView = getChildAt(i);
    if (childView == null){
        return;
    }
    if (adapter != null && mRecyclerList.get(i) != null &&!mRecyclerList.get(i).getFlag()) {
        adapter.displayView(generateViewHolder(i), i);
        // 设置这个标志,表示此 ViewHolder 中包含的 ChildView 已经完成了布局,防止多余的操作
        mRecyclerList.get(i).setFlag(true);
    }
    // 之前设置的列数
    int rows = i / colNum;
    int cols = i % colNum;
    int childLeft = getPaddingLeft() + (childSize + border) * (cols);
    int childTop = getPaddingTop() + (childSize + border) * (rows);
    int childRight = childLeft + childSize;
    int childBottom = childTop + childSize;
    childView.layout(childLeft, childTop, childRight, childBottom);
}

ChildView 数据的更新

到这里已经基本上完成了九宫格控件的编写,最后需要再添加一个数据更新的功能,以完备其功能。这里使用观察者模式的来实现这个功能,Adapter 继承 Observable 类、ViewGroup 实现 Observer 接口。

// 在 Adapter 中
public void notifyChanged(){
    setChanged();
    notifyObservers();
}

// 在 ViewGroup 中

@Override
public void update(Observable o, Object arg) {
    if (o instanceof IKNinePhotoViewAdapter){
        this.adapter = (IKNinePhotoViewAdapter) o;
        adapter.addObserver(this);
        for(IKNinePhotoViewHolder holder: mRecyclerList){
            holder.setFlag(false);
        }
        requestLayout();
        invalidate();
    }
}

总结

本文详细说明了九宫格控件的实现方法,并对其功能进行了分解。分解成多个组件之后,不仅符合程序的单一性原则,更加易于程序的功能扩展,比如添加监听事件,使用不同的图片库去显示图片,同时九宫格的 ChildView 也不仅限于 ImageView,而可以扩展到全部的 View。

源码地址: https://github.com/Idtk/IKNinePhotoView

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

我也只是我

暂无简介

文章
评论
27 人气
更多

推荐作者

李珊平

文章 0 评论 0

Quxin

文章 0 评论 0

范无咎

文章 0 评论 0

github_ZOJ2N8YxBm

文章 0 评论 0

若言

文章 0 评论 0

南…巷孤猫

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文