很久以前看到過有個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下面的圖片:
以上即可實現此效果,歡迎留言一起學習!