我想一次在图像中添加几个引脚。此图像可以缩放和滚动。但是,当我添加许多引脚(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
}
}
我理解-原因主要不在循环中,而是在"BitmapFactory.decodeResource";。这是我们从磁盘上读取文件-这是一个长操作,如果我们把它保存在内存中,而不从资源中一次又一次地解码,一切都会好起来的。