Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Motion Layout or animation item to cart

I'm planning to create an animation like all those apps that have a RecyclerView and when click Add to cart does like a screenshot of the item and with animations goes through the screen and ends in the cart icon. And I'm wondering if the Motion Layout is something I need to use no matter what, I know you can control the scenes and everything but perhaps there's another way to do this.

The scenario is :

I have a horizontal scroll view and in one item I have a button that says Add to cart, when I press to it then it animates to another view adding alpha until alpha is 0.

The naming of this animation is seen as Fly to cart animation.

Any hint?

enter image description here

This is my custom view and what I want is when I press the button of an item of my RecyclerView this exact item make it smaller and "fly" with a parabola or something or even a straight line to the number 2 making the item smaller and reducing alpha until it arrives to number 2 and it dissapear, and then animate this 2. When the item arrives to the item 2 it should move the second to the first item like the item was removed.

The idea is something like this : https://www.youtube.com/watch?v=YOsz8_vkfiM&ab_channel=AravindrajPalani but instead of doing a "screenshoot" moving the item it self or perhaps I have to hide the item and then create a copy of it... I don't know

like image 991
StuartDTO Avatar asked Oct 18 '25 02:10

StuartDTO


1 Answers

Do you mean somthing like this?

enter image description here

Here is How I do it

view_card.xml

Just a typical layout there is no important code here

<?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/card"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:cardBackgroundColor="#E6EFF1"
    app:cardCornerRadius="12dp"
    app:cardUseCompatPadding="true">
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="12dp"
            android:layout_marginTop="4dp"
            android:layout_marginEnd="16dp"
            android:text="Add To Cart"
            android:textSize="11sp"
            app:layout_constraintBottom_toBottomOf="@+id/imageView"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/imageView"
            app:layout_constraintTop_toBottomOf="@+id/textView"
            app:layout_constraintVertical_bias="1.0" />

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="12dp"
            android:layout_marginBottom="12dp"
            android:src="@drawable/a2"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="AIMAN"
            android:textAlignment="center"
            android:textColor="@color/black"
            android:textSize="20dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="@+id/button"
            app:layout_constraintTop_toTopOf="@+id/imageView" />
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.cardview.widget.CardView>

activity_main.xml

Just a typical layout there is no important code here

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <RelativeLayout
        android:id="@+id/card"
        android:layout_width="130dp"
        android:layout_height="100dp"
        android:layout_marginTop="@dimen/cardMargin"
        app:cardBackgroundColor="#EDEAF1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.943"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_centerInParent="true"
            android:src="@drawable/ic_cart" />
    </RelativeLayout>


    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:clipToPadding="false"
        android:orientation="horizontal"
        android:padding="16dp"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/card"
        tools:listitem="@layout/view_card" />

</androidx.constraintlayout.widget.ConstraintLayout>

CustomAdapter.java

Focus here on the remove(..) function, here when you notify by the function notifyItemRemoved(position) the animation after the card removes going to be applied, you don't need to do anything else.

    List<Drawable> list;
    CustomViewListener listener;

    public CustomAdapter() {
        list = new ArrayList<>();
    }

    public void setListener(CustomViewListener listener) {
        this.listener = listener;
    }

    public void add(Drawable drawable) {
        list.add(drawable);
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_card, parent, false);
        return new CustomViewHolder(inflate, listener);
    }

    @Override
    public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
        holder.bind(list.get(position));
    }


    @Override
    public int getItemCount() {
        return list.size();
    }

    public void remove(Drawable drawable) {
        int position = list.indexOf(drawable);
        list.remove(drawable);
        notifyItemRemoved(position);
    }

    public interface CustomViewListener {
        void onFly(View view, Drawable drawable, int x, int y, int width, int hight);
    }
}

CustomViewHolder.java

Foucse here at the following

  • initSize() to get the CardView size witch we need later to pass it for the temporary ImageView in the MainActivity
  • at the binding.button.setOnClickListener we going to get the CardView location and after that we call the function onFly to pass the information we need to the MainActivity
class CustomViewHolder extends RecyclerView.ViewHolder {
    int width = 0, height = 0;
    Drawable drawable;
    CustomAdapter.CustomViewListener listener;
    ViewCardBinding binding;

    public CustomViewHolder(@NonNull View itemView, CustomAdapter.CustomViewListener listener) {
        super(itemView);
        binding = ViewCardBinding.bind(itemView);
        this.listener = listener;
        initSize();
        initEvent();
    }

