【问题标题】:How to draw several pins at zoomable and draggable image and avoid freezes when zooming and dragging?如何在可缩放和可拖动的图像上绘制多个图钉并避免在缩放和拖动时冻结?
【发布时间】:2021-04-27 23:57:19
【问题描述】:

我想一次向图像添加几个图钉。此图像可以缩放和滚动。但是当我添加许多图钉(10 个或更多)时,当我缩放或滚动它时,图像开始冻结。 我正在“onLongPress”中添加新的引脚:

data class CoordinatesEntity(var x: Float, var y: Float)

class ScaleImageView : AppCompatImageView, View.OnTouchListener {
...

private val pinsCoordinates = ArrayList<CoordinatesEntity>()
private var invertedPinsCoordinates = ArrayList<CoordinatesEntity>()
private val pinCountersMap = HashMap<String, String>()

...

mDetector = GestureDetector(savedContext,
                object : GestureDetector.SimpleOnGestureListener() {
                    override fun onDoubleTap(e: MotionEvent): Boolean {
                        ...
                    }

                    override fun onLongPress(e: MotionEvent) {
                        super.onLongPress(e)
                        pinsCoordinates.add(CoordinatesEntity(e.x, e.y))

                        val touchCoords = floatArrayOf(e.x, e.y)
                        val matrixInverse = Matrix()
                        mMatrix!!.invert(matrixInverse) // XY to UV mapping matrix.
                        matrixInverse.mapPoints(touchCoords) // Touch point in bitmap-U,V coords.

                        val entity = CoordinatesEntity(touchCoords[0], touchCoords[1])
                        invertedPinsCoordinates.add(entity)

                        pinCountersMap["${entity.x}${entity.y}"] = (++pinCounter).toString()

                        invalidate()
                    }
                })

...
}

并在“onDraw”中绘制我所有的图钉。我的 pin - 它是图像加文本,我通过 pin 以编程方式将其添加到位图:

class ScaleImageView : AppCompatImageView, View.OnTouchListener {

...

private fun getPinForCoordinates(coordinates: CoordinatesEntity): Bitmap {
    val bm: Bitmap = BitmapFactory.decodeResource(
            context.resources, R.drawable.ic_pin_raster_no_middle_32
    ).copy(Bitmap.Config.ARGB_8888, true)
    val paint = Paint()
    paint.style = Paint.Style.FILL
    paint.color = Color.parseColor("#3874A4")
    paint.textSize = 20f

    val text = pinCountersMap["${coordinates.x}${coordinates.y}"] ?: ""
    val canvas = Canvas(bm)
    val xStart = when (text.length) {
        1 -> bm.width / 2f - 6
        2 -> bm.width / 2f - 12
        else -> bm.width / 2f - 36
    }
    canvas.drawText(text, xStart, bm.height / 2f, paint)

    return BitmapDrawable(context.resources, bm).bitmap
}

override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        invertedPinsCoordinates.forEach {
            val marker = getPinForCoordinates(it)
            val matrixMarker = Matrix()
            matrixMarker.setTranslate(it.x, it.y)
            matrixMarker.postConcat(mMatrix)
            canvas.drawBitmap(marker, matrixMarker, null)
        }
    }

...
}

我认为这是因为“onDraw”内部的循环而冻结。但我不知道如何一次绘制多个图钉并保持它们可缩放和可滚动的图像。请帮忙!

我的全班:

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.util.AttributeSet
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import androidx.appcompat.widget.AppCompatImageView
import com.signalsense.signalsenseapp.R
import com.signalsense.signalsenseapp.interactors.image_interactor.ImageInteractor
import com.signalsense.signalsenseapp.interactors.image_interactor.ImageInteractorImpl
import com.signalsense.signalsenseapp.mvp.models.entities.CoordinatesEntity
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt

class ScaleImageView : AppCompatImageView, View.OnTouchListener {
    private val dirtyRect = RectF()
    private val savedContext: Context
    private val maxScale = 2f
    private val matrixValues = FloatArray(9)
    private var lastTouchX = 0f
    private var lastTouchY = 0f
    private var paint = Paint()
    var tag = "ScaleImageView"

