How to fix animation bug? - android

I have a VideoView (circle) and custom animation (Falling cards) on the screen, in Fragment, as in the screenshot:
I haven't figured out how to do it any other way, so I've inserted four CardViews, and I'm scrolling through them using AnimationUtils. In code it looks like this:
onViewCreated()
viewLifecycleOwner.lifecycleScope.launch {
// withContext(Dispatchers.Main) {
startAnimation()
// }
}
private var count = 0
private var cardsCount = 0
private var animTime = 0L
private suspend fun startAnimation() {
cardsCount = Utils.getScannerTime3(appsList.size).first
repeatCount = Utils.getScannerTime3(appsList.size).third
val cardsList = arrayListOf(binding.cardAnim, binding.cardAnim2, binding.cardAnim3, binding.cardAnim4)
repeat(repeatCount) {
cardsList.forEach {
if (count < cardsCount) {
cardAnimation(it.animCardView, it.appsName, it.appsIcon, appsList[count])
delay(300)
}
count++
}
}
}
private suspend fun cardAnimation(cardView: CardView, textView: TextView, imageView: ImageView, appInfo: AppInfo) {
val scaleUp = AnimationUtils.loadAnimation(requireActivity(), R.anim.cardview_loader_animation_best)
cardView.startAnimation(scaleUp)
scaleUp.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(p0: Animation?) {
viewLifecycleOwner.lifecycleScope.launch {
withContext(Dispatchers.Main) {
textView.text = Utils.refactorLongAppName(appInfo.appName)
imageView.setImageDrawable(appInfo.appIcon)
// lifecycleScope.launch {
for (i in 0..11) {
cardView.elevation = i.toFloat()
delay(i * 100L)
}
// }
}
}
}
override fun onAnimationEnd(p0: Animation?) {
}
override fun onAnimationRepeat(p0: Animation?) {
}
})
}
I launch VideoView in the standard way in onResume
fun startVideoAnim(view: VideoView, context: Context, resourcesRawId: Int) {
view.setVideoURI(Uri.parse("android.resource://" + context.packageName + "/" + resourcesRawId))
view.setOnPreparedListener {
it.isLooping = true
}
view.requestFocus()
view.start()
}
CardView:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/animCardView"
style="#style/Widget.CardView"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginHorizontal="24dp"
android:layout_marginVertical="3dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<ImageView
android:id="#+id/appsIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginVertical="7dp"
android:layout_marginStart="7dp" />
<TextView
android:id="#+id/appsName"
style="#style/TextStyleBold700"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|start"
android:layout_marginStart="55dp"
android:letterSpacing="0.025"
android:textColor="#color/text_dark"
android:textSize="16sp" />
</androidx.cardview.widget.CardView>
The problem is that the animation does not work as it should, I know that the animation is correct, because if I remove the VideoView completely, everything works as it should. If you simply do not show the video, the animation does not work as it should. I tried to replace VideoView with ExoPlayer and Lottie, although this does not suit me. Unsuccessfully. Please tell me what could be wrong and how to fix it? And if there are tips, ideas on how to make such an animation differently, write too.

Related

click on button using Stateflow works itself without any click

I have merged my code from livedata to stateflow to make some actions. Now I'm using StateFlow to navigate to another fragment, the problem is when I click on the button it is not working and when I used livedata it was worked!.
Here's my code of viewmodel
private val _habitsUIState = MutableStateFlow(HabitsMainUIState())
val habitsUIState: StateFlow<HabitsMainUIState> get() = _habitsUIState
private val _addHabitClickedEvent = MutableStateFlow(false)
val addHabitClickedEvent: StateFlow<Boolean> get() = _addHabitClickedEvent
private val _editHabitLongClickedEvent = MutableStateFlow(HabitUIState())
val editHabitLongClickedEvent: StateFlow<HabitUIState> get() = _editHabitLongClickedEvent
override fun onEditHabitLongClicked(habit: HabitUIState): Boolean { _editHabitLongClickedEvent.value = habit return false
}
fun onAddHabitClicked() {
_addHabitClickedEvent.value = true
}
and here is my code in fragment
private fun observeEvents() {
lifecycleScope.launch {
viewModel.apply {
habitsUIState.collectLatest { habitsMainState ->
habitsAdapter.setItems(habitsMainState.habits)
}
addHabitClickedEvent.collectLatest {
navigateToAddHabitDialog()
}
editHabitLongClickedEvent.collectLatest { habitsUIState ->
navigateToHabitEditingDialog(habitsUIState)
}
}
}
}
My button xml "I know it is not needed here but it is ok"
<ImageButton
android:id="#+id/add_habit_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_margin="16dp"
android:layout_marginTop="16dp"
android:background="#drawable/circle_button_style"
android:onClick="#{() -> viewModel.onAddHabitClicked()}"
android:padding="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_add" />
When the code is in the state above, only the data is loaded, but the rest of the code in the lifecycleScope never work as if it has frozen!
When I tried moving each function into its own lifecycleScope (see the code down), they all worked at the same time, but some of them should work when a certain button is pressed.
private fun observeEvents() {
lifecycleScope.launch {
viewModel.habitsUIState.collectLatest { habitsMainState ->
habitsAdapter.setItems(habitsMainState.habits)
}
}
lifecycleScope.launch {
viewModel.addHabitClickedEvent.collectLatest {
navigateToAddHabitDialog()
}
}
lifecycleScope.launch {
viewModel.editHabitLongClickedEvent.collectLatest { habitsUIState ->
navigateToHabitEditingDialog(habitsUIState)
}
}
}
What problem am I facing and how can I solve it? thank you
I have tried using livedata and it was worked!

