OpenGL ES how to apply transformations from android.graphics.Matrix - android

I have a 3x3 android.graphics.Matrix, and i want to apply all transformation to a 4x4 OpenGL Matrix for 2D transformations only. So far I have manage to apply rotation and scaling I am using the example from the android team HERE to render a triangle. I used this class for generating the android.graphics.Matrix, from finger gestures made by the user for scale, move and translate transformations.
After that I attach the MatrixGestureDetector on the onTouchEvent from the View. In the MyGLSurfaceView class:
class MyGLSurfaceView : GLSurfaceView {
...
private val matrixGestureDetector = MatrixGestureDetector()
override fun onTouchEvent(e: MotionEvent): Boolean {
matrixGestureDetector.onTouchEvent(e)
requestRender()
return true
}
}
Then I used it to convert the android.graphics.Matrix to a OpenGL Matrix in the onDrawFrame method in
MyGLRenderer class
...
lateinit var matrixGestureDetector: MatrixGestureDetector
override fun onDrawFrame(unused: GL10) {
...
// get graphics matrix values
val m = FloatArray(9)
matrixGestureDetector.matrix.getValues(m)
// set rotation and scaling from graphics matrix to form new 4x4 OpenGL matrix
val openGLMatrix = floatArrayOf(
m[0], m[3], 0f, 0f,
m[1], m[4], 0f, 0f,
0f, 0f, 1f, 0f,
0f, 0f, 0f, 1f
)
Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, openGLMatrix, 0)
// draw shape, where scaling and rotation work
mTriangle.draw(scratch)
}
To apply the translation I have to add the m[2] and m[5] from the android.graphics.Matrix values and change the openGLMatrix to:
val openGLMatrix = floatArrayOf(
m[0], m[3], 0f, 0f,
m[1], m[4], 0f, 0f,
0f, 0f, 1f, 0f,
m[2], m[5], 0f, 1f
)
Now the problem is that the OpenGL viewbox size is formed by coordinates in range [-1,1], look at the image below:
But the translation X and Y values from the android.graphics.Matrix are not in that range, to do that I changed it to:
val scaleX: Float = m[android.graphics.Matrix.MSCALE_X]
val skewY: Float = m[android.graphics.Matrix.MSKEW_Y]
val translateX = m[android.graphics.Matrix.MTRANS_X]
val translateY = m[android.graphics.Matrix.MTRANS_Y]
val ratio = width.toFloat() / height
val openGLMatrix = floatArrayOf(
m[0], m[3], 0f, 0f,
m[1], m[4], 0f, 0f,
0f, 0f, 1f, 0f,
-ratio * (translateX / width * 2), -(translateY / height * 2), 0f, 1f
)
Now translation work, but scale and rotation are not done on the pivot point(center point of rotation between the two fingers). How to apply all the transformation and is there a example code for 2D transformations for finger gestures that I can find anywhere?

Well, I figure out that there is miscalculations in the conversion from Graphic coordinate system to a OpenGL coordinate system for the translation. Here is the code for getting the accurate translation in a OpenGL coordinate system, set as separate functions:
fun normalizeTranslateX(x: Float): Float {
val translateX = if (x < width / 2f) {
-1f + (x / (width / 2f))
} else {
(x - (width / 2f)) / (width / 2f)
}
return -translateX * OpenGLRenderer.NEAR * ratio
}
fun normalizeTranslateY(y: Float): Float {
val translateY = if (y < height / 2f) {
1f - (y / (height / 2f))
} else {
-(y - (height / 2f)) / (height / 2f)
}
return translateY * OpenGLRenderer.NEAR
}
I have also updated the whole finger gesture transformation class, for generating the OpenGL matrix, with the applied transformations from the finger gestures here is the class OpenGLFingerGestureTransformations.
To get the OpenGL matrix first create your own OpenGLMatrixGestureDetector object, using the same way as creating MatrixGestureDetector:
class MyGLSurfaceView : GLSurfaceView {
...
private val matrixGestureDetector = OpenGLMatrixGestureDetector()
override fun onTouchEvent(e: MotionEvent): Boolean {
matrixGestureDetector.onTouchEvent(e)
requestRender()
return true
}
}
Then in the MyGLRenderer class just generate the matrix with the method transform()
...
lateinit var matrixGestureDetector: OpenGLMatrixGestureDetector
private val transformedMatrixOpenGL: FloatArray = FloatArray(16)
override fun onDrawFrame(unused: GL10) {
...
// get OpenGL matrix with the applied transformations, from finger gestures
matrixGestureDetector.transform(mMVPMatrix, transformedMatrixOpenGL)
// draw shapes with apply transformations from finger gestures
mTriangle.draw(transformedMatrixOpenGL)
}
I have uploaded the full source code HERE.
Here is the final result:

