I read that is bad practice to call invalidate() inside the onDraw method so i want to be able to call invalidate() from my Ball class to the onDraw method that is on the Game class.
In another post i read that creating an object of the Game class and then calling that object like gameClass.invalidate() is the best way to do it and im trying to figure out how to do that.
Im having trouble passing the context and attrs.
Here is my CustomView class:
class Game(context: Context?, attrs: AttributeSet?) : View(context,
attrs){
private val ball1 = Ball(width/2 - 50.toDouble(),150.0,20.0)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.apply {
drawOval((width / 2) - 50,
ball1.posy.toFloat() - 50f,
(width / 2) + 50,
ball1.posy.toFloat() + 50f,
circleColor)
}
ball1.update()
}
Here is the class where i need to call invalidate:
is there a better way to call invalidate() here
val gameClass = Game( how do i pass the context and attrs here? )
class Ball(var posx: Double, var posy:Double,var velocity: Double){
//how do i pass the context and attrs here?
val gameClass = Game(...)
fun update(){
posy += 10
gameClass.invalidate()
}
}
here is my xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<view android:layout_width="0dp"
android:layout_height="0dp"
class="com.example.myapplication.Game"
id="#+id/view4"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
Okay first things first. It maybe a typo in your question but according to your code Game class is instantiating a Ball object and Ball class is instantiating a Game object. This is called circular dependency and you don't want this because it will result in StackOverFlow error (Ironic isn't it ?). So remove the Game object from Ball class.
Secondly, since you are calling ball1.update() inside onDraw() so calling invalidate() inside ball1.update() is no different than calling it after ball1.update(). In both cases you are calling invalidate() inside onDraw().
The purpose of invalidate() is to tell the view to redraw itself whenever we change any data related to the view. Since you are changing the data related to view inside onDraw() by calling ball1.update() so calling invalidate() right after it would be the logical step to take. Like this and it will work in your case.
class Game(context: Context, attributes: AttributeSet): View(context,attributes) {
private val paint :Paint = Paint(ANTI_ALIAS_FLAG)
private val ball1 = Ball(width/2 - 50.toDouble(),150.0,20.0)
init{
paint.color = Color.CYAN
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.apply {
drawOval(
(width / 2) - 50f,
ball1.posy.toFloat() - 50f,
(width / 2) + 50f,
ball1.posy.toFloat() + 50f,
paint)
ball1.update()
invalidate()
}
}
}
But onDraw() is not the best place to call invalidate(). There are many performance related issues so it is better to leave onDraw() alone.
Read here:
https://developer.android.com/training/custom-views/optimizing-view
https://www.youtube.com/watch?v=zK2i7ivzK7M
I'm assuming you want to animate your view. So it will be better to read the docs on animation.
But anyway let's continue. If you want to avoid calling invalidate() from onDraw() but still want to achieve the same result. Your best bet would be to create another function inside Game class which will start the process of updating your Ball object and calling invalidate() continuously. I'm naming this method as startAnimation()
This is how we can do this. Since your view is running on the UI Thread we need to get a Handler to it and tell the UI to run a Runnable continuously by using Handler.post(). That Runnable will contain the code that will update your Ball object and call invalidate(). Luckily the View class itself contains post() method which is the same as Handler.post() so we can safely call this method inside our Game class because Game inherits View.
Here is the code :
class Game(context: Context, attributes: AttributeSet): View(context,attributes) {
private lateinit var runnable : Runnable // reference to the runnable object
private val ball1 = Ball(width/2 - 50.toDouble(),150.0,20.0)
private val paint :Paint = Paint(ANTI_ALIAS_FLAG)
//This is the constructor of the class
init{
paint.color = Color.CYAN
//Here we initialize our Runnable and put the code that we want to run once we call startAnimation()
runnable = Runnable {
ball1.update()
invalidate()
//Calling post() inside here will loop the above code and you will see a smooth animation
post(runnable)
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.apply {
drawOval(
(width / 2) - 50f,
ball1.posy.toFloat() - 50f,
(width / 2) + 50f,
ball1.posy.toFloat() + 50f,
paint)
}
}
//This is the new function I am talking about
fun startAnimation()
{
post(runnable)
}
fun stopAnimation()
{
removeCallbacks(runnable)
}
}
And we start the animation by calling startAnimation() from MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val game = findViewById<Game>(R.id.view)
game.startAnimation()
}
}
To stop the animation just call stopAnimation()
Read more about processes, threads and handler here :
https://developer.android.com/guide/components/processes-and-threads
https://developer.android.com/reference/android/os/Handler
Related
class DrawingView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {
private lateinit var mDrawPath: FingerPath
private lateinit var mCanvasBitmap: Bitmap
private lateinit var mCanvasPaint: Paint
private lateinit var mDrawPaint: Paint
private var mBrushSize = 0
private var color = Color.BLACK
private lateinit var canvas: Canvas
init {
setUpDrawing()
}
private fun setUpDrawing() {
mDrawPaint = Paint()
mDrawPath = FingerPath(color, mBrushSize.toFloat())
mDrawPaint.color = color
mDrawPaint.style = Paint.Style.STROKE
mDrawPaint.strokeJoin = Paint.Join.ROUND
mDrawPaint.strokeCap = Paint.Cap.ROUND
mCanvasPaint = Paint(Paint.DITHER_FLAG)
mBrushSize = 20
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
mCanvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
canvas = Canvas(mCanvasBitmap)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawBitmap(mCanvasBitmap, 0f, 0f, mDrawPaint)
if (!mDrawPath.isEmpty) {
mDrawPaint.strokeWidth = mDrawPath.brushThickness
mDrawPaint.color = mDrawPath.color
canvas.drawPath(mDrawPath, mDrawPaint)
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
val touchX = event?.x
val touchY = event?.y
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
mDrawPath.color = color
mDrawPath.brushThickness = mBrushSize.toFloat()
mDrawPath.reset()
mDrawPath.moveTo(touchX!!, touchY!!)
}
MotionEvent.ACTION_MOVE -> {
mDrawPath.lineTo(touchX!!, touchY!!)
}
MotionEvent.ACTION_UP -> {
mDrawPath = FingerPath(color, mBrushSize.toFloat())
}
else -> return false
}
invalidate()
return true
}
internal inner class FingerPath(var color: Int, var brushThickness: Float) : Path()
}
So, I'm taking a course about Android Development and the instructor is building a drawing app -- and I can't understand how to code works, because he's not explaining the 'why'; so, he types a lot of code without explaining why he uses that variable or why is he overring those functions; therefore, I don't understand how to code work. Could you help me in understanding how this code work?
class DrawingView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {
private lateinit var mDrawPath: FingerPath
private lateinit var mCanvasBitmap: Bitmap
private lateinit var mCanvasPaint: Paint
private lateinit var mDrawPaint: Paint
private var mBrushSize = 0
private var color = Color.BLACK
private lateinit var canvas: Canvas
init {
// init block will called first when instance will be created so we
// are calling method setUpDrawing() as it is initialising everything
// that required to draw like color , brush size , brush behaviour
// (round , stroke etc .. ) . in simple manner , we can say painter is
// collecting all required tools before starting to paint
setUpDrawing()
}
private fun setUpDrawing() {
mDrawPaint = Paint()
mDrawPath = FingerPath(color, mBrushSize.toFloat())
mDrawPaint.color = color
mDrawPaint.style = Paint.Style.STROKE
mDrawPaint.strokeJoin = Paint.Join.ROUND
mDrawPaint.strokeCap = Paint.Cap.ROUND
mCanvasPaint = Paint(Paint.DITHER_FLAG)
mBrushSize = 20
}
// this method is going to be called by system when size is going to be
// changed so we are here creating blank board on which we are going to
// draw
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
mCanvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
canvas = Canvas(mCanvasBitmap)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawBitmap(mCanvasBitmap, 0f, 0f, mDrawPaint)
if (!mDrawPath.isEmpty) {
mDrawPaint.strokeWidth = mDrawPath.brushThickness
mDrawPaint.color = mDrawPath.color
canvas.drawPath(mDrawPath, mDrawPaint) // drawing path on canvas
}
}
// this method will be called by system when user is going to touch screen
override fun onTouchEvent(event: MotionEvent?): Boolean {
val touchX = event?.x
val touchY = event?.y
when (event?.action) {
// this event will be fired when user put finger on screen
MotionEvent.ACTION_DOWN -> {
mDrawPath.color = color
mDrawPath.brushThickness = mBrushSize.toFloat()
mDrawPath.reset() // reseting path before we set inital point
mDrawPath.moveTo(touchX!!, touchY!!)// set point from where drawing will be started
}
// this event will be fired when user start to move it's fingure . this will be fired continually until user pickup fingure
MotionEvent.ACTION_MOVE -> {
mDrawPath.lineTo(touchX!!, touchY!!)
}
// this event will be fired when user will pick up fingure from screen
MotionEvent.ACTION_UP -> {
mDrawPath = FingerPath(color, mBrushSize.toFloat())
}
else -> return false
}
invalidate() / /refreshing layout to reflect drawing changes
return true
}
internal inner class FingerPath(var color: Int, var brushThickness: Float) : Path()
}
This code is a custom view class in Android for drawing. The class extends the View class and implements touch event handling to allow a user to draw on the screen.
The class defines instance variables to store the paint, path, bitmap and canvas used for drawing. It also has variables for brush size and color.
In the setUpDrawing method, the paint object for drawing and the path object for tracking the user's touch are set up.
In onSizeChanged, the bitmap object is created and its canvas object is initialized.
In onDraw, the canvas is drawn on the screen and the path is drawn on the canvas if it's not empty.
The onTouchEvent method handles touch events (DOWN, MOVE, UP) and updates the path accordingly.
The FingerPath inner class extends the Path class and contains the color and brush size of the path.
Have you read through this section of the docs? Custom View Components
It explains the basics of creating a custom view, which on a basic level means overriding onDraw (where you draw the contents of the view) and maybe onSizeChanged (where you're told how big the view is - maybe you want to set the thickness of your paint brushes, work out where the centre of the view is etc).
onTouchEvent is a little more advanced, that allows you to handle the user poking at your view - the link up there gets into it, but you'll have a better understanding if you read the section on touch gestures. Broadly, you can get info about interaction events through onTouchEvent and work out things like whether the user touched the view, moved a finger, lifted it or moved it out of bounds etc. You'd use this info to, say, draw stuff under the user's finger for a painting app.
A Canvas is a drawing interface for a Bitmap. Instead of having to work out what pixels to change to what colour to draw a line or whatever, you do all your drawing through a Canvas that wraps that Bitmap. This lets you abstract the process and do high-level stuff like "draw a line", "draw an arc", "draw this entire path", "render this text" and so on. The Canvas works out how to make that happen in the Bitmap's pixel array.
The drawing happens with Paints, which you can think of as an object that says how the drawing happens. What does drawing a line look like? The Paint you use defines things like stroke width, transparency, colour etc - it's like the tool options in a paint program. It also allows you to set things like text size (if you're using it to render text) and do fancy things with blending over the pixels it's being drawn on, create shaders etc. It can also do things like control tiling if you're using a bitmap as the source image.
Generally you'll want to create these Paints at startup and reuse them during your View's lifespan, so it's typical to have a few of them - the one for painting the background, the one for drawing curves, the one for rendering text etc.
Usually in onDraw you just draw the view in its current state. The method takes a Canvas parameter - that's how you draw to the actual bitmap rendered on the UI. So if you had a knob, you could draw the circle, draw the position indicator at the appropriate location, add any text, and you're done!
For a drawing app it's a little more complicated - that "state" you need to remember and draw is the sum of all the drawing operations that have happened. You can get fancy with this (to create levels of undo) but the simplest approach is to create your own Bitmap to store the current drawing. You wrap that in its own Canvas (so you can draw on it) and then all the user interaction draws stuff to that Canvas, so you're painting on the bitmap. Then, in onDraw, you basically just need to paint the contents of that bitmap onto the system-provided Canvas so it gets drawn on-screen - just showing what you have, basically.
The code you have here is a little weird - you have that internal bitmap (called mCanvasBitmap) wrapped in a Canvas (called canvas) but you never actually draw to it. I feel like this line is meant to be rendering the Path you build up with the touch gestures:
override fun onDraw(canvas: Canvas) {
...
if (!mDrawPath.isEmpty) {
...
canvas.drawPath(mDrawPath, mDrawPaint) // drawing path on canvas
}
}
but in onDraw's scope canvas is referring to that parameter being passed in, i.e. the Canvas for drawing to the screen, not the one wrapping mCanvasBitmap. So that internal bitmap always stays empty, and then you just draw the most recent path (this is a good example of watching how you name things - why not mCanvas to be consistent?). It never "builds up" an image by drawing paths onto your internal bitmap. I can't tell what the exact intent is from the code though, I just thought I'd point it out!
I'm learning android programming and for practicing I'm trying to do a controller for some dc motors, then I did a customview for making a virtual joypad, which uses an interface and a callback for the ontouch listener.
The problem is, I'm working on my app using a single MainActivity as a navhost and then I'm navigating through different fragments, My customview just works when I override the interface method on my MainActivity but I can't make it works on my fragment, where I want to handle all the logic of the joypad.
I've a couple of days researching but most of the post that I've found are written on Java and I just can't make it work on Kotlin.
My custom view class
class KanJoypadView: SurfaceView, SurfaceHolder.Callback, View.OnTouchListener{
...kotlin
var joypadCallback: JoypadListener? = null
//the main constructor for the class
constructor(context: Context): super(context){
...
getHolder().addCallback(this)
setOnTouchListener(this)
...
}
//the interface for the main functionally of the view
interface JoypadListener {
fun onJoypadMove(x: Float, y: Float, src: Int){}
}
...
}
My MainActivity
class NavActivity : AppCompatActivity(), KanJoypadView.joypadListener {
...
//Overriding the Function from the interface,
//I just did this for debguging, but I dont want this override here
override fun onJoypadMove(x: Float, y: Float, src: Int) {
Log.d(src.toString(), y.toString()) //** I wanna do this in my Fragment, not in my activity **
}
}
My Fragment
class JoystickFragment : Fragment(), KanJoypadView.joypadListener {
...
var enginesArray = arrayOf(0.toFloat(), 0.toFloat(), 0.toFloat(), 0.toFloat())
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
...
val binding = DataBindingUtil.inflate(
inflater, R.layout.fragment_joystick, container, false
)
binding.leftJoypad.joypadCallback = (container?.context as KanJoypadView.JoypadListener?)
lJoypad = binding.leftJoypad.id
}
/*what I really want to do, but it is not happening as it is just happenning the
override from the NavActivity, which I dont need, and not from here which I need*/
override fun onJoypadMove(x: Float, y: Float, src: Int) {
if (src == lJoypad) {
if (y >= 0) {
enginesArray[0] = 1.toFloat()
enginesArray[1] = y
} else if (y < 0) {
enginesArray[0] = 0.toFloat()
enginesArray[1] = y
}
if (src == rJoypad) {
if (y >= 0) {
enginesArray[0] = 1.toFloat()
enginesArray[1] = y
} else if (yAxis < 0) {
enginesArray[0] = 0.toFloat()
enginesArray[1] = y
}
Log.d("Engines array", enginesArray.toString())
}
}
}
}
Also I've tried to make a function in the fragment, and then call that function from the onMoveJoypad method from the Activity, but also I couldn't made it work. I'll appreciate any help or advice on how to implement this, thanks in advance !
This:
if (context is joypadListener){
is a very hacky and error prone way to get a listener reference. It’s also very limiting because it makes it impossible for the listener to be anything but the Activity that created the view. Don’t do this!
You already have a joypad listener property. Just remove the private keyword so any class can set any listener it wants from the outside. Remove that whole try/catch block. When it’s time to call the listener’s function, use a null-safe ?. call to do it so it gracefully does nothing if a callback hasn’t been set yet.
Side note: all class and interface names should start with a capital letter by convention. Your code becomes very hard to read and interpret if you fail to follow this convention.
Also, I advise you to avoid using the pattern of making a class implement an interface to serve as a callback for one of the objects it is manipulating or for its own internal functions. You’re doing this twice in your code above. Your custom view does it for its own touch listener, and your Activity does it to serve as the joypad listener.
The reason is that it publicly exposes the class’s inner functionality and reduces modularity. It can make unit testing more difficult. It needlessly exposes ways to misuse the class you’ve designed. It’s not as big deal for Activities to do this because you rarely if ever work with Activity instances from the outside anyway. But it’s ugly for a view class to do it.
The alternative is to implement the interface as an anonymous object or lambda so the functionality of the callback is hidden from outside classes.
Edit: How to do this in your fragment
If you want to follow my advice above, don't implement callback interfaces in your classes. Use lambdas or anonymous classes instead.
class JoystickFragment : Fragment() {
//...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//...
adapter.joypadCallback = joypadListener
//...
}
private val joypadListener = KanJoypadView.JoypadListener { x, y, src ->
if (src == lJoypad) {
if (y >= 0) {
enginesArray[0] = 1.toFloat()
enginesArray[1] = y
} else if (y < 0) {
enginesArray[0] = 0.toFloat()
enginesArray[1] = y
}
if (src == rJoypad) {
if (y >= 0) {
enginesArray[0] = 1.toFloat()
enginesArray[1] = y
} else if (yAxis < 0) {
enginesArray[0] = 0.toFloat()
enginesArray[1] = y
}
Log.d("Engines array", enginesArray.toString())
}
}
}
}
I'm currently trying to change the matchConstraintPercentWidth from 2 to 0 of a view using Animation() when starting my activity (in method onWindowFocusChanged() to make sure that all the views have been drawn correctly). The problem is the animation ends instanlty (and th view has now the new params - seems like the duration of the animation is 0 ms), no matter the duration I set...
Here is my code (in Kotlin) :
override fun onWindowFocusChanged(hasFocus: Boolean) {
if (hasFocus) {
val gradient = findViewById<ImageView>(R.id.black_gradient)
val animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
val params = gradient.layoutParams as ConstraintLayout.LayoutParams
params.matchConstraintPercentWidth = 0f
gradient.layoutParams = params
}
}
animation.duration = 2000L // in ms
gradient.startAnimation(animation) //also tried animation.start() without effect
//animation.hasStarted() is always false here
}
}
Any help is welcome ;)
That applyTransformation method is where you're meant to calculate the current state of the animation, based on interpolatedTime (which is between 0.0 and 1.0). You're just setting your constraint value to 0, so it's not actually changing a value over time and animating anything.
Honestly you probably don't want to touch any of that if you can help it, Android has some helper classes that abstract a lot of that detail away, so you can just easily animate a thing.ValueAnimator is probably a good shout, you could just do
ValueAnimator.ofFloat(0f, 100f).apply {
addUpdateListener { anim ->
val params = (gradient.layoutParams as ConstraintLayout.LayoutParams)
params.matchConstraintPercentWidth = anim.animatedValue as Float
}
duration = 1000
start()
}
and that should be the equivalent of what you're doing. There's also ObjectAnimator at that link too, but that requires a setter method and there isn't one for that layout parameter (ConstraintProperties has some, but not for that one as far as I can see)
Code looks fine to me, try removing the condition of hasFocus, because there might some views which might be getting the focus before the this particular the thing you should do to diagnose is
try to log hasFocus if it's not getting focus then change the code like code below, also just a tip you should always initialize the views outside of callbacks.
override fun onWindowFocusChanged(hasFocus: Boolean) {
val gradient = findViewById<ImageView>(R.id.black_gradient)
val animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
val params = gradient.layoutParams as ConstraintLayout.LayoutParams
params.matchConstraintPercentWidth = 0f
gradient.layoutParams = params
}
}
animation.duration = 2000L // in ms
gradient.startAnimation(animation) //also tried animation.start() without effect
//animation.hasStarted() is always false here
}
I'm making android app with Kotlin in android studio now, and want to draw on already drawn custom view.
This app is simple app about shows graphical information with received data with serial port. Actually I'm new on Kotlin and rookie on Java/Android. This is code that has problem.
This is XML of custom view, (it is in the contraintlayout)
<!--activity_main.xml-->
<androidx.constraintlayout.widget.ConstraintLayout ... >
<com.(...).DrawUI
android:id="#+id/DrawUI" ...
/>
Trying to call drawing function 'DrawRxData(rxData)' on this thread,
//BluetoothController.kt
class BluetoothClient(private val activity: MainActivity,
private val socket:BluetoothSocket): Thread() {
override fun run() {
...
if(inputStream.available() > 0) {
...
inputStream.read(buffer)
val rxData = BluetoothRxParser(buffer)
DrawRxData(rxData)
...
}
...
}
}
And this is DrawUI that wrote above on XML
I want to draw something on this View with calling function in BluetoothController.kt!
//DrawUI.kt
class DrawUI(context: Context, attrs: AttributeSet?) : View(context, attrs) {
init {
// initializing Paint()s
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// measuring dimension and determine drawing sector
}
override fun onDraw(canvas: Canvas) {
// drawing background
}
}
I tried use Canvas on DrawUI class but it didn't work properly.
Also tried making Bitmap but it occurs error that width and height are zero. How can I fix this problem?
EDIT
This way is how I solved this problem, maybe there is another and simple solution..
Created ImageView that has same size with DrawUI on XML
<ImageView
android:id="#+id/DrawRx" ...
/>
Get View and cast to ImageView, then create bitmap and canvas.. DrawRxData() is in BluetoothClient class.
private fun DrawRxData (rxData: RxData) {
val v : View = activity.findViewById(R.id.DrawRx)
val iv : ImageView = v as ImageView
val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.setColor(Color.BLACK)
canvas.drawCircle(50F, 50F, 10F, paint)
Attach bitmap to ImageView but for thread, it looks like Dispatcher in C#..
Reference
activity.runOnUiThread(java.lang.Runnable {
iv.setImageBitmap(bitmap)
})
Actually just overlapped exist view, please answer if there is efficient way!
I would like to let my MainActivity know that there is something to do (e.g. calculate stuff) from a custom View. The View detects user inputs through touch and the MainActivity has to update certain user controls with a calculation from those View-Values. Basically I did an override on onTouchEvent:
override fun onTouchEvent(event: MotionEvent?): Boolean {
val x = event?.x
val y = event?.y
val dX = x?.minus(prevX)
val dY = y?.minus(prevY)
if(dX!! > 0){
lowerBound = x.toInt()
} else{
upperBound = x.toInt()
}
prevX = x!!.toInt()
prevY = y!!.toInt()
this.invalidate() //tell view to redraw
return true
}
How can I let MainActivityknow that lowerBound and upperBound updated?
As already recommended in the comments I'll go a little bit deeper in "hot to use a listener".
First of all, a "Listener" is nothing more than a interface which get called if something happen elsewhere. The best example is the View.setOnClickListener(View).
How to use that in your case?
Simply define a interface (best location is here inside your custom View):
interface OnBoundsUpdatedListener {
fun onBoundsUpdated(upperBound: Int, lowerBound: Int)
}
Then you have to create a property in your CustomView and call the Listener if the bounds have changed :
// Constructor and stuff
val onBoundsUpdateListener: OnBoundsUpdatedListener? = null
override fun onTouchEvent(event: MotionEvent?): Boolean {
...
onBoundsUpdateListener?.onBoundsUpdated(upperBound, lowerBound)
...
}
In your Activtiy you can find that View and set the listener:
val myCustomView = findViewById<MyCustomView>(R.id.id_of_your_customview)
myCustomView.onBoundsUpdateListener = object : OnBoundsUpdatedListener {
override fun onBoundsUpdated(upperBound, lowerBound) {
// Get called if its called in your CustomView
}
}
Note: The code can be simplified. But that is the basic stuff 😉