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
    }
}
相关推荐
_李小白2 小时前
【Android FrameWork】延伸阅读:Jetpack Compose
android
私人珍藏库2 小时前
[Android] B站第三方电视TVapp BV_0.3.10
android
Arenaschi2 小时前
安卓显示翻转
android·网络·人工智能·笔记·其他
解局易否结局2 小时前
鸿蒙UI开发中Flutter的现状与鸿蒙系统UI生态未来方向
flutter·ui·harmonyos
晚霞的不甘2 小时前
Flutter + OpenHarmony 设计系统实战:构建统一、美观、无障碍的跨端 UI 体系
flutter·ui
黑臂麒麟2 小时前
DevUI modal 弹窗表单联动实战:表格编辑功能完整实现
前端·javascript·ui·angular.js
晚霞的不甘3 小时前
鸿蒙(HarmonyOS)UI 美化实战:打造美观、响应式的应用界面
ui·华为·harmonyos
晚霞的不甘3 小时前
鸿蒙(HarmonyOS)应用开发深度入门:ArkTS 语法、UI 构建与状态管理详解
ui·华为·harmonyos