Converting Layout params animation from Kotlin to Java in Android Studio - java

I have an onScrollListener animation in my Kotlin project and I will like to use that code in my java as well, I have tired to convert the Kotlin code to Java but i am see some error and my app crashes when it detects the onScrollListener the animation hides a TextView
This is the Kotlin Code
class MainActivity : AppCompatActivity()
{
private lateinit var category_recycler: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var btnExtended = true
//animated scroll
var animator : ValueAnimator? = null
//the note onscroll listener
note_recycler.addOnScrollListener(object : RecyclerView.OnScrollListener(){
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (animator==null){
animator=createanimator()
}
if (dy>0 && btnExtended)
{
animator!!.start()
btnExtended = !btnExtended
}else if (dy<0 && !btnExtended){
animator!!.reverse()
btnExtended = !btnExtended
}
}
})
}
private fun createanimator(): ValueAnimator{
var textview = findViewById<TextView>(R.id.textview) //textview i want to hide
val initSize=textview.measuredWidth
val animator=ValueAnimator.ofInt(initSize,0)
animator.duration=250
animator.addUpdateListener { animation ->
val value = animation.animatedValue as Int
val layoutParams = textview.layoutParams //line 50
layoutParams.width = value
textview.requestLayout()
}
return animator
}
}
//So far this is the code i has converted to java
public class MainActivity extends AppCompatActivity implements NoteListener {
private RecyclerView note_recycler;
private ValueAnimator animator = null;
private Boolean btnExtended;
private TextView textview;
private LinearLayout.LayoutParams layoutParams;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnExtended = true;
note_recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(animator==null){
animator=createanimator();
}
if (dy>0 && btnExtended)
{
if(animator!=null){
animator.start();
}
btnExtended = !btnExtended;
}
else if (dy<0 && !btnExtended){
if(animator!=null){
animator.reverse();
}
btnExtended = !btnExtended;
}
}
});
}
private ValueAnimator createanimator() {
textview = findViewById(R.id.textview);
int initSize = textview.getMeasuredWidth();
animator=ValueAnimator.ofInt(initSize,0);
animator.setDuration(250);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(#NonNull ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
//there is a missing line here named line 50 from the kotlin code, Please can you help me convert it and also cross check the other code for the java convertion
layoutParams.width =value;
textview.requestLayout();
}
});
return animator;
}
}
//Please I am new to kotlin and i am not sure if this java convertion is right please can you check through it and convert the line 50 i was unable to convert

After a few looking around i wont say i found the answer my self but it sure works :)
ViewGroup.LayoutParams layoutParams = textview.getLayoutParams();
that was the missing line 50 that works, shockingly there are no error with the rest of the conversion.
+4hours haha
Either way this is a OnScrollAnimation like in gmail with their compose button, i set the button to wrap content and when the animation hides the text in the button, it only shows the icon.

Related

How Add Animation On view when view width is changing [Android]