    // display width height.
    private var mWidth = 0
    private var mHeight = 0
    private var mIntrinsicWidth = 0
    private var mIntrinsicHeight = 0
    private var mScale = 0f
    private var mMinScale = 1f
    private var mPrevDistance = 0f
    private var isScaling = false
    private var mPrevMoveX = 0
    private var mPrevMoveY = 0
    private var mDetector: GestureDetector? = null
    private val pinsCoordinates = ArrayList<CoordinatesEntity>()
    private var invertedPinsCoordinates = ArrayList<CoordinatesEntity>()
    private var pinCounter = 0
    private val pinCountersMap = HashMap<String, String>()

    constructor(context: Context, attr: AttributeSet?) : super(context, attr) {
        savedContext = context
        initialize()
    }

    constructor(context: Context) : super(context) {
        savedContext = context
        initialize()
    }

    private fun resetDirtyRect(eventX: Float, eventY: Float) {
        dirtyRect.left = min(lastTouchX, eventX)
        dirtyRect.right = max(lastTouchX, eventX)
        dirtyRect.top = min(lastTouchY, eventY)
        dirtyRect.bottom = max(lastTouchY, eventY)
    }

    override fun setImageBitmap(bm: Bitmap) {
        super.setImageBitmap(bm)
        initialize()
    }

    override fun setImageResource(resId: Int) {
        super.setImageResource(resId)
        initialize()
    }

    private fun initialize() {
        this.scaleType = ScaleType.MATRIX
        mMatrix = Matrix()
        val d = drawable
        paint.isAntiAlias = true
        paint.color = Color.RED
        paint.style = Paint.Style.STROKE
        paint.strokeJoin = Paint.Join.ROUND
        paint.strokeWidth = STROKE_WIDTH
        if (d != null) {
            mIntrinsicWidth = d.intrinsicWidth
            mIntrinsicHeight = d.intrinsicHeight
            setOnTouchListener(this)
        }
        mDetector = GestureDetector(savedContext,
                object : GestureDetector.SimpleOnGestureListener() {
                    override fun onDoubleTap(e: MotionEvent): Boolean {
                        maxZoomTo(e.x.toInt(), e.y.toInt())
                        cutting()
                        return super.onDoubleTap(e)
                    }

                    override fun onLongPress(e: MotionEvent) {
                        super.onLongPress(e)
                        pinsCoordinates.add(CoordinatesEntity(e.x, e.y))

                        val touchCoords = floatArrayOf(e.x, e.y)
                        val matrixInverse = Matrix()
                        mMatrix!!.invert(matrixInverse) // XY to UV mapping matrix.
                        matrixInverse.mapPoints(touchCoords) // Touch point in bitmap-U,V coords.

                        val entity = CoordinatesEntity(touchCoords[0], touchCoords[1])
                        invertedPinsCoordinates.add(entity)

                        pinCountersMap["${entity.x}${entity.y}"] = (++pinCounter).toString()

                        invalidate()
                    }
                })
    }

    override fun setFrame(l: Int, t: Int, r: Int, b: Int): Boolean {
        mWidth = r - l
        mHeight = b - t
        mMatrix!!.reset()
        val rNorm = r - l
        mScale = rNorm.toFloat() / mIntrinsicWidth.toFloat()
        var paddingHeight = 0
        var paddingWidth = 0
        // scaling vertical
        if (mScale * mIntrinsicHeight > mHeight) {
            mScale = mHeight.toFloat() / mIntrinsicHeight.toFloat()
            mMatrix!!.postScale(mScale, mScale)
            paddingWidth = (r - mWidth) / 2
            paddingHeight = 0
            // scaling horizontal
        } else {
            mMatrix!!.postScale(mScale, mScale)
            paddingHeight = (b - mHeight) / 2
            paddingWidth = 0
        }
        mMatrix!!.postTranslate(paddingWidth.toFloat(), paddingHeight.toFloat())
        imageMatrix = mMatrix
        mMinScale = mScale
        Log.i(tag, "MinScale: $mMinScale")
        zoomTo(mScale, mWidth / 2, mHeight / 2)
        cutting()
        return super.setFrame(l, t, r, b)
    }

