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?
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
Do you mean somthing like this?
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>
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>
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);
}
}
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
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);
});
}
}
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));
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With