How to sequentially update view using threads Android - android

I'm making an app where I have a row of 3 colored rectangles and have to change the color of each one then revert back to its original color. I have to do this for each rectangle in order so that they do not change colors simultaneously, but in sequence. I've been trying to do this with handlers since I need to wait 1 second before changing it back to its original color, but for some reason it only does the action for a single rectangle rather than all 3. Here's my code:
private fun start() {
val r1: Runnable = object : Runnable {
override fun run() {
// Lighting up the rectangle to be white
val highlightColor = Color.argb(160, 255, 255, 255)
box.setBackgroundColor(highlightColor)
}
}
val r2: Runnable = object : Runnable {
override fun run() {
// Changing back to original color
box.setBackgroundColor(Color.parseColor("#7c4dff"))
}
}
var i=0
while (i<3) {
val handler = Handler(Looper.getMainLooper())
val tableRow = tableLayout.getChildAt(0) as TableRow
box = tableRow.getChildAt(i) as TextView
val handler = Handler(Looper.getMainLooper())
handler.postDelayed(r1, 1000)
handler.postDelayed(r2, 2000)
i+=1
}
}
This only lights up the last rectangle rather than a sequence of all 3 in order. How can I fix it so that it lights up the first, then lights up the second once the first reverts back to the original color, etc.?

I suggest you use GlobalScope as it is still a CoroutineScope and you won't have to worry about migrating your project to androidx. Then to delay the thread, just use withContext followed by your delay time, like so below
private fun start() {
GlobalScope.launch(Dispatchers.Main) {
val boxes = with(tableLayout.getChildAt(0) as TableRow) {
(0..2).map { getChildAt(it) }
}
withContext(Dispatchers.Default) { delay(800) }
boxes.forEach { it.setBackgroundColor(Color.argb(160, 255, 255, 255)) }
withContext(Dispatchers.Default) { delay(800) }
boxes.forEach { it.setBackgroundColor(Color.parseColor("#7c4dff")) }
}
}

The reason it doesn't work is that your entire while loop that queues up the runnables finishes before they even start running, so they are all looking at the same box instance. For this strategy to work, you would either need to create two runnable instances for each of the three boxes, with each of them viewing a different box; or have each of the two runnables have a while loop that goes through all three boxes. You can also use the view to post your runnable so you don't need to create a Handler instance. Like this:
private fun start() {
val boxes = with(tableLayout.getChildAt(0) as TableRow) {
(0..2).map { getChildAt(it) }
}
tableLayout.postDelayed(1000L) {
boxes.forEach { it.setBackgroundColor(Color.argb(160, 255, 255, 255)) }
}
tableLayout.postDelayed(2000L) {
boxes.forEach { it.setBackgroundColor(Color.parseColor("#7c4dff")) }
}
}
This would be simpler using a coroutine like this, which will not leak your views like the Runnables will.
private fun start() = lifecycleScope.launch {
val boxes = with(tableLayout.getChildAt(0) as TableRow) {
(0..2).map { getChildAt(it) }
}
delay(1000L)
boxes.forEach { it.setBackgroundColor(Color.argb(160, 255, 255, 255)) }
delay(1000L)
boxes.forEach { it.setBackgroundColor(Color.parseColor("#7c4dff")) }
}

Related

Cannot hear the sound activated by a specific word (speech-to-text)

I am trying to create a speech-to-text app, and once you say a specific word, you should hear a sound for 5 seconds and then see a image.
However, I am only able to see the image, but I don't hear the sound.
Here is the block of code for this sound in the Main Activity:
private fun protocolDist() {
sounds!!.play(sound1, 1.0f, 1.0f, 1, 0, 1f)
Thread {
for (i in 0..5) {
try {
Thread.sleep(1000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
val finalI = 5 - i
textTest!!.post { textTest!!.text = finalI.toString() }
}
runOnUiThread { imMain!!.setImageResource(R.drawable.img1) }
}.start()
}
Also, depending on the version, there are two different types:
private fun createSoundPool() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
createNewSoundPool()
} else {
createOldSoundPool()
}
}
I am not sure what I did wrong or if I forgot something. Any ideas?
Thanks.
everything seems okay in that code, maybe you forgot to add a function at the end that enables the sound.
Try:
private fun loadSounds() {
sound_sound1 = sounds!!.load(this, R.raw.sound1, 1)
}
I hope it helps.