How to add a calendar header in kotlin

I am using this library, https://github.com/kizitonwose/CalendarView, to create a calendar view. I've followed the docs to add the month header.
I've add the following to res/layout/calendar_month_header_layout.xml:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/headerTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:textSize="26sp"
tools:text="October 2019" />
also added the following to the activity xml:
<com.kizitonwose.calendarview.CalendarView
android:id="#+id/month_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cv_dayViewResource="#layout/calendar_day_layout"
app:cv_monthHeaderResource="#layout/calendar_month_header_layout" />
I've also added the following code:
val monthHeader: com.kizitonwose.calendarview.CalendarView = findViewById(R.id.month_header)
monthHeader.monthHeaderBinder = object : MonthHeaderFooterBinder<MonthViewContainer> {
override fun create(view: View) = MonthViewContainer(view)
override fun bind(container: MonthViewContainer, month: CalendarMonth) {
println("TEST")
container.textView.text = "${month.yearMonth.month.name.toLowerCase().capitalize()} ${month.year}"
}
}
However, nothing shows up for the activity. What am I missing? Thank you.
EDIT:
I notice the println("TEST") statement doesn't even get logged
Looks like the problem was I had to have the DayBinder too. The monthHeaderBinder on it's own results in a null error. Below is the working code
val currentMonth = YearMonth.now()
val firstMonth = currentMonth.minusMonths(10)
val lastMonth = currentMonth.plusMonths(10)
val firstDayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek
setContentView(R.layout.activity_calender_view)
var calendarView: com.kizitonwose.calendarview.CalendarView = findViewById(R.id.calendarView)
calendarView.dayBinder = object : DayBinder<DayViewContainer> {
// Called only when a new container is needed.
override fun create(view: View) = DayViewContainer(view)
// Called every time we need to reuse a container.
override fun bind(container: DayViewContainer, day: CalendarDay) {
container.textView.text = day.date.dayOfMonth.toString()
}
}
calendarView.setup(firstMonth, lastMonth, firstDayOfWeek)
calendarView.scrollToMonth(currentMonth)
calendarView.monthHeaderBinder = object : MonthHeaderFooterBinder<MonthViewContainer> {
override fun create(view: View) = MonthViewContainer(view)
override fun bind(container: MonthViewContainer, month: CalendarMonth) {
container.textView.text = "${month.yearMonth.month.name.toLowerCase().capitalize()} ${month.year}"
}
}
And the activity xml:
<com.kizitonwose.calendarview.CalendarView
android:id="#+id/calendarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cv_dayViewResource="#layout/calendar_day_layout"
app:cv_monthHeaderResource="#layout/calendar_month_header_layout"/>

Set data in Custom view