Related

Android: 3D-transform an object

In an Android app I have an object that is created through a custom view. It basically has a rectangular shape. I want to apply a 3D transformation that makes it look like this:
In the draw() function I have tried various things like this:
override fun draw(canvas: Canvas?) {
super.draw(canvas)
val matrix = Matrix()
var pivX = myWidth / 2.0f
var pivY = myHeight /1.0f
matrix.setSkew(1f, 0f, pivX, pivY);
canvas?.withMatrix(matrix) {
drawPath(path(-150F), paint())
drawPath(path(150F), paint())
}
}
I couldn't make it work though. Where's my mistake? What am I missing?

Is there a convenient way to draw arc only using two points and curve radius in Android?

First of all
I searched for it for a long time, and I have already seen many questions including the two:
How to draw Arc between two points on the Canvas?
How to draw a curved line between 2 points on canvas?
Although they seem like the same question, I'm very sure they are not the same. In the first question, the center of the circle is known, and in the second, it draws a Bezier curve not an arc.
Description
Now we have two points A and B and the curve radius given, how to draw the arc as the image shows?
Since Path.arcTo's required arguments are RectF, startAngle and sweepAngle, there seems hardly a easy way.
My current solution
I now have my solution, I'll show that in the answer below.
Since the solution is so complex, I wonder if there is a easier way to solve it?
Probably there is no easier way. All what can do would be to refine your solution by geometrical approach. Since the center of circle is always on the perpendicular bisector of the chord, it's not required to solve so generalized equations.
By the way, it's not clear how you defined Clockwise/Counter-clockwise. Arc's winding direction should be determined independently of node-placements (=A, B's coordinates).
As is shown in the figure below, on the straight path from A to B, the center O is to be placed righthandside(CW) or lefthandside(CCW). That's all.
And, some more aspects to be altered:
It's better to calculate startAngle by atan2(). Because acos() has singularity at some points.
It's also possible to calculate sweepAngle with asin().
After all the code can be slightly simplified as follows.
#Throws(Exception::class)
private fun Path.arcFromTo2(
x1: Float, y1: Float, x2: Float, y2: Float, r: Float,
clockwise: Boolean = true
) {
val d = PointF((x2 - x1) * 0.5F, (y2 - y1) * 0.5F)
val a = d.length()
if (a > r) throw Exception()
val side = if (clockwise) 1 else -1
val oc = sqrt(r * r - a * a)
val ox = (x1 + x2) * 0.5F - side * oc * d.y / a
val oy = (y1 + y2) * 0.5F + side * oc * d.x / a
val startAngle = atan2(y1 - oy, x1 - ox) * 180F / Math.PI.toFloat()
val sweepAngle = side * 2.0F * asin(a / r) * 180F / Math.PI.toFloat()
arcTo(
ox - r, oy - r, ox + r, oy + r,
startAngle, sweepAngle,
false
)
}
1. find the center of the circle
This can be solved by a binary quadratic equation, as the image shows:
Though there are other solutions, anyway, now the position of circle center is known.
2. calculate start angle and sweep angle
According to the circle center, RectF is easy to know.
Now calculate startAngle and sweepAngle.
Via geometric methods, we can calculate the startAngle and sweepAngle:
val startAngle = acos((x1 - x0) / r) / Math.PI.toFloat() * 180
val endAngle = acos((x2 - x0) / r) / Math.PI.toFloat() * 180
val sweepAngle = endAngle - startAngle
In this case, x1 is the x-coordinary of point A, x2 if the x-coordinary of point B, and r is the curve radius of the arc. (There are possible results, the other is [-startAngle, startAngle - endAngle]. Choose one according to actual situation.)
Thus, we get all the required arguments for Path.arcTo method and we can draw the arc now.
3. kotlin code
the entire code of the help function:
/**
* Append the arc which is starting at ([x1], [y1]), ending at ([x2], [y2])
* and with the curve radius [r] to the path.
* The Boolean value [clockwise] shows whether the process drawing the arc
* is clockwise.
*/
#Throws(Exception::class)
private fun Path.arcFromTo(
x1: Float, y1: Float, x2: Float, y2: Float, r: Float,
clockwise: Boolean = true
) {
val c = centerPos(x1, y1, x2, y2, r, clockwise) // circle centers
// RectF borders
val left = c.x - r
val top = c.y - r
val right = c.x + r
val bottom = c.y + r
val startAngle = acos((x1 - c.x) / r) / Math.PI.toFloat() * 180
val endAngle = acos((x2 - c.x) / r) / Math.PI.toFloat() * 180
arcTo(
left, top, right, bottom,
if (clockwise) startAngle else -startAngle,
if (clockwise) endAngle - startAngle else startAngle - endAngle,
false
)
}
// use similar triangles to calculate circle center
#Throws(Exception::class)
private fun centerPos(
x1: Float, y1: Float, x2: Float, y2: Float, r: Float,
clockwise: Boolean
): Point {
val ab = ((x1 - x2).p2 + (y1 - y2).p2).sqrt
if (ab > r * 2) throw Exception("No circle fits the condition.")
val a = ab / 2
val oc = (r.p2 - a.p2).sqrt
val dx = (oc * (y2 - y1) / ab).absoluteValue.toInt()
val dy = (oc * (x2 - x1) / ab).absoluteValue.toInt()
val cx = ((x1 + x2) / 2).toInt()
val cy = ((y1 + y2) / 2).toInt()
return if (x1 >= x2 && y1 >= y2 || x1 <= x2 && y1 <= y2)
if (clockwise) Point(cx + dx, cy - dy) else Point(cx - dx, cy + dy)
else
if (clockwise) Point(cx - dx, cy - dy) else Point(cx + dx, cy + dy)
}

How to rotate text drawn in a straight line (without radius) when using drawTextOnPath?

I want to rotate the numbers in canvas,
This is what I have tried:
override fun onDraw(canvas: Canvas) {
var i = 0
while (i < rulerHeightInInch) {
val markingPositionYaxis =
screenHeightInPx - (ydpinch * i + topThreshold)
paint.textSize = getPixelValueForDp(18.0f)
val path = Path()
path.reset()
path.moveTo(
(getPixelValueForDp(30f) + paint.textSize),
markingPositionYaxis +17
)
path.lineTo(
(getPixelValueForDp(30f) + paint.textSize),
markingPositionYaxis - (paint.textSize)
)
canvas.drawTextOnPath(nf.format(i / 32), path, 0f, 0f, paint)
}
i++
}
}
First image is what I have right now
Second image is what I want
I don't want to draw in circular path or by using radius I want from top to bottom in a straight line
This is how i did it:
canvas.save()
canvas.rotate(180f,(getPixelValueForDp(30f) + paint.textSize),markingPositionYaxis)
canvas.drawTextOnPath(nf.format(i / 32), path, 0f, 0f, paint)
canvas.restore()