Android kotlin update TextView every second

I want the text view to be updated every second or less in a dynamic random way and at the end of the loop the text view show the last random number.
I tried to add a sleep method but it did not work every time I click the button the text view show the last random number directly.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rollButton: Button = findViewById(R.id.button1)
var result: TextView = findViewById(R.id.textView)
rollButton.setOnClickListener {
for (i in 1..10){
result.text = "${(1..6).random()}"
}
Toast.makeText(this, "Dice Rolled!", Toast.LENGTH_SHORT).show()
}
}
}
Coroutine delay is a way to do that, which is suggested in ltp's answer,
Another way is Runnable and Handler, an example function:
fun animateTextView(handler: Handler, textView: TextView, animate: Boolean = true) {
val runnable: Runnable = object : Runnable {
var randomNumber = 0
override fun run() {
randomNumber = (1..100).random()
textView.text = "$randomNumber"
handler.postDelayed(this, 500L)
}
}
// animate == false -> end text updates
if (animate) handler.postDelayed(runnable, 500L)
else handler.removeCallbacks(runnable)
}
Example use of the function:
private val textAnimaterHandler = Handler(Looper.getMainLooper())
animateTextView(textAnimaterHandler, binding.textView)
// Stop updates:
animateTextView(textAnimaterHandler, binding.textView, animate = false)
You can use Coroutine delay
rollButton.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
//Disable button temporarily to prevent multiple clicks
it.isEnabled = false
for (i in 1..10) {
binding.textviewFirst.text = "${(1..6).random()}"
//One second delay before the next
delay(1000)
//If you want random delay - say 100ms to 1s
//delay((100L..1000L).random())
}
it.isEnabled = true
Toast.makeText(this#MainActivity, "Dice Rolled!", Toast.LENGTH_SHORT).show()
}
}

Is it possible to debounce onClick callback right in a listener?