I am building a custom view that makes an HTTP request to a Rest API every 6 seconds and displays a different question with its possible answers. A Buff in this case would be the object that contains the question and its possible answers.
Right now, the views are injected, but, when setting the data (see setAnswer function), the first view shows the text from the last element of the answer list. The rest of the views don't show any text.
Buff object received from API
With this data, I should show a question with 3 possible answers: "No goals!", "One goal!", "Two or more" in that order.
{
"result": {
"id": 1,
"author": {
"first_name": "Ronaldo"
},
"question": {
"id": 491,
"title": "Kaio Jorge has 4 goals this tournament – I think he will score again today. What do you think?"
},
"answers": [
{
"id": 1163,
"buff_id": 0,
"title": "No goals!"
},
{
"id": 1164,
"buff_id": 0,
"title": "One goal!"
},
{
"id": 1165,
"buff_id": 0,
"title": "Two or more!"
}
]
}
}
This is how is displayed at the moment
First element displays the text from the 3rd answer and the other two are empty
BuffView.kt (my custom view)
class BuffView(context: Context, attrs: AttributeSet? = null): LinearLayout(context, attrs) {
private val apiErrorHandler = ApiErrorHandler()
private val getBuffUseCase = GetBuffUseCase(apiErrorHandler)
private var buffIdCount = 1
private val buffView: View
init {
buffView = inflate(context, R.layout.buff_view, this)
}
fun start() {
getBuff()
}
private fun getBuff() {
getBuffUseCase.invoke(buffIdCount.toLong(), object : UseCaseResponse<Buff> {
override fun onSuccess(result: Buff) {
displayBuff(result)
}
override fun onError(errorModel: ErrorModel?) {
//Todo: show error toast
Log.e("AppDebug", "onError: errorModel $errorModel")
}
})
val delay = 6000L
RepeatHelper.repeatDelayed(delay) {
if (buffIdCount < 5) {
buffIdCount++
getBuff()
}
}
}
private fun displayBuff(buff: Buff) {
setQuestion(buff.question.title)
setAuthor(buff.author)
setAnswer(buff.answers)
setCloseButton()
buffView.visibility = VISIBLE
}
private fun setQuestion(questionText: String) {
question_text.text = questionText
}
private fun setAuthor(author: Buff.Author) {
val firstName = author.firstName
val lastName = author.lastName
sender_name.text = "$firstName $lastName"
Glide.with(buffView)
.load(author.image)
.into(sender_image)
}
private fun setAnswer(answers: List<Buff.Answer>) {
val answersContainer = findViewById<LinearLayout>(R.id.answersContainer)
answersContainer.removeAllViews()
for(answer in answers) {
val answerView: View = LayoutInflater.from(answersContainer.context).inflate(
R.layout.buff_answer,
answersContainer,
false
)
answer_text?.text = answer.title
answersContainer.addView(answerView)
}
}
private fun setCloseButton() {
buff_close.setOnClickListener {
buffView.visibility = GONE
}
}
}
buff_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="#layout/buff_sender"/>
<include layout="#layout/buff_question"/>
<LinearLayout
android:id="#+id/answersContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>
buff_answer.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="#drawable/light_bg"
android:orientation="horizontal"
tools:ignore="RtlHardcoded">
<ImageView
android:id="#+id/answer_image"
android:layout_width="27dp"
android:layout_height="27dp"
android:layout_gravity="center_vertical"
android:src="#drawable/ic_generic_answer"
android:padding="4dp" />
<TextView
android:id="#+id/answer_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:textColor="#color/test_color_dark"
tools:text="The keeper's right"
android:textStyle="bold" />
</LinearLayout>
Try to call invalidate() after addView() to tell the View it should re-render. Also you need to set text on answerView, so the method should look like this:
private fun setAnswer(answers: List<Buff.Answer>) {
val answersContainer = findViewById<LinearLayout>(R.id.answersContainer)
answersContainer.removeAllViews()
for(answer in answers) {
val answerView: View = LayoutInflater.from(answersContainer.context).inflate(
R.layout.buff_answer,
answersContainer,
false
)
answerView.answer_text?.text = answer.title
answersContainer.addView(answerView)
}
invalidate() // or invalidateSelf()
}
Another thing is that it's a very bad practice to call your api from the custom view. Executing the usecase should be on the ViewModel/Presenter etc. level. Then the buffs should be provided to the Fragment/Activity and then set in the CustomView. You are also not cancelling the request in some view-destroying callback like onDetachedFromWindow() which can lead to memory leaks
You cannot add the same view more than once without recreating its new variable, this is not as simple as one would think.
private fun setAnswer(answers: List<Buff.Answer>) {
val answersContainer = findViewById<LinearLayout>(R.id.answersContainer)
var viewlist = ArrayList()
answersContainer.removeAllViews()
for(answer in answers) {
val answerView: View = LayoutInflater.from(answersContainer.context).inflate(
R.layout.buff_answer,
answersContainer,
false
)
answer_text?.text = answer.title
viewlist.add(answerView)
}
for(view in viewlist)
answersContainer.addView(view)
}