    private fun getValue(matrix: Matrix?, whichValue: Int): Float {
        matrix!!.getValues(matrixValues)
        return matrixValues[whichValue]
    }

    private val scale: Float
        get() = getValue(mMatrix, Matrix.MSCALE_X)
    private val translateX: Float
        get() = getValue(mMatrix, Matrix.MTRANS_X)
    private val translateY: Float
        get() = getValue(mMatrix, Matrix.MTRANS_Y)

    private fun maxZoomTo(x: Int, y: Int) {
        if (mMinScale != getCalculatedScale() && getCalculatedScale() - mMinScale > 0.1f) {
            // threshold 0.1f
            val scale = mMinScale / getCalculatedScale()
            zoomTo(scale, x, y)
        } else {
            val scale = maxScale / getCalculatedScale()
            zoomTo(scale, x, y)
        }
    }

    private fun zoomTo(scale: Float, x: Int, y: Int) {
        if (getCalculatedScale() * scale < mMinScale) {
            return
        }
        if (scale >= 1 && getCalculatedScale() * scale > maxScale) {
            return
        }
        Log.i(tag, "Scale: $scale, multiplied: ${scale * scale}")
        mMatrix!!.postScale(scale, scale)
        // move to center
        mMatrix!!.postTranslate(-(mWidth * scale - mWidth) / 2,
                -(mHeight * scale - mHeight) / 2)

        // move x and y distance
        mMatrix!!.postTranslate(-(x - mWidth / 2) * scale, 0f)
        mMatrix!!.postTranslate(0f, -(y - mHeight / 2) * scale)
        imageMatrix = mMatrix
    }

    private fun cutting() {
        val width = (mIntrinsicWidth * getCalculatedScale()).toInt()
        val height = (mIntrinsicHeight * getCalculatedScale()).toInt()
        imageWidth = width
        imageHeight = height
        if (translateX < -(width - mWidth)) {
            mMatrix!!.postTranslate(-(translateX + width - mWidth), 0f)
        }
        if (translateX > 0) {
            mMatrix!!.postTranslate(-translateX, 0f)
        }
        if (translateY < -(height - mHeight)) {
            mMatrix!!.postTranslate(0f, -(translateY + height - mHeight))
        }
        if (translateY > 0) {
            mMatrix!!.postTranslate(0f, -translateY)
        }
        if (width < mWidth) {
            mMatrix!!.postTranslate(((mWidth - width) / 2).toFloat(), 0f)
        }
        if (height < mHeight) {
            mMatrix!!.postTranslate(0f, ((mHeight - height) / 2).toFloat())
        }
        imageMatrix = mMatrix
    }

    private fun distance(x0: Float, x1: Float, y0: Float, y1: Float): Float {
        val x = x0 - x1
        val y = y0 - y1
        return sqrt((x * x + y * y).toDouble()).toFloat()
    }

    private fun dispDistance(): Float {
        return sqrt((mWidth * mWidth + mHeight * mHeight).toDouble()).toFloat()
    }

    fun clear() {
        path.reset()
        invalidate()
    }

    fun save() {
        val returnedBitmap = Bitmap.createBitmap(
                width,
                height,
                Bitmap.Config.ARGB_8888)
        val canvas = Canvas(returnedBitmap)
        draw(canvas)
        setImageBitmap(returnedBitmap)
    }