I have a listener for zooming in and out mapview:
class ZoomMapListener(
mapView: MapView,
private val zoom: Zoom,
) : View.OnClickListener {
private val localMapView = WeakReference(mapView)
private var clickCount = 0
override fun onClick(view: View?) {
clickCount++
}
fun moveCamera() {
val mapView = localMapView.get()
mapView?.let {
var cameraPosition = it.map.cameraPosition
val zoom = if (zoom == IN) {
cameraPosition.zoom + (1.0f * clickCount)
} else {
cameraPosition.zoom - (1.0f * clickCount)
}
cameraPosition = CameraPosition(
cameraPosition.target,
zoom,
cameraPosition.azimuth,
cameraPosition.tilt,
)
clickCount = 0
it.map.move(cameraPosition, Animation(Animation.Type.SMOOTH, 0.5f), null)
}
}
}
enum class Zoom {
IN,
OUT
}
In order if user clicks on button several times I've decided to use debounce operator from another answer(https://stackoverflow.com/a/60234167/13236614), so if there are five clicks, for example, camera makes fivefold increase in one operation.
The extension function:
#FlowPreview
#ExperimentalCoroutinesApi
fun View.setDebouncedListener(
listener: ZoomMapListener,
lifecycleCoroutineScope: LifecycleCoroutineScope,
) {
callbackFlow {
setOnClickListener {
listener.onClick(this#setDebouncedListener)
offer(Unit)
}
awaitClose {
setOnClickListener(null)
}
}
.debounce(500L)
.onEach { listener.moveCamera() }
.launchIn(lifecycleCoroutineScope)
}
And how I use it in my fragment:
zoomInMapButton.setDebouncedListener(ZoomMapListener(mapView, Zoom.IN), lifecycleScope)
I think it all looks kinda bad and I'm doubting because of #FlowPreview annotation, so is there a way to make it right in the custom listener class at least?
Using something with #FlowPreview or #ExperimentalCoroutinesApi is sort of like using a deprecated function, because it's possible it will stop working as expected or be removed in a future version of the library. They are relatively stable, but you'll need to check them each time you update your core Kotlin libraries.
My coroutine-free answer on that other question is more like throttleFirst than debounce, because it doesn't delay the first click.
I think you can directly handle debounce in your ZoomListener class by changing only one line of code! Replace clickCount++ with if (++clickCount == 1) v.postDelayed(::moveCamera, interval).
Disclaimer: I didn't test this.
The strategy here is on the first click to immediately post a delayed call to moveCamera(). If any clicks come in during that delay time, they do not post new delayed calls, because their contribution is accounted for in the clickCount that moveCamera() will use when the delay is over.
I also did some cleanup in moveCamera(), but it's functionally the same. In my opinion, ?.let should not be used for local variables because you can take advantage of smart casting (or early returns) for local variables, so you can keep your code more readable and less nested.
class ZoomMapListener(
mapView: MapView,
private val zoom: Zoom,
private val interval: Long
) : View.OnClickListener {
private val localMapView = WeakReference(mapView)
private var clickCount = 0
override fun onClick(v: View) {
if (++clickCount == 1) v.postDelayed(::moveCamera, interval)
}
fun moveCamera() {
val map = localMapView.get()?.map ?: return
val multiplier = if (zoom == IN) 1f else -1f
val newCameraPosition = CameraPosition.builder(map.cameraPosition)
.zoom(map.cameraPosition.zoom + multiplier * clickCount)
.build()
clickCount = 0
map.move(newCameraPosition, Animation(Animation.Type.SMOOTH, 0.5f), null)
}
}
...so is there a way to make it right in the custom listener class at least?
If I'm not mistaken, you want to make the debounce in the method of onClick, right?
override fun onClick(view: View?) {
// debounce
clickCount++
}
If that, why not use this reference link [it's in the link you provided]
https://stackoverflow.com/a/60193549/11835023

How to change picture in ImageView every second using Kotlin

I would like to have ImageView change picture every 1 second. I've tried using Java-specific solutions using Handler but weren't able to translate them correctly.
Basically, I have a list of image resources:
val loopImages = listOf(R.drawable.one, R.drawable.two, R.drawable.three, R.drawable.four)
and I want to change the pictures in a loop every 1 second. That was my current attempt that didn't work:
val handler = Handler()
val runnable = Runnable() {
var i = 0
fun run() {
binding.slideImage.setImageResource(loopImages[i])
i++
if (i > loopImages.size - 1) {
i = 0
}
handler.postDelayed({ run() }, 2000)
}
}
handler.postDelayed(runnable, 2000)
Is there any way to do it completely in Kotlin?
private fun startSlider() {
Handler().apply {
val runnable = object : Runnable {
var index = 0
var imageView = findViewById<ImageView>(R.id.imageView)
override fun run() {
imageView.setImageResource(loopImages[index])
index++
if (index > loopImages.size - 1) {
index = 0
}
postDelayed(this, 1000)
}
}
postDelayed(runnable, 1000)
}
}
Is there any way to do it completely in Kotlin?
Not really sure what you mean by this - your code is written in Kotlin syntax.
You can achieve a similar result using coroutines which removes the requirement for runnables and handlers, if that is closer to what you meant.
Untested code but something like this should work :
private fun imageChanger(scope: CoroutineScope, images: List<Int>, target: ImageView, periodMillis : Long = 2_000): Job =
scope.launch(Dispatchers.Main.immediate) {
if (images.isNotEmpty()) {
var idx = 0
while (isActive) {
if(idx >= images.size) idx = 0
images[idx].let(target::setImageResource)
idx++
delay(periodMillis)
}
}
}
Its a bit scrappy but can be refined.

Doing a task after a view became invisible

I'm writing a screenshot app using Android MediaProjection Api in which an overlay button is shown on top of everything and user can click it to capture a screenshot anywhere. Since MediaProjection records screen content, overlay button itself is in captured screenshots. To hide the button when capturing screenshot, I tried to set view visibility to INVISIBLE, take screenshot and revert it back to VISIBLE but since changing visibility is an async operation in Android, sometimes overlay button is still present in recorded shots.
I Changed to below snippet and it worked in my experiments:
floatingButton?.setOnClickListener { view ->
view.visibility = View.INVISIBLE
view.postDelayed(100) {
takeShot()
view.post {view.visibility = View.VISIBLE}
}
}
But it's basically saying I feeling good that in 100ms, button would be invisible. It's not a good solution and in the case of videos, in 100ms content could be very different from what user actually saw at that moment.
Android doesn't provide a onVisibiltyChangedListener kind of thing, so how could I perform a task after ensuring that a view visibility has changed?
Edit 1
Here's the takeShot() method:
private fun takeShot() {
val image = imageReader.acquireLatestImage()
val bitmap = image?.run {
val planes = image.planes
val buffer: ByteBuffer = planes[0].buffer
val pixelStride = planes[0].pixelStride
val rowStride = planes[0].rowStride
val rowPadding = rowStride - pixelStride * width
val bitmap = Bitmap.createBitmap(
width + rowPadding / pixelStride,
height,
Bitmap.Config.ARGB_8888
)
bitmap.copyPixelsFromBuffer(buffer)
image.close()
bitmap
}
bitmap?.let{
serviceScope.launch {
gallery.store(it)
}
}
}
The codes are inside of a foreground service and when user accepts media projection, I create ImageReader and VirtualDisplay:
imageReader = ImageReader.newInstance(size.width, size.height, PixelFormat.RGBA_8888, 2)
virtualDisplay = mediaProjection.createVirtualDisplay(
"screen-mirror",
size.width,
size.height,
Resources.getSystem().displayMetrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, // TODO: DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC ??
imageReader.surface, null, null
)
mediaProjection.registerCallback(object : MediaProjection.Callback() {
override fun onStop() {
virtualDisplay.release()
mediaProjection.unregisterCallback(this)
}
}, null)
I've tried without suspension and coroutine stuff and result was the same, so they most likely are irrelevant to problem.
Seems my problem is related to MediaProjection and that would be a separate question, but this question itself is relevant.
I ended up using this (almost copy-pasting core-ktx code for doOnPreDraw()). Pay attention that:
This doesn't work for View.INVISIBLE, because INVISIBLE doesn't trigger a "layout"
I don't endorse this, since it's GLOBAL, meaning that every visibility change related to "someView" view hierarchy, will call the onGlobalLayout method (and therefore your action/runnable).
I save the accepted answer for a better solution.
// usage
// someView.doOnVisibilityChange(become = View.GONE) {
// someView is GONE, do stuff here
// }
inline fun View.doOnVisibilityChange(become: Int, crossinline action: (view: View) -> Unit) {
OneShotVisibilityChangeListener(this) { action(this) }
visibility = newVisibility
}
class OneShotVisibilityChangeListener(
private val view: View,
private val runnable: Runnable
) : ViewTreeObserver.OnGlobalLayoutListener, View.OnAttachStateChangeListener {
private var viewTreeObserver: ViewTreeObserver
init {
viewTreeObserver = view.viewTreeObserver
viewTreeObserver.addOnGlobalLayoutListener(this)
view.addOnAttachStateChangeListener(this)
}
override fun onGlobalLayout() {
removeListener()
runnable.run()
}
private fun removeListener() {
if (viewTreeObserver.isAlive) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
} else {
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
view.removeOnAttachStateChangeListener(this)
}
override fun onViewAttachedToWindow(v: View) {
viewTreeObserver = v.viewTreeObserver
}
override fun onViewDetachedFromWindow(v: View) {
removeListener()
}
}

Categories

Resources