Android(Kotlin)类似微博的九宫格图片显示控件

前言

在微博浏览的时候,我们可以看到一个类似下图的九宫格图片显示控件,类似的效果在微信朋友圈里面也有遇到。当只有一张图片的时候就显示一张图片占满布局宽度;如果有2-4张图片,则显示两列;如果有5-9张图片泽显示三列。
微博九宫格截图
由于工作项目的原因,我也需要实现一个类似这样的图片显示控件,通过百度发现,现在有两种方式进行制作,一个是自定义View的方式,另一个是通过ViewGroup。在这里,我采用的是ViewGroup的方式实现(参考w4lle大神的源码)。
现在Android的官方语言已经变为了Kotlin,所以这里我使用的是Kotlin编写,直接上代码吧!

NineGridlayout

NineGridlayout是图片显示的自定义ViewGroup,在layout布局文件里面直接使用就可以了

class NineGridlayout : ViewGroup {
    private val TAG = "NineGridlayout"
    private var adapter: NineGridAdapter? = null
    private var onItemClickListerner: OnItemClickListener? = null
    /**
     * 默认图片间隔
     */
    private val ITEM_GAP = 3
    private val DEFAULT_WIDTH = 140
    /**
     * 图片之间的间隔
     */
    var gap: Int = 0
    private var columns: Int = 0//
    private var rows: Int = 0//
    private var totalWidth: Int = 0
    internal var singleWidth = 0
    internal var singleHeight = 0
    private var defaultWidth: Int = 0
    private var defaultHeight: Int = 0

    private var oldCount: Int = 0
    private var isFirstLayout = true

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        gap = dip2px(context, ITEM_GAP.toFloat())
        defaultHeight = dip2px(context, DEFAULT_WIDTH.toFloat())
        defaultWidth = defaultHeight
    }

    fun setDefaultWidth(defaultWidth: Int) {
        this.defaultWidth = defaultWidth
    }

    fun setDefaultHeight(defaultHeight: Int) {
        this.defaultHeight = defaultHeight
    }

    fun setAdapter(adapter: NineGridAdapter?) {
        this.adapter = adapter
        if (adapter == null) {
            return
        }
        //初始化布局形状
        generateChildrenLayout(adapter.getCount())
        removeAllViews()

        for (i in 0..adapter.getCount() - 1) {
            val itemView = adapter.getView(i)
            addView(itemView, generateDefaultLayoutParams())
        }

        oldCount = adapter.getCount()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        Log.e(TAG, "onMeasure")
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthMode = View.MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = View.MeasureSpec.getMode(heightMeasureSpec)
        val sizeWidth = View.MeasureSpec.getSize(widthMeasureSpec)
        val sizeHeight = View.MeasureSpec.getSize(heightMeasureSpec)
        totalWidth = sizeWidth - paddingLeft - paddingRight
        if (adapter != null && adapter!!.getCount() > 0) {
            val measureHeight: Int
            //计算单个图片的大小
            singleWidth = (totalWidth - gap * (columns - 1)) / columns
            singleHeight = singleWidth

            measureChildren(View.MeasureSpec.makeMeasureSpec(singleWidth, View.MeasureSpec.EXACTLY),
                    View.MeasureSpec.makeMeasureSpec(singleHeight, View.MeasureSpec.EXACTLY))
            measureHeight = singleHeight * rows + gap * (rows - 1) + paddingTop + paddingBottom
            setMeasuredDimension(sizeWidth, measureHeight)
        }

    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        if (!isFirstLayout)
            layoutChildrenView()

        isFirstLayout = false
    }

    private fun layoutChildrenView() {
        Log.e(TAG, "layoutChildrenView")

        if (adapter == null || adapter!!.getCount() == 0) {
            return
        }
        val childrenCount = adapter!!.getCount()
        for (i in 0..childrenCount - 1) {
            val position = findPosition(i)
            val left = (singleWidth + gap) * position[1] + paddingLeft
            val top = (singleHeight + gap) * position[0] + paddingTop
            val right = left + singleWidth
            val bottom = top + singleHeight
            val childrenView = getChildAt(i) as ImageView
            if (childrenCount == 1) {
                //只有一张图片
                childrenView.scaleType = ImageView.ScaleType.FIT_CENTER
            } else {
                childrenView.scaleType = ImageView.ScaleType.CENTER_CROP
            }

            val itemPosition = i
            childrenView.setOnClickListener {
                if (onItemClickListerner != null) {
                    onItemClickListerner!!.onClick(itemPosition)
                }
            }
            childrenView.layout(left, top, right, bottom)
        }
    }


    private fun findPosition(childNum: Int): IntArray {
        val position = IntArray(2)
        for (i in 0..rows - 1) {
            for (j in 0..columns - 1) {
                if (i * columns + j == childNum) {
                    position[0] = i//行
                    position[1] = j//列
                    break
                }
            }
        }
        return position
    }

    /**
     * 根据图片个数确定行列数量
     * 对应关系如下
     * num  row column
     * 1       1    1
     * 2       1    2
     * 3-4     2    2
     * 5-6     2    3
     * 7-9     3    3
     * @param length
     */
    private fun generateChildrenLayout(length: Int) {
        if (length == 1) {
            rows = 1
            columns = 1
        } else if (length <= 4) {
            rows = 2
            columns = 2
        } else if (length <= 6) {
            rows = 2
            columns = 3
        } else {
            rows = 3
            columns = 3
        }

        singleWidth = (totalWidth - gap * (columns - 1)) / columns
        singleHeight = singleWidth
    }

    fun setOnItemClickListener(onItemClickListerner: OnItemClickListener) {
        this.onItemClickListerner = onItemClickListerner
    }

    companion object {

        /**
         * dp to px
         */
        fun dip2px(context: Context, dpValue: Float): Int {
            val scale = context.resources.displayMetrics.density
            return (dpValue * scale + 0.5f).toInt()
        }
    }
}