    @Suppress("DEPRECATION")
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (mDetector!!.onTouchEvent(event)) {
            return true
        }
        val touchCount = event.pointerCount
        when (event.action) {
            MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_1_DOWN, MotionEvent.ACTION_POINTER_2_DOWN -> {
                if (touchCount >= 2) {
                    val distance = distance(event.getX(0), event.getX(1),
                            event.getY(0), event.getY(1))
                    mPrevDistance = distance
                    isScaling = true
                } else {
                    mPrevMoveX = event.x.toInt()
                    mPrevMoveY = event.y.toInt()
                }
                if (touchCount >= 2 && isScaling) {
                    val dist = distance(event.getX(0), event.getX(1),
                            event.getY(0), event.getY(1))
                    var scale = (dist - mPrevDistance) / dispDistance()
                    mPrevDistance = dist
                    scale += 1f
                    scale *= scale
                    zoomTo(scale, mWidth / 2, mHeight / 2)
                    cutting()
                } else if (!isScaling) {
                    val distanceX = mPrevMoveX - event.x.toInt()
                    val distanceY = mPrevMoveY - event.y.toInt()
                    mPrevMoveX = event.x.toInt()
                    mPrevMoveY = event.y.toInt()
                    mMatrix!!.postTranslate(-distanceX.toFloat(), -distanceY.toFloat())
                    cutting()
                }
            }
            MotionEvent.ACTION_MOVE -> if (touchCount >= 2 && isScaling) {
                val dist = distance(event.getX(0), event.getX(1),
                        event.getY(0), event.getY(1))
                var scale = (dist - mPrevDistance) / dispDistance()
                mPrevDistance = dist
                scale += 1f
                scale *= scale
                zoomTo(scale, mWidth / 2, mHeight / 2)
                cutting()
            } else if (!isScaling) {
                val distanceX = mPrevMoveX - event.x.toInt()
                val distanceY = mPrevMoveY - event.y.toInt()
                mPrevMoveX = event.x.toInt()
                mPrevMoveY = event.y.toInt()
                mMatrix!!.postTranslate(-distanceX.toFloat(), -distanceY.toFloat())
                cutting()
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_POINTER_2_UP -> if (event.pointerCount <= 1) {
                isScaling = false
            }
        }
        return true
    }

    private fun getCalculatedScale() = getValue(mMatrix, Matrix.MSCALE_X)

    private fun getPinForCoordinates(coordinates: CoordinatesEntity): Bitmap {
        val bm: Bitmap = BitmapFactory.decodeResource(
                context.resources, R.drawable.ic_pin_raster_no_middle_32
        ).copy(Bitmap.Config.ARGB_8888, true)
        val paint = Paint()
        paint.style = Paint.Style.FILL
        paint.color = Color.parseColor("#3874A4")
        paint.textSize = 20f

        val text = pinCountersMap["${coordinates.x}${coordinates.y}"] ?: ""
        val canvas = Canvas(bm)
        val xStart = when (text.length) {
            1 -> bm.width / 2f - 6
            2 -> bm.width / 2f - 12
            else -> bm.width / 2f - 36
        }
        canvas.drawText(text, xStart, bm.height / 2f, paint)

        return BitmapDrawable(context.resources, bm).bitmap
    }

    @SuppressLint("DrawAllocation")
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        invertedPinsCoordinates.forEach {
            val marker = getPinForCoordinates(it)
            val matrixMarker = Matrix()
            matrixMarker.setTranslate(it.x, it.y)
            matrixMarker.postConcat(mMatrix)
            canvas.drawBitmap(marker, matrixMarker, null)
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouch(v: View, event: MotionEvent): Boolean {
        return super.onTouchEvent(event)
    }

    companion object {
        const val STROKE_WIDTH = 10f
        const val HALF_STROKE_WIDTH = STROKE_WIDTH / 2
        var path = Path()
        var imageHeight = 0
        var imageWidth = 0
        private var mMatrix: Matrix? = null
    }
}

【问题讨论】:

    标签: android imageview drawing


    【解决方案1】:

    我明白 - 原因主要不是在循环中,而是在“BitmapFactory.decodeResource”中。这是我们正在从磁盘读取文件 - 这是一个长时间的操作,如果我们将它保存在内存中,而不是一次又一次地从资源中解码,一切都会好起来的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-13
      • 2015-01-26
      • 1970-01-01
      相关资源
      最近更新 更多