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

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
}
}