I'm using collapsing toolbar and I am changing view width OnOffsetChangedListener. what I want is that put little animation when searchView width will change , now its changing very straight and its little weird for user. I tried to set android:animateLayoutChanges="true" on parent layout but it not worked
var mListener =
OnOffsetChangedListener { appBarLayout, verticalOffset ->
if (binding.collapsingToolbar.getHeight() + verticalOffset < 2 * ViewCompat.getMinimumHeight(
binding.collapsingToolbar
)
) {
val view: View = binding.searchView
val layoutParams: CollapsingToolbarLayout.LayoutParams = view.layoutParams as CollapsingToolbarLayout.LayoutParams
layoutParams.width = binding.appBar.width / 2
view.layoutParams = layoutParams
} else {
val view: View = binding.searchView
val layoutParams: CollapsingToolbarLayout.LayoutParams = view.layoutParams as CollapsingToolbarLayout.LayoutParams
layoutParams.width = CollapsingToolbarLayout.LayoutParams.MATCH_PARENT
view.layoutParams = layoutParams
}
}
binding.appBar.addOnOffsetChangedListener(mListener)
How you tried built-in interpolators?
Please check below link for some insights:
https://jebware.com/interp/android-animation-interpolators.html
a very basic example is as follows:
val fastOutSlowInInterpolator = FastOutSlowInInterpolator()
val interpolatedValue = fastOutSlowInInterpolator.getInterpolation(0.5f)
In above example 0.5f would vary from 0 to 1 and it will return animated value which you should use in view animation.
You can simply define an animation class.
class ResizeWidthAnimation(private val mView: View, private val mWidth: Int) : Animation() {
private val mStartWidth: Int = mView.width
override fun applyTransformation(interpolatedTime: Float, t: Transformation{
mView.layoutParams.width = mStartWidth + ((mWidth - mStartWidth) * interpolatedTime).toInt()
mView.requestLayout()
}
override fun willChangeBounds(): Boolean {
return true
}
}

When i click on multiple buttons, multiple audios start playing at the same time in android studio

When i click on multiple images of recyclerview then multiple audios start playing without stopping the previous one. It is getting irritating. I hope someone can help me fix this. I have read about singleton class and using only one instance of media player but couldn't understood how to implement it correctly.
class RecylerViewAdapter (var context: Context, var arrayList: ArrayList<ItemModel>) :
RecyclerView.Adapter<RecylerViewAdapter.ItemHolder>() {
var mediaPlayer: MediaPlayer? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
val viewHolder = LayoutInflater.from(parent.context)
.inflate(R.layout.sound_item_list, parent, false)
return ItemHolder(viewHolder)
}
override fun onBindViewHolder(holder: ItemHolder, position: Int) {
val itemModel: ItemModel = arrayList[position]
holder.soundImage.setImageResource(itemModel.soundImage)
holder.soundTitle.text = itemModel.soundTitle
mediaPlayer = MediaPlayer.create(context, itemModel.soundTrack)
mediaPlayer!!.isLooping = true
holder.soundImage.setOnClickListener {
Toast.makeText(context, itemModel.soundTitle, Toast.LENGTH_LONG).show()
mediaPlayer!!.start()
}
}
override fun getItemCount(): Int {
return arrayList.size
}
class ItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var soundImage = itemView.soundImage
var soundTitle = itemView.soundTitle
}
}
Code seems to be good. Remember to call release() on your player and initialise it again with new media file before playing it. Maybe it could be useful for you to implement a MediaPlayer.OnCompletionListener in which you could call player.release()
Or maybe this is enough:
holder.soundImage.setOnClickListener {
mediaPlayer!!.release()
mediaPlayer = null
mediaPlayer = MediaPlayer.create(context, itemModel.soundTrack)
Toast.makeText(context, itemModel.soundTitle, Toast.LENGTH_LONG).show()
mediaPlayer!!.start()
}
I'll recommend to initialize just single object of MediaPlayer in your fragment or activity in which your RecyclerView is located and than pass that object into adapter as a parameter and use it and dont forget to release MediaPlayer in onDestroy() of activity or onDestroyView() of fragment by calling MediaPlayer?.release(). In adapter OnClickListener() you have to stop the media player calling MediaPlayer?.stop() and MediaPlayer?.reset() and than add the new source
class RecylerViewAdapter (var context: Context, var arrayList: ArrayList<ItemModel>, var mediaPlayer : MediaPlayer?) :
RecyclerView.Adapter<RecylerViewAdapter.ItemHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
val viewHolder = LayoutInflater.from(parent.context)
.inflate(R.layout.sound_item_list, parent, false)
return ItemHolder(viewHolder)
}
override fun onBindViewHolder(holder: ItemHolder, position: Int) {
val itemModel: ItemModel = arrayList[position]
holder.soundImage.setImageResource(itemModel.soundImage)
holder.soundTitle.text = itemModel.soundTitle
holder.soundImage.setOnClickListener {
mediaPlayer?.stop()
mediaPlayer?.reset()
mediaPlayer?.setDataSource(itemModel.soundTrack)
mediaPlayer?.isLooping = true
mediaPlayer?.start()
}
}
override fun getItemCount(): Int {
return arrayList.size
}
class ItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var soundImage = itemView.soundImage
var soundTitle = itemView.soundTitle
}
}

Can You smooth scroll to new position within RecyclerView (before it is shown) and then show insert animation?

