You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Kotlin:缩放后保持附加ImageView在可缩放ImageView固定位置

Android中TouchImageView缩放时同步箭头ImageView位置与尺寸的解决方案

我用MikeOrtiz的TouchImageView实现了可缩放的ImageView,现在需要添加一个红色箭头ImageView指向山顶,要求主图缩放时箭头始终固定在山顶位置,且尺寸适配缩放比例。但目前缩放后箭头的位置和尺寸都无法跟随变化,试过多种方案要么无效要么不适合Kotlin,求解决办法。

当前XML布局代码:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="230dp"
        android:layout_gravity="center">

        <com.ortiz.touchview.TouchImageView
            android:id="@+id/image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/black"
            android:src="@drawable/mountain"/>

        <ImageView
            android:id="@+id/arrowImageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/arrow" />

    </androidx.cardview.widget.CardView>

</FrameLayout>

问题现象:

  • 未缩放状态:箭头位置正确,指向山顶
  • 已缩放状态:箭头位置偏移,无法匹配山顶位置,且尺寸未适配缩放比例

解决方案(Kotlin实现)

核心思路是监听TouchImageView的缩放/平移变换,通过图片矩阵将山顶的原始坐标转换为当前屏幕坐标,同时反向调整箭头尺寸,保证视觉上的一致性。

步骤1:修改布局

移除箭头的translationX/Y属性,添加唯一ID方便代码获取。

步骤2:Activity中实现同步逻辑

import android.graphics.Matrix
import android.graphics.PointF
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import com.ortiz.touchview.TouchImageView

class MainActivity : AppCompatActivity() {
    // 山顶在原始图片中的相对坐标(需根据你的图片实际调整,范围0-1)
    private val peakRelativeX = 0.6f
    private val peakRelativeY = 0.2f
    // 箭头的原始尺寸(单位:dp)
    private val originalArrowWidth = 48f
    private val originalArrowHeight = 110f

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val touchImageView = findViewById<TouchImageView>(R.id.image)
        val arrowImageView = findViewById<ImageView>(R.id.arrowImageView)

        // 将dp转换为像素
        val density = resources.displayMetrics.density
        val arrowWidthPx = originalArrowWidth * density
        val arrowHeightPx = originalArrowHeight * density

        // 监听TouchImageView的变换事件
        touchImageView.setOnTouchImageViewListener(object : TouchImageView.OnTouchImageViewListener {
            override fun onMove() {
                val drawable = touchImageView.drawable ?: return
                val imgWidth = drawable.intrinsicWidth.toFloat()
                val imgHeight = drawable.intrinsicHeight.toFloat()
                val matrix = touchImageView.imageMatrix

                // 计算山顶在原始图片中的绝对坐标
                val peakOriginalX = imgWidth * peakRelativeX
                val peakOriginalY = imgHeight * peakRelativeY

                // 将原始坐标转换为当前屏幕上的坐标
                val screenPoint = PointF()
                matrix.mapPoints(screenPoint.array(), floatArrayOf(peakOriginalX, peakOriginalY))

                // 获取当前缩放比例
                val matrixValues = FloatArray(9)
                matrix.getValues(matrixValues)
                val currentScale = (matrixValues[Matrix.MSCALE_X] + matrixValues[Matrix.MSCALE_Y]) / 2

                // 调整箭头尺寸:反向缩放,保证视觉大小不变
                val newArrowWidth = arrowWidthPx / currentScale
                val newArrowHeight = arrowHeightPx / currentScale

                // 设置箭头位置:让箭头中心对准山顶坐标
                val arrowLeft = screenPoint.x - newArrowWidth / 2
                val arrowTop = screenPoint.y - newArrowHeight / 2

                // 更新箭头布局参数
                val layoutParams = arrowImageView.layoutParams.apply {
                    width = newArrowWidth.toInt()
                    height = newArrowHeight.toInt()
                }
                arrowImageView.layoutParams = layoutParams

                // 更新箭头位置
                arrowImageView.x = arrowLeft
                arrowImageView.y = arrowTop
            }
        })

        // 初始化箭头位置(布局完成后执行)
        touchImageView.post {
            val drawable = touchImageView.drawable ?: return@post
            val imgWidth = drawable.intrinsicWidth.toFloat()
            val imgHeight = drawable.intrinsicHeight.toFloat()

            val peakOriginalX = imgWidth * peakRelativeX
            val peakOriginalY = imgHeight * peakRelativeY

            // 初始状态无缩放,直接计算位置
            val arrowLeft = peakOriginalX - arrowWidthPx / 2
            val arrowTop = peakOriginalY - arrowHeightPx / 2

            arrowImageView.x = arrowLeft
            arrowImageView.y = arrowTop
            arrowImageView.layoutParams.apply {
                width = arrowWidthPx.toInt()
                height = arrowHeightPx.toInt()
            }
        }
    }
}

关键说明

  1. 相对坐标:用peakRelativeX/Y(0-1范围)表示山顶在原始图片中的位置,避免因图片尺寸差异导致的适配问题。
  2. 矩阵转换:通过TouchImageView的imageMatrix将原始图片坐标转换为当前屏幕坐标,确保箭头始终跟随山顶。
  3. 反向缩放:箭头尺寸随图片缩放比例反向调整,保证缩放后箭头在视觉上的大小不变。
  4. 初始化处理:通过post方法在布局完成后设置初始位置,避免因drawable未加载导致的空指针问题。

内容的提问来源于stack exchange,提问作者GamerBlind

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 20:10:39