很久以前看到过有个app的加入购物车效果是有点像树叶飘落的效果一样,现在我自己也来实现一下,先看效果:
实现思路:
以列表中的购物车的坐标为起点,以页尾的购物车为终点,通过创建view实现view从起点到终点间的动画达到相应的效果。
主要技术点:
1.获取view的坐标位置:
iv_car.getLocationInWindow(carPosition);//获取购物车的位置坐标(left,top)
2.获取状态栏高度
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusHeight = getResources().getDimensionPixelSize(resourceId);
}
3.用path类绘制路径
Path path = new Path();
path.moveTo(startX, startY);
if (toY - startY < height / 3) {//一阶贝塞尔曲线
path.quadTo((startX + toX) / 2, startY, toX - 50, toY);
} else {//二阶贝塞尔曲线
Point p1 = getPoint(startY, toY, 1);
Point p2 = getPoint(startY, toY, 2);
path.cubicTo(p1.x, p1.y, p2.x, p2.y, toX, toY);
}
4.借助PathMeasure类测量path的长度(mPathMeasure.getLength();),并获取对应长度对应的path上的坐标点(mPathMeasure.getPosTan(value, mPos, null);//pos此时就是长度为value的path路径上对应的坐标值)
final PathMeasure mPathMeasure = new PathMeasure(path, false);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
valueAnimator.setDuration(2000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
mPathMeasure.getPosTan(value, mPos, null);//pos此时就是中间距离点的坐标值
iv.setTranslationX(mPos[0]);
iv.setTranslationY(mPos[1]);
}
});
5.通过属性动画动态的变更view的位置
final PathMeasure mPathMeasure = new PathMeasure(path, false);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
valueAnimator.setDuration(2000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
mPathMeasure.getPosTan(value, mPos, null);//pos此时就是长度为value的path路径上对应的坐标值
iv.setTranslationX(mPos[0]);
iv.setTranslationY(mPos[1]);
}
});
// 动画结束后的处理
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
//当动画结束后:
@Override
public void onAnimationEnd(Animator animation) {
// 购物车的数量加1
carCount++;
totalPrice += productList.get(position).getProductPrice();
discount += productList.get(position).getOriginalPrice() - productList.get(position).getProductPrice();
iv.setVisibility(View.GONE);
rl_parent.removeView(iv);
UpdataCarCount();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
valueAnimator.start();
6.给textview添加中间线
holder.tv_orignalPrice.getPaint().setFlags(Paint.STRIKE_THRU_TEXT_FLAG );//textview 加中间线
所有代码如下:
LeaveFallingActivity:
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Point;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.swjt.androidapp.Adapter.LeaveAdapter;
import java.util.ArrayList;
import java.util.List;
public class LeaveFallingActivity extends AppCompatActivity {
ImageView iv_pic;//用来做动画的view
ImageView iv_car;//底部购物车
ListView lv_products;//商品列表listview
TextView tv_pCount, tv_total, tv_discount;
RelativeLayout rl_parent;
int carPosition[] = new int[2];//底部购物车的位置信息
List<Product> productList = new ArrayList<>();
private int carCount = 0; //购物车商品数量
private double totalPrice = 0.0;//商品总价
private double discount = 0;//折扣
private LeaveAdapter.ShoppingCarChangeListener listener = new LeaveAdapter.ShoppingCarChangeListener() {
@Override
public void add(int[] pos, int position,ImageView iv) {
addCart(pos, position);
// StartTranslateAnimation(pos,position);
}
};
/**
* 通过平移动画实现商品飘入购物车
* @param pos 点击点坐标位置(起点)
* @param position 点击的listview的item的位置
*/
private void StartTranslateAnimation(int[] pos, final int position) {
final ImageView iv = new ImageView(LeaveFallingActivity.this);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(DensityUtil.dip2px(LeaveFallingActivity.this, 40), DensityUtil.dip2px(LeaveFallingActivity.this, 40));
iv.setLayoutParams(params);
rl_parent.addView(iv);
TranslateAnimation animation1 = new TranslateAnimation(pos[0], carPosition[0] - 15, pos[1] - statusHeight, carPosition[1] - statusHeight);
animation1.setDuration(1100);
animation1.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
iv.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animation animation) {
iv.setVisibility(View.GONE);
rl_parent.removeView(iv);
carCount++;
totalPrice += productList.get(position).getProductPrice();
discount += productList.get(position).getOriginalPrice() - productList.get(position).getProductPrice();
UpdataCarCount();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
iv.setBackgroundResource(productList.get(position).getResourceId());还可以将imageview传过来,取到对应的图片资源给这个新创建的imageview
iv.startAnimation(animation1);
}
/**
* 创建属性动画加入购物车
* 1.根据点击获取到的位置pos(起点) 以及 购物车位置 carPosition(终点)
* 2.根据当前点的位置计算出当前点击的地方与屏幕高度的1/3比较,
* 小于1/3的用一阶贝塞尔曲线画路径
* 大于等于1/3用二阶贝塞尔曲线画路径
* 3.根据判断计算出贝塞尔曲线中间的影响点
* 4.path设定好之后执行动画,长度从0到path的长度
* 5.借助PathMeasure类测量路径的长度以及通过mPathMeasure的getPosTan方法获取到一定长度所对应的坐标点
* 将创建的imageview按照这些点移动即是最终效果
*
* @param pos 点击的点的坐标
* @param position 点击的item对应的position
*/
private void addCart(final int[] pos, final int position) {
final float mPos[] = new float[]{pos[0], pos[1]};
final ImageView iv = new ImageView(this);
iv.setBackgroundResource(productList.get(position).getResourceId());//还可以将imageview传过来,取到对应的图片资源给这个新创建的imageview
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(DensityUtil.dip2px(LeaveFallingActivity.this, 40), DensityUtil.dip2px(LeaveFallingActivity.this, 40));
rl_parent.addView(iv, params);
int startLoc[] = new int[2];
iv.getLocationInWindow(startLoc);
//计算各点
float startX = pos[0] + iv.getWidth() / 2;//path的起点x
float startY = pos[1] + iv.getHeight() / 2 - statusHeight;//path的起点y
float toX = carPosition[0];//path的终点x
float toY = carPosition[1] - statusHeight;//path的终点y
Path path = new Path();
path.moveTo(startX, startY);
if (toY - startY < height / 3) {//一阶贝塞尔曲线
path.quadTo((startX + toX) / 2, startY, toX - 50, toY);
} else {//二阶贝塞尔曲线
Point p1 = getPoint(startY, toY, 1);
Point p2 = getPoint(startY, toY, 2);
path.cubicTo(p1.x, p1.y, p2.x, p2.y, toX, toY);
}
final PathMeasure mPathMeasure = new PathMeasure(path, false);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
valueAnimator.setDuration(2000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
mPathMeasure.getPosTan(value, mPos, null);//pos此时就是长度为value的path路径上对应的坐标值
iv.setTranslationX(mPos[0]);
iv.setTranslationY(mPos[1]);
}
});
// 动画结束后的处理
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
//当动画结束后:
@Override
public void onAnimationEnd(Animator animation) {
// 购物车的数量加1
carCount++;
totalPrice += productList.get(position).getProductPrice();
discount += productList.get(position).getOriginalPrice() - productList.get(position).getProductPrice();
iv.setVisibility(View.GONE);
rl_parent.removeView(iv);
UpdataCarCount();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
valueAnimator.start();
}
/**
* 获取 pos对应的点的坐标
*
* @param startY 最小的有、
* @param toY 最大的y
* @param pos 第pos个点
* @return
*/
private Point getPoint(float startY, float toY, int pos) {
Point p = new Point();
int dy = (int) ((toY - startY) / 3);//将总高度分成3分
p.x = (int) (Math.random() * width);
// p.y= (int) (dy*(pos-1+Math.random())+startY);//这里是限制 上一点的最大y坐标<每个点的y坐标在<上一点最大坐标+dy
p.y = (int) (Math.random() * height + statusHeight);//不限制影响点的高度
return p;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leave_falling);
iv_pic = findViewById(R.id.iv_pic);
iv_car = findViewById(R.id.car);
lv_products = findViewById(R.id.lv_products);
tv_pCount = findViewById(R.id.tv_pCount);
tv_total = findViewById(R.id.tv_total);
tv_discount = findViewById(R.id.tv_discount);
rl_parent = findViewById(R.id.rl_parent);
initPostions();
intiDatas();
}
/**
* 商品加载完成后更新数据
*/
private void UpdataCarCount() {
String s = carCount + "";
if (carCount > 99) {
s = "99+";
}
tv_pCount.setText(s + "");
tv_total.setText("¥ " + totalPrice);
if (carCount == 0) {
tv_pCount.setVisibility(View.GONE);
} else {
tv_pCount.setVisibility(View.VISIBLE);
}
tv_discount.setText("已节省 ¥ " + discount);
}
/**
* 初始化数据
* 并设置adapter
*/
private void intiDatas() {
Product p1 = new Product(R.mipmap.closes, "潮流服装", 1, 1688.00d, 2360.00d);
Product p2 = new Product(R.mipmap.pants, "潮流裤子", 2, 1288.00d, 1399.00d);
Product p3 = new Product(R.mipmap.hap, "时尚鸭舌帽", 3, 688.00d, 4258.00d);
Product p4 = new Product(R.mipmap.watch, "经典手表", 1, 8688.00d, 18898.00d);
Product p5 = new Product(R.mipmap.handbag, "商务手提包", 1, 888.00d, 999.88d);
Product p6 = new Product(R.mipmap.shoes, "高端皮鞋", 1, 2888.00d, 6789.00d);
productList.add(p1);
productList.add(p2);
productList.add(p3);
productList.add(p4);
productList.add(p5);
productList.add(p6);
productList.add(p4);
productList.add(p5);
productList.add(p6);
productList.add(p1);
productList.add(p2);
productList.add(p3);
LeaveAdapter adapter = new LeaveAdapter(productList, this);
adapter.setListener(listener);
lv_products.setAdapter(adapter);
}
int statusHeight = 0;
int width, height;
//初始化原始位置即固定的位置
private void initPostions() {
iv_car.post(new Runnable() {
@Override
public void run() {
iv_car.getLocationInWindow(carPosition);//获取购物车的位置坐标(left,top)
获取状态栏高度
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusHeight = getResources().getDimensionPixelSize(resourceId);
}
获取状态栏高度
获取屏幕宽高
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
width = wm.getDefaultDisplay().getWidth();
height = wm.getDefaultDisplay().getHeight();
获取屏幕宽高
}
});
}
}
activity_leave_falling.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:id="@+id/rl_parent"
tools:context="com.swjt.androidapp.LeaveFallingActivity">
<ListView
android:id="@+id/lv_products"
android:layout_width="match_parent"
android:divider="#23b0b2"
android:dividerHeight="5dp"
android:layout_marginBottom="40dp"
android:paddingBottom="20dp"
android:layout_height="match_parent"></ListView>
<RelativeLayout
android:layout_width="match_parent"
android:layout_alignParentBottom="true"
android:background="@android:color/transparent"
android:layout_height="60dp">
<LinearLayout
android:layout_marginTop="20dp"
android:background="#00ff00"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="0dp"
android:layout_weight="3"
android:orientation="vertical"
android:gravity="center_vertical"
android:background="@color/colorPrimaryDark"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_total"
android:layout_width="wrap_content"
android:layout_marginLeft="70dp"
android:text="¥ 0.00"
android:textSize="13sp"
android:textColor="#ffffff"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_discount"
android:layout_width="wrap_content"
android:layout_marginLeft="75dp"
android:text="已节省 ¥ 0.00"
android:textSize="10sp"
android:textColor="#ffffff"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:background="@color/colorAccent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:text="去结算"
android:textSize="13sp"
android:textColor="#ffffff"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<RelativeLayout
android:layout_width="40dp"
android:layout_marginLeft="15dp"
android:background="@drawable/oval_yellow"
android:layout_height="40dp">
<ImageView
android:id="@+id/car"
android:layout_width="25dp"
android:background="@mipmap/shopingcar"
android:layout_centerInParent="true"
android:layout_height="25dp" />
<TextView
android:visibility="gone"
android:id="@+id/tv_pCount"
android:layout_width="20dp"
android:layout_alignParentTop="true"
android:background="@drawable/oval_red"
android:padding="2dp"
android:textColor="#ffffff"
android:gravity="center"
android:layout_alignParentRight="true"
android:textSize="9dp"
android:text="99+"
android:layout_height="20dp" />
</RelativeLayout>
</RelativeLayout>
<ImageView
android:visibility="gone"
android:id="@+id/iv_pic"
android:layout_width="40dp"
android:background="@mipmap/hap"
android:layout_height="40dp" />
</RelativeLayout>
LeaveAdapter:
import android.content.Context;
import android.graphics.Paint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.swjt.androidapp.Product;
import com.swjt.androidapp.R;
import java.util.List;
public class LeaveAdapter extends BaseAdapter {
private List<Product> list;
private Context context;
private ShoppingCarChangeListener listener;
public LeaveAdapter(List<Product> list, Context context) {
this.list = list;
this.context = context;
}
public void setListener(ShoppingCarChangeListener listener) {
this.listener = listener;
}
@Override
public int getCount() {
return list == null ? 0 : list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View view, ViewGroup parent) {
ViewHolder holder=null;
if (view == null){
view = LayoutInflater.from(context).inflate(R.layout.item_product, null);
holder=new ViewHolder(view);
view.setTag(holder);
}
holder= (ViewHolder) view.getTag();
holder.iv_productPic.setImageResource(list.get(position).getResourceId());
holder.tv_name.setText(list.get(position).getProductName());
holder.tv_price.setText("¥ " + list.get(position).getProductPrice());
holder.tv_orignalPrice.setText("¥ "+list.get(position).getOriginalPrice());
holder.tv_orignalPrice.getPaint().setFlags(Paint.STRIKE_THRU_TEXT_FLAG );//textview 加中间线
final ViewHolder finalHolder = holder;
holder.ll_car.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != listener) {
int[] pos = new int[2];
finalHolder.ll_car.getLocationInWindow(pos);
listener.add(pos, position,finalHolder.iv_productPic);
}
}
});
return view;
}
//购物车的点击事件
public interface ShoppingCarChangeListener {
void add(int pos[], int position,ImageView iv);
}
class ViewHolder {
private ImageView iv_productPic;
private TextView tv_name, tv_price,tv_orignalPrice;
private LinearLayout ll_car;
public ViewHolder(View view) {
initView(view);
}
private void initView(View view) {
iv_productPic = view.findViewById(R.id.iv_productPic);
tv_name = view.findViewById(R.id.tv_name);
tv_price = view.findViewById(R.id.tv_price);
tv_orignalPrice = view.findViewById(R.id.tv_orignalPrice);
ll_car = view.findViewById(R.id.ll_car);
}
}
}
item_product.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_height="80dp">
<ImageView
android:id="@+id/iv_productPic"
android:layout_width="60dp"
android:background="#f3f8d2"
android:src="@mipmap/hap"
android:layout_height="60dp" />
<LinearLayout
android:layout_width="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="15dp"
android:gravity="center_vertical"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:text="时尚鸭舌帽"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="wrap_content"
android:orientation="horizontal"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_price"
android:layout_width="wrap_content"
android:textColor="#0000ff"
android:text="¥ 888"
android:layout_height="wrap_content" />
<TextView
android:layout_marginLeft="10dp"
android:id="@+id/tv_orignalPrice"
android:layout_width="wrap_content"
android:textColor="#686868"
android:textSize="12sp"
android:text="¥ 888.00"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<LinearLayout
android:id="@+id/ll_car"
android:layout_width="30dp"
android:layout_marginRight="15dp"
android:gravity="center"
android:background="@drawable/oval_yellow"
android:layout_height="30dp">
<ImageView
android:layout_width="20dp"
android:background="@mipmap/shopingcar"
android:layout_height="20dp" />
</LinearLayout>
</LinearLayout>
实体类Product
public class Product {
private int resourceId;
private String productName;
private int productId;
private double productPrice;
private String productDescreptions;
private double originalPrice;
public Product() {
}
public Product(int resourceId, String productName, int productId, double productPrice, String productDescreptions,double originalPrice) {
this.resourceId = resourceId;
this.productName = productName;
this.productId = productId;
this.productPrice = productPrice;
this.productDescreptions = productDescreptions;
this.originalPrice=originalPrice;
}
public Product(int resourceId, String productName, int productId, double productPrice,double originalPrice) {
this.resourceId = resourceId;
this.productName = productName;
this.productId = productId;
this.productPrice = productPrice;
this.originalPrice=originalPrice;
}
public int getResourceId() {
return resourceId;
}
public void setResourceId(int resourceId) {
this.resourceId = resourceId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public int getProductId() {
return productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
public double getProductPrice() {
return productPrice;
}
public void setProductPrice(double productPrice) {
this.productPrice = productPrice;
}
public String getProductDescreptions() {
return productDescreptions;
}
public void setProductDescreptions(String productDescreptions) {
this.productDescreptions = productDescreptions;
}
public double getOriginalPrice() {
return originalPrice;
}
public void setOriginalPrice(double originalPrice) {
this.originalPrice = originalPrice;
}
}
工具类 DensityUtil
import android.app.Activity;
import android.content.Context;
import android.util.DisplayMetrics;
public class DensityUtil {
// 根据手机的分辨率将dp的单位转成px(像素)
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
// 根据手机的分辨率将px(像素)的单位转成dp
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
// 将px值转换为sp值
public static int px2sp(Context context, float pxValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
// 将sp值转换为px值
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
// 屏幕宽度(像素)
public static int getWindowWidth(Activity context){
DisplayMetrics metric = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(metric);
return metric.widthPixels;
}
// 屏幕高度(像素)
public static int getWindowHeight(Activity activity){
DisplayMetrics metric = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(metric);
return metric.heightPixels;
}
}
drawable下的oval_red.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#cd0000"/>
<corners android:radius="360dp"/>
</shape>
drawable下的oval_yellow.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ebe832"/>
<corners android:radius="360dp"/>
</shape>
mipmap下面的图片:
以上即可实现此效果,欢迎留言一起学习!