Seekbar - tint color changing for all instances of view

I have a Seekbar:
<SeekBar
android:id="#+id/sw_lock"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="60dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="60dp"
android:max="100"
android:thumb="#drawable/ic_thumb"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/lbl_device_status" />
Im using databinding and all is working fine. The problem comes when i want to change the thumb color.
In a fragment, i have a vertical linear layout containing 0...n views which contains this seekbar
response.observe(viewLifecycleOwner, Observer { list ->
activity?.run {
list.forEach { element ->
val mView = MyView(this)
mView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
mView.bind(element)
mView.didUnlock = { view, unlocked, element ->
//DO STUFF
}
binding.container.addView(mView)
}
}
})
This is working fine. I have n instances and each instance works properly.
Now, i want to change the thumb color when the progress change so i have:
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
changeColor(binding.swLock.progress > 80)
}
and changeColor method is just like this:
private fun changeColor(active: Boolean) {
val color = if(active) {
R.color.colorAccent
} else {
R.color.text_main
}
binding.swLock.progressDrawable.setTint(getColor(color))
binding.swLock.thumb.setTint(getColor(color))
}
And here comes the weird thing, the progressDrawable changes in each instance, the thumb changes for all instances. What am i doing wrong?
Thanks and regards
Send seekbar to changeColor fun like this:
private fun changeColor(active: Boolean, seekBar : SeekBar) {
val color = if(active) {
R.color.colorAccent
} else {
R.color.text_main
}
seekBar.thumb.setTint(getColor(color))
}
and call changeColor:
changeColor(binding.swLock.progress > 80, seekBar)
Good luck...
it seems that adding tint to the drawable is modifying all the instances of that drawable (if it makes any sense), so i added also the "active" drawable and changed the changeColor function to
private fun changeColor(active: Boolean) {
val thumb = if(active) {
R.drawable.ic_thumb_active
} else {
R.drawable.ic_thumb
}
val color = if(active) {
R.color.colorAccent
} else {
R.color.text_main
}
binding.swLock.progressDrawable.setTint(getColor(color))
binding.swLock.thumb = resources.getDrawable(thumb, null)
}
i just have to make this cleaner but its working now

How can I apply a shutter effect to a TextureView Camera2 preview

I have used a TextureView to preview image capture in an android application. This works okay, but I'm attempting to provide user feedback that images have been captured. I would like briefly hide the image preview, and then resume normal preview to provide that feedback.
I have failed attempting to push a blank image to the TextureView, and I've also failed to use the setVisibility() attribute (INVISIBLE and GONE) to momentarily hide the TextureView itself - this attribute has no visible effect. How else can I implement this feedback?
Similar to Lukas' answer, I also have a full black view on top of the texture view. When user presses the shutter button, this black view will fade in and then fade out. I find this animation to be closer to a real shutter effect that the phone's native camera has.
<TextureView
android:id="#+id/view_finder"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<View
android:id="#+id/camera_overlay"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
view.captureButton.setOnClickListener {
val fadeOutAnimation = AlphaAnimation(1.0f, 0.0f).apply {
duration = 250
setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
view.captureOverlay.alpha = 0.0f
view.captureOverlay.isVisible = false
}
override fun onAnimationStart(animation: Animation?) {
}
})
}
val fadeInAnimation = AlphaAnimation(0.0f, 1.0f).apply {
duration = 20
setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
view.captureOverlay.alpha = 1.0f
Handler(Looper.getMainLooper()).postDelayed({
view.captureOverlay.startAnimation(fadeOutAnimation)
}, 20)
}
override fun onAnimationStart(animation: Animation?) {
view.captureOverlay.isVisible = true
}
})
}
view.captureOverlay.startAnimation(fadeInAnimation)
/**
* Capture image here
*/
}
I did it by using a simple view that is exactly as big as your camera preview that is invisible per default and showing it for 100 ms when a picture gets taken.
camera?.takePicture(shutterCallback, null, takePictureCallback)
private val shutterCallback = Camera.ShutterCallback {
shutterEffectView.setVisible()
val handler = Handler()
val runnable = Runnable { shutterEffectView.setGone() }
handler.postDelayed(runnable, 100)
}
This code is kotlin and I am using the shutter callback because I am using the old camera API. With the camera2 API I think you have to use this callback

Categories

Resources