    private void initSize() {
        ViewTreeObserver treeObserver = binding.card.getViewTreeObserver();
        treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                binding.card.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                width = binding.card.getMeasuredWidth();
                height = binding.card.getMeasuredHeight();
            }
        });
    }

    public void bind(Drawable drawable) {
        this.drawable = drawable;
        binding.imageView.setImageDrawable(drawable);
        binding.card.setVisibility(View.VISIBLE);
    }

    private void initEvent() {
        binding.button.setOnClickListener(view -> {
            int[] location = new int[2];
            binding.card.getLocationInWindow(location);
            int x = location[0];
            int y = location[1];
            listener.onFly(binding.card, drawable, x, y, width, height);
            binding.card.setVisibility(View.INVISIBLE);
        });
    }
}

MainActivity.java

Foucse here at the following

  • Inside the function handleAnimateTheCard(..)

    1- getTemporaryImageView(..) create temporary ImageView with the width and hight of CardView from the RecyclerView

    2- loadBitmapFromView(..) draw inside a BitMap to look like the CardView of the RecyclerView

  • Inside the function startAnime(..) going to create animations you request by the coordinates of the CardView inside the RecyclerView ,and the RelativeLayout inside the MainActivty, here we have three animation pop up then moving the card with fading
    after the second animation finish going to remove the card from the RecyclerView

public class MainActivity extends AppCompatActivity {
    ActivityMainBinding binding;
    CustomAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        adapter = new CustomAdapter();
        LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true);
        binding.recyclerView.setLayoutManager(layoutManager);
        binding.recyclerView.setAdapter(adapter);
        adapter.setListener(this::handleAnimateTheCard);
        addSomeData();
    }


    void handleAnimateTheCard(View view, Drawable drawable, int x, int y, int width, int hight) {
        ImageView tempImage = getTemporaryImageView(width, hight);
        Bitmap viewBitmap = loadBitmapFromView(view);
        tempImage.setImageBitmap(viewBitmap);

        
        // here because the y value not include the toolbar and actionbar hight
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        int topOffset = dm.heightPixels - binding.getRoot().getMeasuredHeight();
        y -= topOffset;
        
        int[] location = new int[2];
        binding.card.getLocationInWindow(location);
        int cartX = location[0] - 100;
        int cartY = location[1] - topOffset;

        tempImage.setAlpha(1f);
        startAnime(tempImage, drawable, x, y, cartX, cartY);
    }

    private ImageView getTemporaryImageView(int width, int hight) {
        ImageView tempImage = new ImageView(MainActivity.this);
        binding.getRoot().addView(tempImage);
        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(width, hight);
        tempImage.setLayoutParams(params);
        return tempImage;
    }

    private void startAnime(ImageView tempImage, Drawable drawable, int x, int y, int cartX, int cartY) {
        ObjectAnimator popAnime = ObjectAnimator.ofFloat(tempImage, "translationX", x, x);
        ObjectAnimator popAnime2 = ObjectAnimator.ofFloat(tempImage, "translationY", y, y);
        ObjectAnimator popAnime3 = ObjectAnimator.ofFloat(tempImage, "scaleY", 1f, 0.9f, 1.2f);
        ObjectAnimator popAnime4 = ObjectAnimator.ofFloat(tempImage, "scaleX", 1f, 0.9f, 1.2f);
        AnimatorSet popAnimeSet = new AnimatorSet();
        popAnimeSet.setDuration(200).playTogether(popAnime, popAnime2, popAnime3, popAnime4);

        ObjectAnimator moveAnime = ObjectAnimator.ofFloat(tempImage, "translationX", x, cartX);
        ObjectAnimator moveAnime2 = ObjectAnimator.ofFloat(tempImage, "translationY", y, cartY);
        ObjectAnimator moveAnime3 = ObjectAnimator.ofFloat(tempImage, "scaleY", 1.2f, 0.5f);
        ObjectAnimator moveAnime4 = ObjectAnimator.ofFloat(tempImage, "scaleX", 1.2f, 0.5f);
        AnimatorSet moveAnimeSet = new AnimatorSet();
        moveAnimeSet.setDuration(500).playTogether(moveAnime, moveAnime2, moveAnime3, moveAnime4);
        moveAnimeSet.setStartDelay(50);
        moveAnimeSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                tempImage.animate().alpha(0f).setDuration(600);
            }
        });

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playSequentially(popAnimeSet, moveAnimeSet);
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                adapter.remove(drawable);
            }
        });
        animatorSet.start();
    }

    public Bitmap loadBitmapFromView(View view) {
        Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(returnedBitmap);
        Drawable bgDrawable = view.getBackground();
        if (bgDrawable != null)
            bgDrawable.draw(canvas);
        else
            canvas.drawColor(Color.WHITE);
        view.draw(canvas);
        return returnedBitmap;
    }


    private void addSomeData() {
        adapter.add(ContextCompat.getDrawable(this, R.drawable.a1));
        adapter.add(ContextCompat.getDrawable(this, R.drawable.a2));
        adapter.add(ContextCompat.getDrawable(this, R.drawable.a3));
    }
}
like image 151
Aiman Alyosofi Avatar answered Oct 20 '25 16:10

Aiman Alyosofi