Android九宫格,1张图到9张图适配;图片自定义UI

根据链接地址显示对应图片,可拓展显示本地图片,点击放大图片,点击缩小,左右滑显示下一张图片

Kotlin 复制代码
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import com.bumptech.glide.Glide

/**
 * 九宫格图片
 * @Author: guanlinlin
 * @Date: 2025/12/02 18:37
 */
class NineGridPictureView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    private val imageViews = mutableListOf<ImageView>()
    private var imageUrls = listOf<String>()
    private var interactionListener: OnImageInteractionListener? = null
    private var isZoomMode = false
    private var currentZoomPosition = 0

    init {
        // 初始化9个ImageView
        for (i in 0 until 9) {
            val imageView = ImageView(context)
            imageView.scaleType = ImageView.ScaleType.CENTER_CROP
            imageViews.add(imageView)
        }
    }

    // 在 NineGridPictureView 类中添加
    interface OnImageInteractionListener {
        fun onImageClicked(position: Int)
        fun onImageSwitched(fromPosition: Int, toPosition: Int)
        fun onVerticalSwipe(direction: SwipeDirection)
    }

    enum class SwipeDirection {
        UP, DOWN
    }

    fun setOnImageInteractionListener(listener: OnImageInteractionListener) {
        this.interactionListener = listener
    }

    /**
     * 设置图片URL列表并更新显示
     */
    fun setImageUrls(urls: List<String>) {
        imageUrls = urls.take(9) // 最多取9张图
        if (measuredWidth == 0 && measuredHeight == 0) {
            // 如果还没有测量完成,等待测量完成后更新布局
            post {
                updateLayout()
            }
        } else {
            updateLayout()
        }
    }

    /**
     * 根据图片数量更新布局
     */
    private fun updateLayout() {
        // 清除所有子视图
        removeAllViews()

        when (imageUrls.size) {
            0 -> return
            1 -> showSingleImage()
            2 -> showTwoImages()
            3 -> showThreeImages()
            4 -> showFourImages()
            5 -> showFiveImages()  // 新增5张图片的专门布局
            6 -> showSixImages()   // 新增6张图片的专门布局
            in 7..9 -> showMultipleImages()
        }
    }

    /**
     * 显示单张图片 - 占满全屏
     */
    private fun showSingleImage() {
        val imageView = imageViews[0]
        val layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
        layoutParams.width = measuredWidth
        layoutParams.height = measuredHeight
        addView(imageView, layoutParams)
        loadImage(imageView, imageUrls[0], 0)

        // 设置初始缩放值以便动画效果
        if (isZoomMode) {
            imageView.scaleX = 0.8f
            imageView.scaleY = 0.8f
            imageView.animate()
                .scaleX(1f)
                .scaleY(1f)
                .setDuration(200)
                .start()
        }
    }

    /**
     * 显示两张图片 - 左右排列,间隔1dp
     */
    private fun showTwoImages() {
        val spacing = dpToPx(1f).toInt()
        val width = (measuredWidth - spacing) / 2
        val height = measuredHeight

        // 左侧图片
        val leftParams = LayoutParams(width.toInt(), height)
        addView(imageViews[0], leftParams)
        loadImage(imageViews[0], imageUrls[0], 0)  // 添加位置参数

        // 右侧图片
        val rightParams = LayoutParams(width.toInt(), height)
        rightParams.leftMargin = width.toInt() + spacing
        addView(imageViews[1], rightParams)
        loadImage(imageViews[1], imageUrls[1], 1)  // 添加位置参数
    }

    /**
     * 显示三张图片 - 上面两张,下面一张居中,间隔1dp
     */
    private fun showThreeImages() {
        val spacing = dpToPx(1f).toInt()

        // 计算每张图片的尺寸,保持正方形
        val itemSize = (measuredWidth - spacing) / 2
        val itemWidth = itemSize
        val itemHeight = itemSize

        // 上面两张图片在同一行
        val topLeftParams = LayoutParams(itemWidth.toInt(), itemHeight.toInt())
        addView(imageViews[0], topLeftParams)
        loadImage(imageViews[0], imageUrls[0], 0)

        val topRightParams = LayoutParams(itemWidth.toInt(), itemHeight.toInt())
        topRightParams.leftMargin = itemWidth.toInt() + spacing
        addView(imageViews[1], topRightParams)
        loadImage(imageViews[1], imageUrls[1], 1)

        // 下面一张图片居中显示
        val bottomParams = LayoutParams(itemWidth.toInt(), itemHeight.toInt())
        bottomParams.topMargin = itemHeight.toInt() + spacing
        bottomParams.leftMargin = itemWidth.toInt() / 2
        addView(imageViews[2], bottomParams)
        loadImage(imageViews[2], imageUrls[2], 2)
    }

    /**
     * 显示四张图片 - 上下各两张,间隔1dp
     */
    private fun showFourImages() {
        val spacing = dpToPx(1f).toInt()
        val itemWidth = (measuredWidth - spacing) / 2
        val itemHeight = (measuredHeight - spacing) / 2

        // 第一行
        val params1 = LayoutParams(itemWidth.toInt(), itemHeight.toInt())
        addView(imageViews[0], params1)
        loadImage(imageViews[0], imageUrls[0], 0)

        val params2 = LayoutParams(itemWidth.toInt(), itemHeight.toInt())
        params2.leftMargin = itemWidth.toInt() + spacing
        addView(imageViews[1], params2)
        loadImage(imageViews[1], imageUrls[1], 1)

        // 第二行
        val params3 = LayoutParams(itemWidth.toInt(), itemHeight.toInt())
        params3.topMargin = itemHeight.toInt() + spacing
        addView(imageViews[2], params3)
        loadImage(imageViews[2], imageUrls[2], 2)

        val params4 = LayoutParams(itemWidth.toInt(), itemHeight.toInt())
        params4.topMargin = itemHeight.toInt() + spacing
        params4.leftMargin = itemWidth.toInt() + spacing
        addView(imageViews[3], params4)
        loadImage(imageViews[3], imageUrls[3], 3)
    }

    /**
     * 显示五张图片 - 上面三张,下面两张,间隔1dp
     */
    private fun showFiveImages() {
        val spacing = dpToPx(1f).toInt()

        // 上层三张图片
        val topItemWidth = (measuredWidth - 2 * spacing) / 3
        val topItemHeight = topItemWidth

        // 第一行三张图片
        for (i in 0 until 3) {
            val params = LayoutParams(topItemWidth.toInt(), topItemHeight.toInt())
            params.leftMargin = i * (topItemWidth.toInt() + spacing)
            addView(imageViews[i], params)
            loadImage(imageViews[i], imageUrls[i], i)
        }

        // 下层两张图片
        val bottomItemWidth = (measuredWidth - spacing) / 2
        val bottomItemHeight = bottomItemWidth
        val topMargin = topItemHeight.toInt() + spacing

        // 下面两张图片
        for (i in 3 until 5) {
            val index = i - 3  // 转换为下层的索引(0,1)
            val params = LayoutParams(bottomItemWidth.toInt(), bottomItemHeight.toInt())
            params.topMargin = topMargin
            params.leftMargin = index * (bottomItemWidth.toInt() + spacing)
            addView(imageViews[i], params)
            loadImage(imageViews[i], imageUrls[i], i)
        }
    }

    /**
     * 显示六张图片 - 上面三张,下面三张,间隔1dp
     */
    private fun showSixImages() {
        val spacing = dpToPx(1f).toInt()

        // 每行三张图片
        val itemWidth = (measuredWidth - 2 * spacing) / 3
        val itemHeight = itemWidth

        // 第一行三张图片
        for (i in 0 until 3) {
            val params = LayoutParams(itemWidth.toInt(), itemHeight.toInt())
            params.leftMargin = i * (itemWidth.toInt() + spacing)
            addView(imageViews[i], params)
            loadImage(imageViews[i], imageUrls[i], i)
        }

        // 第二行三张图片
        val topMargin = itemHeight.toInt() + spacing

        for (i in 3 until 6) {
            val index = i - 3  // 转换为第二行的索引(0,1,2)
            val params = LayoutParams(itemWidth.toInt(), itemHeight.toInt())
            params.topMargin = topMargin
            params.leftMargin = index * (itemWidth.toInt() + spacing)
            addView(imageViews[i], params)
            loadImage(imageViews[i], imageUrls[i], i)
        }
    }

    /**
     * 显示多张图片(6-9张)- 九宫格布局,间隔1dp
     */
    private fun showMultipleImages() {
        val spacing = dpToPx(1f).toInt()
        val rows = 3
        val cols = 3
        val itemWidth = (measuredWidth - (cols - 1) * spacing) / cols
        val itemHeight = itemWidth // 保持正方形,确保长宽一致

        for (i in imageUrls.indices) {
            val row = i / cols
            val col = i % cols

            val params = LayoutParams(itemWidth.toInt(), itemHeight.toInt())
            params.leftMargin = col * (itemWidth.toInt() + spacing)
            params.topMargin = row * (itemHeight.toInt() + spacing)

            addView(imageViews[i], params)
            loadImage(imageViews[i], imageUrls[i], i)
        }
    }

    /**
     * 使用Glide加载图片
     */
    private fun loadImage(imageView: ImageView, url: String, position: Int) {
        Glide.with(context)
            .load(url)
            .into(imageView)

        // 初始化缩放值
        imageView.scaleX = 1f
        imageView.scaleY = 1f
        imageView.alpha = 1f

        imageView.setOnClickListener {
            if (!isZoomMode) {
                enterZoomMode(position)
                interactionListener?.onImageClicked(position)
            } else {
                if (currentZoomPosition == position) {
                    exitZoomMode()
                } else {
                    switchZoomImage(position)
                    interactionListener?.onImageSwitched(currentZoomPosition, position)
                }
            }
        }
        imageView.setOnTouchListener(object : OnTouchListener {
            // 添加触摸事件处理
            private var startX = 0f
            private var startY = 0f
            private val SWIPE_THRESHOLD = 100
            override fun onTouch(v: View, event: MotionEvent): Boolean {
                if (!isZoomMode) return false

                when (event.action) {
                    MotionEvent.ACTION_DOWN -> {
                        startX = event.x
                        startY = event.y
                        return true
                    }

                    MotionEvent.ACTION_UP -> {
                        val endX = event.x
                        val endY = event.y
                        val diffX = endX - startX
                        val diffY = endY - startY

                        if (Math.abs(diffY) > Math.abs(diffX)) {
                            // 垂直滑动
                            if (Math.abs(diffY) > SWIPE_THRESHOLD) {
                                if (diffY > 0) {
                                    interactionListener?.onVerticalSwipe(SwipeDirection.DOWN)
                                } else {
                                    interactionListener?.onVerticalSwipe(SwipeDirection.UP)
                                }
                                return true
                            }
                        } else {
                            // 水平滑动
                            if (Math.abs(diffX) > SWIPE_THRESHOLD) {
                                if (diffX > 0 && currentZoomPosition > 0) {
                                    // 向右滑动,显示前一张图片
                                    Log.d("ceshi", "向右滑动,显示前一张图片: $currentZoomPosition")
                                    switchZoomImage(currentZoomPosition - 1)
                                    interactionListener?.onImageSwitched(currentZoomPosition, currentZoomPosition - 1)
                                    return true
                                } else if (diffX < 0 && currentZoomPosition < imageUrls.size - 1) {
                                    // 向左滑动,显示后一张图片
                                    Log.d("ceshi", "向左滑动,显示后一张图片: $currentZoomPosition")
                                    switchZoomImage(currentZoomPosition + 1)
                                    interactionListener?.onImageSwitched(currentZoomPosition, currentZoomPosition + 1)
                                    return true
                                }
                            }else{
                                if (!isZoomMode) {
                                    enterZoomMode(position)
                                    interactionListener?.onImageClicked(position)
                                } else {
                                    if (currentZoomPosition == position) {
                                        exitZoomMode()
                                    } else {
                                        switchZoomImage(position)
                                        interactionListener?.onImageSwitched(currentZoomPosition, position)
                                    }
                                }
                            }
                        }
                    }
                }
                return false
            }
        })
    }

    // 进入缩放模式
    private fun enterZoomMode(position: Int) {
        isZoomMode = true
        currentZoomPosition = position
        parent?.requestDisallowInterceptTouchEvent(true)

        for (i in imageViews.indices) {
            if (i == position) {
                // 将选中图片放大至整个视图,并添加动画效果
                val params = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
                params.width = measuredWidth
                params.height = measuredHeight
                imageViews[i].layoutParams = params

                // 添加缩放动画
                imageViews[i].animate()
                    .scaleX(1f)
                    .scaleY(1f)
                    .setDuration(200)
                    .start()
            } else {
                // 隐藏其他图片
                imageViews[i].visibility = GONE
            }
        }
    }

    // 退出缩放模式
    private fun exitZoomMode() {
        isZoomMode = false

        // 先执行缩小动画
        val currentImageView = imageViews[currentZoomPosition]
        currentImageView.animate()
            .scaleX(0.8f)
            .scaleY(0.8f)
            .setDuration(150)
            .withEndAction {
                // 动画结束后恢复初始状态并更新布局
                for (i in imageViews.indices) {
                    imageViews[i].visibility = VISIBLE
                    imageViews[i].scaleX = 1f
                    imageViews[i].scaleY = 1f
                }
                updateLayout()
            }
            .start()
    }

    // 切换缩放图片
    private fun switchZoomImage(newPosition: Int) {
        val oldPosition = currentZoomPosition
        currentZoomPosition = newPosition

        // 对旧图片执行缩小动画
        val oldImageView = imageViews[oldPosition]
        oldImageView.animate()
            .scaleX(0.8f)
            .scaleY(0.8f)
            .alpha(0.5f)
            .setDuration(150)
            .start()

        // 对新图片执行放大动画
        val newImageView = imageViews[newPosition]
        newImageView.visibility = VISIBLE
        newImageView.scaleX = 0.8f
        newImageView.scaleY = 0.8f
        newImageView.alpha = 0.5f

        val params = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
        params.width = measuredWidth
        params.height = measuredHeight
        newImageView.layoutParams = params

        newImageView.animate()
            .scaleX(1f)
            .scaleY(1f)
            .alpha(1f)
            .setDuration(150)
            .withEndAction {
                // 动画结束后隐藏旧图片
                oldImageView.visibility = GONE
                oldImageView.scaleX = 1f
                oldImageView.scaleY = 1f
                oldImageView.alpha = 1f
            }
            .start()
    }

    /**
     * DP转PX
     */
    private fun dpToPx(dp: Float): Float {
        return dp * resources.displayMetrics.density
    }
}
相关推荐
踏雪羽翼18 小时前
android TextView实现文字字符不同方向显示
android·自定义view·textview方向·文字方向·textview文字显示方向·文字旋转·textview文字旋转
lxysbly18 小时前
安卓玩MRP冒泡游戏:模拟器下载与使用方法
android·游戏
夏沫琅琊20 小时前
Android 各类日志全面解析(含特点、分析方法、实战案例)
android
程序员JerrySUN21 小时前
OP-TEE + YOLOv8:从“加密权重”到“内存中解密并推理”的完整实战记录
android·java·开发语言·redis·yolo·架构
TeleostNaCl1 天前
Android | 启用 TextView 跑马灯效果的方法
android·经验分享·android runtime
TheNextByte11 天前
Android USB文件传输无法使用?5种解决方法
android
quanyechacsdn1 天前
Android Studio创建库文件用jitpack构建后使用implementation方式引用
android·ide·kotlin·android studio·implementation·android 库文件·使用jitpack
程序员陆业聪1 天前
聊聊2026年Android开发会是什么样
android
编程大师哥1 天前
Android分层
android
极客小云1 天前
【深入理解 Android 中的 build.gradle 文件】
android·安卓·安全架构·安全性测试