• 贝塞尔曲线实现的购物车添加商品动画效果


    效果图如下:

    blob.png

    1.activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/rly_bezier_curve_shopping_cart"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">
    
        <FrameLayout
            android:id="@+id/fly_bezier_curve_shopping_cart"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentLeft="true"
            android:paddingRight="30dp"
            android:layout_alignParentStart="true">
            <ImageView
                android:id="@+id/iv_bezier_curve_shopping_cart"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_gravity="right"
                android:src="@drawable/menu_shop_car_selected" />
            <TextView
                android:id="@+id/tv_bezier_curve_shopping_cart_count"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/white"
                android:background="@drawable/corner_view"
                android:text="0"
                android:layout_gravity="right"/>
        </FrameLayout>
    
        <ListView
            android:id="@+id/lv_bezier_curve_shopping_cart"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@+id/fly_bezier_curve_shopping_cart"/>
    </RelativeLayout>
    

    menu_shop_car_selected.png

    corner_view.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android">
        <solid android:color="@color/red" />
        <corners android:radius="10dp" />
        <padding android:left="5dp" android:top="1dp"
            android:right="5dp" android:bottom="1dp" />
    </shape>
    

      

    2.adapter_shopping_cart_item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_marginBottom="1dp"
        android:layout_height="wrap_content"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">
        <ImageView
            android:id="@+id/iv_shopping_cart_item"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_centerVertical="true"
            android:src="@mipmap/ic_launcher"/>
        <TextView
            android:id="@+id/tv_shopping_cart_item"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="购 买"
            android:textSize="16sp"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"/>
    </RelativeLayout>
    

    3.MainActivity

    public class MainActivity extends AppCompatActivity {
        // 购物车父布局
        private RelativeLayout mShoppingCartRly;
        // 购物车列表显示
        private ListView mShoppingCartLv;
        // 购物数目显示
        private TextView mShoppingCartCountTv;
        // 购物车图片显示
        private ImageView mShoppingCartIv;
        // 购物车适配器
        private GoodsAdapter mGoodsAdapter;
        // 数据源(购物车商品图片)
        private ArrayList<GoodsModel> mData;
        // 贝塞尔曲线中间过程点坐标
        private float[] mCurrentPosition = new float[2];
        // 路径测量
        private PathMeasure mPathMeasure;
        // 购物车商品数目
        private int goodsCount = 0;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // findView
            mShoppingCartLv = (ListView) findViewById(R.id.lv_bezier_curve_shopping_cart);
            mShoppingCartCountTv = (TextView) findViewById(R.id.tv_bezier_curve_shopping_cart_count);
            mShoppingCartRly = (RelativeLayout) findViewById(R.id.rly_bezier_curve_shopping_cart);
            mShoppingCartIv = (ImageView) findViewById(R.id.iv_bezier_curve_shopping_cart);
            // 是否显示购物车商品数目
            isShowCartGoodsCount();
            // 添加数据源
            addData();
            // 设置适配器
            setAdapter();
        }
    
        private void setAdapter() {
            // 初始化适配器
            mGoodsAdapter = new GoodsAdapter(this, mData);
            // 设置适配器监听
            mGoodsAdapter.setCallBackListener(new GoodsAdapter.CallBackListener() {
                @Override
                public void callBackImg(ImageView goodsImg) {
                    // 添加商品到购物车
                    addGoodsToCart(goodsImg);
                }
            });
            // 设置适配器
            mShoppingCartLv.setAdapter(mGoodsAdapter);
        }
    
        private void addGoodsToCart(ImageView goodsImg) {
            // 创造出执行动画的主题goodsImg(这个图片就是执行动画的图片,从开始位置出发,经过一个抛物线(贝塞尔曲线),移动到购物车里)
            final ImageView goods = new ImageView(this);
            goods.setImageDrawable(goodsImg.getDrawable());
            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(100, 100);
            mShoppingCartRly.addView(goods, params);
    
            // 得到父布局的起始点坐标(用于辅助计算动画开始/结束时的点的坐标)
            int[] parentLocation = new int[2];
            mShoppingCartRly.getLocationInWindow(parentLocation);
    
            // 得到商品图片的坐标(用于计算动画开始的坐标)
            int startLoc[] = new int[2];
            goodsImg.getLocationInWindow(startLoc);
    
            // 得到购物车图片的坐标(用于计算动画结束后的坐标)
            int endLoc[] = new int[2];
            mShoppingCartIv.getLocationInWindow(endLoc);
    
            // 开始掉落的商品的起始点:商品起始点-父布局起始点+该商品图片的一半
            float startX = startLoc[0] - parentLocation[0] + goodsImg.getWidth() / 2;
            float startY = startLoc[1] - parentLocation[1] + goodsImg.getHeight() / 2;
    
            // 商品掉落后的终点坐标:购物车起始点-父布局起始点+购物车图片的1/5
            float toX = endLoc[0] - parentLocation[0] + mShoppingCartIv.getWidth() / 5;
            float toY = endLoc[1] - parentLocation[1];
    
            // 开始绘制贝塞尔曲线
            Path path = new Path();
            // 移动到起始点(贝塞尔曲线的起点)
            path.moveTo(startX, startY);
            // 使用二阶贝塞尔曲线:注意第一个起始坐标越大,贝塞尔曲线的横向距离就会越大,一般按照下面的式子取即可
            path.quadTo((startX + toX) / 2, startY, toX, toY);
            // mPathMeasure用来计算贝塞尔曲线的曲线长度和贝塞尔曲线中间插值的坐标,如果是true,path会形成一个闭环
            mPathMeasure = new PathMeasure(path, false);
    
            // 属性动画实现(从0到贝塞尔曲线的长度之间进行插值计算,获取中间过程的距离值)
            ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
            valueAnimator.setDuration(500);
    
            // 匀速线性插值器
            valueAnimator.setInterpolator(new LinearInterpolator());
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    // 当插值计算进行时,获取中间的每个值,
                    // 这里这个值是中间过程中的曲线长度(下面根据这个值来得出中间点的坐标值)
                    float value = (Float) animation.getAnimatedValue();
                    // 获取当前点坐标封装到mCurrentPosition
                    // boolean getPosTan(float distance, float[] pos, float[] tan) :
                    // 传入一个距离distance(0<=distance<=getLength()),然后会计算当前距离的坐标点和切线,pos会自动填充上坐标,这个方法很重要。
                    // mCurrentPosition此时就是中间距离点的坐标值
                    mPathMeasure.getPosTan(value, mCurrentPosition, null);
                    // 移动的商品图片(动画图片)的坐标设置为该中间点的坐标
                    goods.setTranslationX(mCurrentPosition[0]);
                    goods.setTranslationY(mCurrentPosition[1]);
                }
            });
    
            // 开始执行动画
            valueAnimator.start();
    
            // 动画结束后的处理
            valueAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    // 购物车商品数量加1
                    goodsCount ++;
                    isShowCartGoodsCount();
                    mShoppingCartCountTv.setText(String.valueOf(goodsCount));
                    // 把执行动画的商品图片从父布局中移除
                    mShoppingCartRly.removeView(goods);
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
                }
            });
        }
    
        private void isShowCartGoodsCount(){
            if (goodsCount == 0){
                mShoppingCartCountTv.setVisibility(View.GONE);
            }else {
                mShoppingCartCountTv.setVisibility(View.VISIBLE);
            }
        }
    
        private void addData() {
            // 初始化数据源
            mData = new ArrayList<>();
            // 添加数据源
            GoodsModel goodsModel = new GoodsModel();
            goodsModel.setmGoodsBitmap(BitmapFactory.decodeResource(getResources(),
                    R.drawable.goods_one));
            mData.add(goodsModel);
    
            goodsModel = new GoodsModel();
            goodsModel.setmGoodsBitmap(BitmapFactory.decodeResource(getResources(),
                    R.drawable.goods_two));
            mData.add(goodsModel);
    
            goodsModel = new GoodsModel();
            goodsModel.setmGoodsBitmap(BitmapFactory.decodeResource(getResources(),
                    R.drawable.goods_three));
            mData.add(goodsModel);
        }
    }

     4.GoodsAdapter

     1 public class GoodsAdapter extends BaseAdapter{
     2 
     3     // 数据源(购物车商品图片)
     4     private ArrayList<GoodsModel> mData;
     5     // 布局
     6     private LayoutInflater mLayoutInflater;
     7     // 回调监听
     8     private CallBackListener mCallBackListener;
     9 
    10     public GoodsAdapter(Context context, ArrayList<GoodsModel> mData){
    11         mLayoutInflater = LayoutInflater.from(context);
    12         this.mData = mData;
    13     }
    14 
    15     @Override
    16     public int getCount() {
    17         return mData != null ? mData.size(): 0;
    18     }
    19 
    20     @Override
    21     public Object getItem(int i) {
    22         return mData != null ? mData.get(i): null;
    23     }
    24 
    25     @Override
    26     public long getItemId(int i) {
    27         return i;
    28     }
    29 
    30     @Override
    31     public View getView(int i, View view, ViewGroup viewGroup) {
    32         ViewHolder viewHolder;
    33         if (view == null){
    34             view = mLayoutInflater.inflate(R.layout.adapter_shopping_cart_item, null);
    35             viewHolder = new ViewHolder(view);
    36             view.setTag(viewHolder);
    37         }else {
    38             // 复用ViewHolder
    39             viewHolder = (ViewHolder) view.getTag();
    40         }
    41 
    42         // 更新UI
    43         if (i < mData.size())
    44             viewHolder.updateUI(mData.get(i));
    45         return view;
    46     }
    47 
    48     class  ViewHolder{
    49         // 显示商品图片
    50         private ImageView mShoppingCartItemIv;
    51 
    52         public ViewHolder(View view){
    53             // findView
    54             mShoppingCartItemIv = (ImageView) view.findViewById(R.id.iv_shopping_cart_item);
    55             // onClick
    56             view.findViewById(R.id.tv_shopping_cart_item).setOnClickListener(
    57                     new View.OnClickListener() {
    58                 @Override
    59                 public void onClick(View view) {
    60                     if (mShoppingCartItemIv != null && mCallBackListener != null)
    61                         mCallBackListener.callBackImg(mShoppingCartItemIv);
    62                 }
    63             });
    64         }
    65 
    66         public void updateUI(GoodsModel goods){
    67             if (goods != null
    68                     && goods.getmGoodsBitmap() != null
    69                     && mShoppingCartItemIv != null)
    70                 mShoppingCartItemIv.setImageBitmap(goods.getmGoodsBitmap());
    71         }
    72     }
    73 
    74     public void setCallBackListener(CallBackListener mCallBackListener){
    75         this.mCallBackListener = mCallBackListener;
    76     }
    77 
    78     public interface CallBackListener{
    79         void callBackImg(ImageView goodsImg);
    80     }
    81 
    82 }

    5.GoodsModel

    public class GoodsModel {
        // 商品图
        private Bitmap mGoodsBitmap;
    
        public Bitmap getmGoodsBitmap() {
            return mGoodsBitmap;
        }
        public void setmGoodsBitmap(Bitmap mGoodsBitmap) {
            this.mGoodsBitmap = mGoodsBitmap;
        }
    }
    

      

      

     http://www.see-source.com/androidwidget/detail.html?wid=914

  • 相关阅读:
    开源大数据周刊-第29期
    rsync
    Tomcat基础教程(四)
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    python 取前10位
    python 字典统计次数
    阿里开源分布式事务Fescar demo示例
  • 原文地址:https://www.cnblogs.com/ganchuanpu/p/6896530.html
Copyright © 2020-2023  润新知