OpenGL ES 2.0 not drawing in Android Lollipop (5.1)

I'm new in openGL ES programming, so I followed the guide provided by Android Developers site. It shows how draw a simple green triangle that rotate on the screen following the touch point. I tried it on my Tablet (Galaxy Tab A10, Android Oreo - 8) and it worked, also on a Huawei (Android Marhmallow - 6.0); the problem is that the triangle is not shown on my Galaxy J3 (Android Lollipop - 5.1) without log errors and 0 as return of all glGetError() call, the only thing I can see is the color change of the background.
I couldn't find similar problems here in SO and on the web, has someone had the same problem?
(The language used is Kotlin, but I think it's a conceptual question, so please take a look also if the code is slightly different from Java).
Game Activity:
import android.content.Context
import android.opengl.GLSurfaceView
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.MotionEvent
class GameActivity : AppCompatActivity() {
private lateinit var mGLView: GLSurfaceView
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Create a GLSurfaceView instance and set it
// as the ContentView for this Activity.
mGLView = MyGLSurfaceView(this)
setContentView(mGLView)
}
class MyGLSurfaceView(context: Context) : GLSurfaceView(context) {
private val mRenderer: MyGLRenderer
init {
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2)
mRenderer = MyGLRenderer()
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(mRenderer)
renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
}
private val TOUCH_SCALE_FACTOR: Float = 180.0f / 320f
private var previousX: Float = 0f
private var previousY: Float = 0f
override fun onTouchEvent(e: MotionEvent): Boolean {
// MotionEvent reports input details from the touch screen
// and other input controls. In this case, you are only
// interested in events where the touch position changed.
val x: Float = e.x
val y: Float = e.y
when (e.action) {
MotionEvent.ACTION_MOVE -> {
var dx: Float = x - previousX
var dy: Float = y - previousY
// reverse direction of rotation above the mid-line
if (y > height / 2) {
dx *= -1
}
// reverse direction of rotation to left of the mid-line
if (x < width / 2) {
dy *= -1
}
mRenderer.angle += (dx + dy) * TOUCH_SCALE_FACTOR
requestRender()
}
}
previousX = x
previousY = y
return true
}
}
}
Custom Renderer Class:
import android.opengl.GLES20
import android.opengl.GLSurfaceView
import android.opengl.Matrix
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
class MyGLRenderer : GLSurfaceView.Renderer {
val TAG = MyGLRenderer::class.java.name
#Volatile
var angle: Float = 0f
private lateinit var mTriangle: Triangle
private val mRotationMatrix = FloatArray(16)
// mMVPMatrix is an abbreviation for "Model View Projection Matrix"
private val mMVPMatrix = FloatArray(16)
private val mProjectionMatrix = FloatArray(16)
private val mViewMatrix = FloatArray(16)
override fun onSurfaceCreated(unused: GL10, config: EGLConfig) {
// Set the background frame color
GLES20.glClearColor(0.8f, 0.2f, 0.2f, 1.0f)
// initialize a triangle
mTriangle = Triangle()
}
override fun onDrawFrame(unused: GL10) {
val scratch = FloatArray(16)
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
// enable face culling feature
//GLES20.glEnable(GL10.GL_CULL_FACE)
// specify which faces to not draw
//GLES20.glCullFace(GL10.GL_BACK)
// Set the camera position (View matrix)
Matrix.setLookAtM(mViewMatrix, 0, 0f, 0f, -3f, 0f, 0f, 0f, 0f, 1.0f, 0.0f)
// Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0)
// Create a rotation transformation for the triangle
/*val time = SystemClock.uptimeMillis() % 4000L
val angle = 0.090f * time.toInt()*/
Matrix.setRotateM(mRotationMatrix, 0, -angle, 0f, 0f, -1.0f)
// Combine the rotation matrix with the projection and camera view
// Note that the mMVPMatrix factor *must be first* in order
// for the matrix multiplication product to be correct.
Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0)
// Draw shape
mTriangle.draw(scratch)
}
override fun onSurfaceChanged(unused: GL10, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
val ratio: Float = width.toFloat() / height.toFloat()
// this projection matrix is applied to object coordinates
// in the onDrawFrame() method
Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1f, 1f, 3f, 7f)
}
}
Triangle Class:
import android.opengl.GLES20
import android.util.Log
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
class Triangle {
val TAG = Triangle::class.java.name
// number of coordinates per vertex in this array
val COORDS_PER_VERTEX = 3
var triangleCoords = floatArrayOf( // in counterclockwise order:
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
)
private val fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}"
private val vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
// the matrix must be included as a modifier of gl_Position
// Note that the uMVPMatrix factor *must be first* in order
// for the matrix multiplication product to be correct.
" gl_Position = uMVPMatrix * vPosition;" +
"}"
// Use to access and set the view transformation
private var mMVPMatrixHandle: Int = 0
// Set color with red, green, blue and alpha (opacity) values
val color = floatArrayOf(0.63671875f, 0.76953125f, 0.22265625f, 1.0f)
private var vertexBuffer: FloatBuffer =
// (number of coordinate values * 4 bytes per float)
ByteBuffer.allocateDirect(triangleCoords.size * 4).run {
// use the device hardware's native byte order
order(ByteOrder.nativeOrder())
// create a floating point buffer from the ByteBuffer
asFloatBuffer().apply {
// add the coordinates to the FloatBuffer
put(triangleCoords)
// set the buffer to read the first coordinate
position(0)
}
}
private var mProgram: Int
init {
val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
// create empty OpenGL ES Program
mProgram = GLES20.glCreateProgram().also {
// add the vertex shader to program
GLES20.glAttachShader(it, vertexShader)
Log.d(TAG, "glAttachShader: ${GLES20.glGetError()}")
// add the fragment shader to program
GLES20.glAttachShader(it, fragmentShader)
Log.d(TAG, "glAttachShader: ${GLES20.glGetError()}")
// creates OpenGL ES program executables
GLES20.glLinkProgram(it)
Log.d(TAG, "glLinkProgram: ${GLES20.glGetError()}")
}
Log.d(TAG, "glCreateProgram: ${GLES20.glGetError()}")
}
fun loadShader(type: Int, shaderCode: String): Int {
// create a vertex shader type (GLES20.GL_VERTEX_SHADER)
// or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
return GLES20.glCreateShader(type).also { shader ->
// add the source code to the shader and compile it
GLES20.glShaderSource(shader, shaderCode)
GLES20.glCompileShader(shader)
}
}
private var mPositionHandle: Int = 0
private var mColorHandle: Int = 0
private val vertexCount: Int = triangleCoords.size / COORDS_PER_VERTEX
private val vertexStride: Int = COORDS_PER_VERTEX * 4 // 4 bytes per vertex
fun draw(mvpMatrix: FloatArray) { // pass in the calculated transformation matrix
// Add program to OpenGL ES environment
GLES20.glUseProgram(mProgram)
Log.d(TAG, "glUseProgram: ${GLES20.glGetError()}")
// get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition").also {
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(it)
Log.d(TAG, "glEnableVertexAttribArray: ${GLES20.glGetError()}")
// Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(
it,
COORDS_PER_VERTEX,
GLES20.GL_FLOAT,
false,
vertexStride,
vertexBuffer
)
Log.d(TAG, "glVertexAttribPointer: ${GLES20.glGetError()}")
// get handle to fragment shader's vColor member
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor").also { colorHandle ->
// Set color for drawing the triangle
GLES20.glUniform4fv(colorHandle, 1, color, 0)
}
Log.d(TAG, "glGetUniformLocation: ${GLES20.glGetError()}")
// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix")
Log.d(TAG, "glGetUniformLocation: ${GLES20.glGetError()}")
// Pass the projection and view transformation to the shader
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0)
Log.d(TAG, "glUniformMatrix4fv: ${GLES20.glGetError()}")
// Draw the triangle
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)
Log.d(TAG, "glDrawArrays: ${GLES20.glGetError()}")
// Disable vertex array
GLES20.glDisableVertexAttribArray(it)
Log.d(TAG, "glDisableVertexAttribArray: ${GLES20.glGetError()}")
}
Log.d(TAG, "glGetAttribLocation: ${GLES20.glGetError()}")
}
}
Of course I added this line in the manifest.xml:
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
UPDATE:
I discovered that if I try to run the app while the phone (the Lollipop) is disconnected from PC, it'll fail to run, with the toast message: "Authorization denied" (the Italian message is "Autorizzazione negata", so in English it could be also "Permission denied"), and I don't know what it means, but maybe it could be helpful for someone.