Hello fellow programmers!
I am currently facing problem with RecyclerView's add animation with smooth scroll simulataneously.
Problem
I have implemented RecyclerView with ListAdapter and DiffUtil for cool animations (insert, remove). The problem is that I can't get it to smooth scroll to added position without cancelling insert animation.
User is supposed to type in 'exercise name' and then push 'add' button to add it to current 'exercise list'. Then this exercise should be added to RecyclerView list. Everything works fine here, but I can't achieve smooth scroll to new item position without cancelling (or accelerating) insert animation.
viewModel.exerciseListLiveData.observe(viewLifecycleOwner, {
adapter.submitList(it) {
// Cancels insert animation (or accelerates it rapidly) - bad UX...
layoutManager.smoothScrollToPosition(recyclerView, RecyclerView.State(), 0)
}
})
On the other hand, substituting smoothScrollToPosition with scrollToPosition works fine but when there's more items (recycler's view is filled from top to bottom) it blinks on scroll. I know it's rather jump than smooth scroll but then, why is it working with add animation so it doesn't cancel or accelerate?
viewModel.exerciseListLiveData.observe(viewLifecycleOwner, {
adapter.submitList(it) {
// Works fine (recyclerView is scrolling to added position)
// but when there's no room to display more items - it scrolls to it with blink...
// which I simply can't stand!
layoutManager.smoothScrollToPosition(recyclerView, RecyclerView.State(), 0)
}
})
Possible solution?
I'm supposed to first smoothScrollToPosition(0) to top-most position, then call submitList(it) and finally call scrollToPosition(0) but I can't achieve that (it is doing so fast, simultaneously, that it's layering each other). Maybe I should use coroutines? Don't know...
How am I supposed to add proper delay? Maybe I am doing something wrong because i read that some programmers had this autoscroll when added item.
Code sample
Adapter
class ExercisesListAdapter :
ListAdapter<Exercise, ExercisesListAdapter.ExerciseItemViewHolder>(ExercisesListDiffUtil()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExerciseItemViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding: ItemRecyclerviewExerciseBinding =
DataBindingUtil.inflate(inflater, R.layout.item_recyclerview_exercise, parent, false)
return ExerciseItemViewHolder(binding)
}
override fun onBindViewHolder(holder: ExerciseItemViewHolder, position: Int) {
val currentExercise = getItem(position)
holder.bind(currentExercise)
}
override fun getItemId(position: Int): Long {
return getItem(position).id
}
// Item ViewHolder class
class ExerciseItemViewHolder(private val binding: ItemRecyclerviewExerciseBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(itemExercise: Exercise) {
binding.textCardExerciseName.text = itemExercise.name
}
}
}
DiffUtil
class ExercisesListDiffUtil: DiffUtil.ItemCallback<Exercise>() {
override fun areItemsTheSame(oldItem: Exercise, newItem: Exercise): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Exercise, newItem: Exercise): Boolean {
return oldItem == newItem
}
}
ViewModel
class CreateWorkoutViewModel(private val repository: WorkoutRepository) : ViewModel() {
private var exerciseId = 0L
private var exerciseList = mutableListOf<Exercise>()
val exerciseName = MutableLiveData<String?>()
private val _exerciseListLiveData = MutableLiveData<List<Exercise>>()
val exerciseListLiveData: LiveData<List<Exercise>>
get() = _exerciseListLiveData
fun onAddExercise() {
if (canValidateExerciseName())
addExerciseToList()
}
fun onAddWorkout() {
exerciseList.removeAt(2)
_exerciseListLiveData.value = exerciseList.toList()
}
private fun canValidateExerciseName(): Boolean {
return !exerciseName.value.isNullOrBlank()
}
private fun addExerciseToList() {
exerciseList.add(0, Exercise(exerciseId++, exerciseName.value!!))
_exerciseListLiveData.value = exerciseList.toList()
}
}
Fragment
class CreateWorkoutFragment : Fragment() {
lateinit var binding: FragmentCreateWorkoutBinding
lateinit var viewModel: CreateWorkoutViewModel
lateinit var recyclerView: RecyclerView
lateinit var adapter: ExercisesListAdapter
lateinit var layoutManager: LinearLayoutManager
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding =
DataBindingUtil.inflate(inflater, R.layout.fragment_create_workout, container, false)
setViewModel()
setRecyclerView()
// Observers:
// Update RecyclerView list on change
viewModel.exerciseListLiveData.observe(viewLifecycleOwner, {
adapter.submitList(it) {
layoutManager.smoothScrollToPosition(recyclerView, RecyclerView.State(), 0)
}
})
return binding.root
}
private fun setRecyclerView() {
recyclerView = binding.recyclerviewExercises
setLayoutManager()
setAdapter()
}
private fun setLayoutManager() {
layoutManager = LinearLayoutManager(context)
recyclerView.layoutManager = layoutManager
}
private fun setAdapter() {
adapter = ExercisesListAdapter()
adapter.setHasStableIds(true)
recyclerView.adapter = adapter
}
private fun setViewModel() {
val dao = WorkoutDatabase.getInstance(requireContext()).workoutDAO
val repository = WorkoutRepository(dao)
val factory = CreateWorkoutViewModelFactory(repository)
viewModel = ViewModelProvider(this, factory).get(CreateWorkoutViewModel::class.java)
binding.viewModel = viewModel
binding.lifecycleOwner = this
}
}
EDIT
I got it working but it's not 'elegant' way and insert animation is canceled through process :/
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if(layoutManager.findFirstCompletelyVisibleItemPosition() == 0)
layoutManager.scrollToPosition(0)
else {
recyclerView.clearAnimation()
layoutManager.smoothScrollToPosition(recyclerView, RecyclerView.State(), 0)
}
}
})