NineGridAdapter

NineGridAdapter图片的数据适配器

abstract class NineGridAdapter(protected var context: Context, protected var list: List) {

    abstract fun getCount(): Int

    abstract fun getUrl(positopn: Int): String

    abstract fun getItemId(position: Int): Long

    abstract fun getView(i: Int): View
}

使用方法

  • 首先继承NineGridAdapter

    class NineGridsAdapter(context: Context, list: MutableList<String>) :
        NineGridAdapter(context, list) {
    
    override fun getCount(): Int {
        return if (list == null) 0 else list.size
    }
    
    override fun getUrl(positopn: Int): String {
        return list?.get(positopn)
    }
    
    override fun getItemId(position: Int): Long {
        return position.toLong()
    }
    
    override fun getView(i: Int): View {
        val iv = ImageView(context)
        iv.setScaleType(ImageView.ScaleType.CENTER_CROP)
        iv.setBackgroundColor(context.resources.getColor(R.color.color_gray_light))
        Glide.with(context).load(getUrl(i)).into(iv)
        return iv
    }
    

    }

  • 设置Adapter显示数据,添加点击响应事件

    val imageAdapter = NineGridsAdapter(mContext!!, imageList)
            mMvpView?.picLayout?.setAdapter(imageAdapter)
            mMvpView?.picLayout?.setOnItemClickListener(object : OnItemClickListener {
                override fun onClick(vararg args: Int) {
                    clickImage(args[0])
                }
            })
    

在此,我们的九宫格图片显示控件就制作完毕了,Enjoy It! 最后我们的实际显示效果图如下:
最终效果图

<h3>附:点击回掉接口</h3>

文中使用的OnItemClickListener是一个自定义的点击回掉接口,附上代码

interface OnItemClickListener {
    fun onClick(vararg args: Int)
}
Search by:GoogleBingBaidu