Android LinearGradient Preallocate and reuse

I have been updating some old code in an app that has a lot of re-allocations in onDraw that complain with message:
Avoid object allocations during draw/layout operations (preallocate and reuse instead).
So I have got all of it updated properly with no more warning, except one. LinearGradient. It seems there is no method to set values on an instance of the object. And the properties are not public so you can't do linLayout.x = value;
This is my code and it complains with the warning described above (underlines LinearGradient):
myPaintGradient.setShader(new LinearGradient(deviation,6,halfwidth,LinearGradientSize,barColorGreen, barColorRed, android.graphics.Shader.TileMode.CLAMP));
I have just solved the same problem, albeit with a RadialGradient.
If you want to update location data of a shader on each draw call you should pre-allocate the shader (as the linter hints at) and you should also pre-allocate a Matrix. The only functionality a shader instance exposes is getLocalMatrix and setLocalMatrix.
To do what you want here, you will make use of setLocalMatrix, passing in a Matrix instance which you have performed some appropriate transforms on. In your case, I think a simple translation transform will do the trick.
As I touched on earlier, you will need to pre-allocate a Matrix and you will modify this pre-allocated one on each draw loop, before passing it into the shader.setLocalMatrix.
Here is an example of how I did this to update the centerX and centerY of a RadialGradient shader. The use case for this was a user can drag a circle around and I needed to keep the radial gradient centered on the circle.
The code below shows you a full working solution as a custom view which you could copy and paste into your code base, and use in some XML.
The interesting bit is in the onDraw override, where I am doing the matrix transforms and updating the shader:
class MoveableGradientCircle #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// Struct for all the data which needs pre-allocating
private data class Circle(
val radius: Float,
val centerX: Float,
val centerY: Float,
val paint: Paint,
val shaderMatrix: Matrix
)
// Pre-allocate the data
private var circle: Circle = Circle(
radius = 10f,
centerX = x,
centerY = y,
paint = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL_AND_STROKE
shader = RadialGradient(
centerX = 0f,
centerY = 0f,
radius = 10f,
startColor = Color.RED,
edgeColor = Color.GREEN,
tileMode = Shader.TileMode.CLAMP
)
},
shaderMatrix = Matrix()
)
// Setup a touch listener to update the circles x/y positions on user touch location
init {
setOnTouchListener { view, event ->
view.performClick()
when (event.action) {
MotionEvent.ACTION_DOWN -> {
circle = circle.copy(
centerX = event.x,
centerY = event.y
)
invalidate()
true
}
MotionEvent.ACTION_MOVE -> {
circle = circle.copy(
centerX = event.x,
centerY = event.y
)
invalidate()
true
}
MotionEvent.ACTION_UP -> {
circle = circle.copy(
centerX = event.x,
centerY = event.y
)
invalidate()
true
}
else -> false
}
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// No need to keep re-allocating shader
// Instead, we update the pre-allocated matrix
// In this case, we'll just translate it to the circles current center x,y
// which happens to be the users touch location
circle.shaderMatrix.reset()
circle.shaderMatrix.setTranslate(
circle.centerX,
circle.centerY
)
// Update the matrix on the shader
circle.paint.shader.setLocalMatrix(circle.shaderMatrix)
// Draw the arc with the updated paint
canvas?.drawArc(
circle.centerX - circle.radius,
circle.centerY - circle.radius,
circle.centerX + circle.radius,
circle.centerY + circle.radius,
0f,
360f,
true,
circle.paint
)
}
}
I hope this helps you or someone else with the same problem in the future!

Categories

Resources