How to fix Scrolling issue of horizontal ViewPager2 and RecyclerView that are inside a vertical RecyclerView?

I have a RecyclerView (say, rootRecyclerView) that can have different kinds of rows depending on some API response. I implemented one of them is a horizontal ViewPager2 and another one is implemented with horizontal RecyclerView (say, childRecyclerView).
The rootRecyclerView swipes vertically whereas the viewPager2 and childRecyclerView swipes horizontally.
The Problem:
When I swipe on the screen, if the swipe is on the the viewPager2 or childRecyclerView, the swipe MUST go perfectly straight horizontally. Otherwise, they won't scroll horizontally; the swipe is taken by the rootRecyclerView and so the you would see vertical movement.
So, this happens because your thumb would move in a curved/circular direction creating movement in both the X axis and Y axis, and the so the rootRecyclerView intercepts the swipe creating this unpleasant user experience.
I did try to solve the issue, such as adding an OnItemTouchListener to the childRecyclerView like this:
private float Y_BUFFER = ViewConfiguration.get(getContext())
.getScaledPagingTouchSlop(); // 10;
private float preX = 0f;
private float preY = 0f;
childRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
#Override
public boolean onInterceptTouchEvent(#NonNull RecyclerView rv, #NonNull MotionEvent e) {
if(e.getAction()==MotionEvent.ACTION_DOWN){
childRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
}
if(e.getAction() == MotionEvent.ACTION_MOVE){
if (Math.abs(e.getX() - preX) > Math.abs(e.getY() - preY)) {
childRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
} else if (Math.abs(e.getY() - preY) > Y_BUFFER) {
childRecyclerView.getParent().requestDisallowInterceptTouchEvent(false);
}
}
preX = e.getX();
preY = e.getY();
return false;
}
// ... rest of the code
It solves the problem only for the childRecyclerView, but I could not solve it for the ViewPager2.
I have also tried to use GestureDetector as described in this answer link, and some other combinations of code, but I could not make it work.
Could anyone help me?
Okay, so after some research, I came to the conclusion of substituting my ViewPager2 with a recyclerView that will 'behave like' a viewPager :/ .
First I replaced my viewPager2 with a horizontal recyclerView. To make it behave like a viewpager, use SnapHelper.
RecyclerView childRecyclerView2 = findViewById(R.id.previously_viewPager);
// other init like setup layout manager, adapter etc
SnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(replacedRecyclerView); // <-- this makes out rv behave like a viewPager
After that, you have to add an OnItemTouchListener and override onInterceptTouchEvent just like the code segment in my question:
childRecyclerView2.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
#Override
public boolean onInterceptTouchEvent(#NonNull RecyclerView rv, #NonNull MotionEvent e) {
// same as the code segment in the question,
//so skipping this part.
//just copy it from my question
}
// ...
}
Optional:
In viewPager2, you can get the current focus with getCurrentItem(), but since we have replaced out viewpager2 with recyclerview, we don't have that method. So, we need to implement our own equivalent version. If you are a Kotlin guy, you can directly jump to the reference 2 and skip this part. Here is the java version if you need, I'll skip the explanation though.
Create SnapHelperExt.java
public class SnapHelperExt {
public SnapHelperExt(){}
public int getSnapPosition(RecyclerView recyclerView, SnapHelper snapHelper){
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
View snapView = snapHelper.findSnapView(layoutManager);
if (snapView != null) {
return layoutManager.getPosition(snapView);
}else{
return -1;
}
}
}
Next create an interface OnSnapPositionChangeListener as our listener :
public interface OnSnapPositionChangeListener {
void onSnapPositionChange(int position);
}
After that, create SnapOnScrollListener.java:
public class SnapOnScrollListener extends RecyclerView.OnScrollListener {
public enum Behavior {
NOTIFY_ON_SCROLL,
NOTIFY_ON_SCROLL_STATE_IDLE
}
private SnapHelperExt snapHelperExt;
private SnapHelper snapHelper;
private Behavior behavior;
private OnSnapPositionChangeListener onSnapPositionChangeListener;
private int snapPosition = RecyclerView.NO_POSITION;
public SnapOnScrollListener(SnapHelper snapHelper, Behavior behavior, OnSnapPositionChangeListener onSnapPositionChangeListener){
this.snapHelper = snapHelper;
this.behavior = behavior;
this.onSnapPositionChangeListener = onSnapPositionChangeListener;
this.snapHelperExt = new SnapHelperExt();
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (behavior == Behavior.NOTIFY_ON_SCROLL) {
maybeNotifySnapPositionChange(recyclerView);
}
}
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (behavior == Behavior.NOTIFY_ON_SCROLL_STATE_IDLE
&& newState == RecyclerView.SCROLL_STATE_IDLE) {
maybeNotifySnapPositionChange(recyclerView);
}
}
private void maybeNotifySnapPositionChange(RecyclerView recyclerView){
int prevPosition = this.snapHelperExt.getSnapPosition(recyclerView, snapHelper);
boolean snapPositionIsChanged = (this.snapPosition!=prevPosition);
if(snapPositionIsChanged){
onSnapPositionChangeListener.onSnapPositionChange(prevPosition);
this.snapPosition = prevPosition;
}
}
}
Finally, use it in this way:
SnapOnScrollListener snapOnScrollListener = new SnapOnScrollListener(
snapHelper,
SnapOnScrollListener.Behavior.NOTIFY_ON_SCROLL,
position -> {
Log.e(TAG, "currently focused page no = "+position);
// your code here, do whatever you want
}
);
childRecyclerView2.addOnScrollListener(snapOnScrollListener);
References:
create-viewpager-using-recyclerview
detecting-snap-changes-with-androids-recyclerview

Calling animation in custom view's Kotlin class from Java activity class

I have a Java Activity class that contains a custom view which is written in Kotlin
#BindView(R.id.icon)
Icon icon;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
icon.showIcon();
}
And the custom view class:
fun showIcon() {
visibility = View.VISIBLE
rotate()
}
fun rotate() {
ValueAnimator.ofFloat(0f, 360f).apply {
interpolator = AccelerateDecelerateInterpolator()
startDelay = 1000
duration = 1400
addUpdateListener {
val value = it.animatedValue as Float
squatIV.rotation = value
Timber.d(iconIV.rotation.toString())
}
start()
}
Binding work normally, I can see the icon on the activity when created. Even the animation runs because it's logging the rotation value of the Image View of the component. But on screen, the animation is not playing.
Is there a specific reason for that? Have you ever encounter this issue?
Update: Below you can see the dummy activity page and the custom view needs to be animate rotation. After some backend checks, custom view becomes visible and rotate animation triggers.
try this solution:
fun rotate() {
ValueAnimator.ofFloat(0f, 360f).apply {
interpolator = AccelerateDecelerateInterpolator()
startDelay = 1000
duration = 1400
addUpdateListener {
val value = it.animatedValue as Float
squatIV.rotation = value
Timber.d(iconIV.rotation.toString())
invalidate() // must be!
requestLayout() // check if works without it
}
start()
}

Categories

Resources