效果图android
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="140dp" android:padding="5dp"> <com.zykj.djs.widget.RoundImageView android:id="@+id/iv_good" android:layout_width="120dp" android:layout_height="120dp" android:src="@color/colorText" app:radius="5dp"/> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" android:text="地浆水入门套装,精华补水提亮肤色" android:textColor="@color/colorBlack" android:textSize="16sp" /> <TextView android:id="@+id/tv_money" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:text="¥ 186.00" android:textColor="@color/colorText" android:textSize="20sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_vertical" android:orientation="horizontal"> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> <!--放在加的图标前面--> <ImageView android:id="@+id/iv_goods_reduce" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@id/iv_goods_add" android:layout_marginLeft="37dp" android:layout_toLeftOf="@id/tv_goods_count" android:src="@mipmap/one_jianshao" android:visibility="visible"/> <TextView android:id="@+id/tv_goods_count" android:layout_width="28dp" android:layout_height="24dp" android:layout_alignTop="@id/iv_goods_add" android:layout_toLeftOf="@id/iv_goods_add" android:gravity="center" android:textColor="#333333" android:textSize="14sp"/> <ImageView android:id="@+id/iv_goods_add" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:src="@mipmap/one_tianjia"/> <TextView android:id="@+id/tv_unit" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="10dp" android:text="彩妆/瓶" /> </LinearLayout> </LinearLayout> </LinearLayout>
Item布局效果以下图所示,主要是对加号和减号作动画。app
Item布局ide
public class ProductAdapter extends BaseAdapter<ProductAdapter.ProductHolder, ProductBean> { private int reduceLeft = 0; private int addLeft = 0; private static final long TIME = 300; // 动画的执行时间 public ProductAdapter(Context context) { super(context); setShowFooter(false); } @Override public int provideItemLayoutId() { return R.layout.item_product; } @Override public ProductHolder createVH(View view) { return new ProductHolder(view); } @Override public void onBindViewHolder(@NonNull ProductHolder holder, int i) { holder.mTvTitle.setText(mData.get(i).name); holder.mTvMoney.setText(mData.get(i).price); holder.mTvGoodsCount.setText(mData.get(i).count == 0 ? "" : String.valueOf(mData.get(i).count)); holder.mIvGoodsReduce.setVisibility(mData.get(i).count == 0 ? View.INVISIBLE : View.VISIBLE); } public class ProductHolder extends RecyclerView.ViewHolder implements View.OnClickListener { @Nullable @Bind(R.id.iv_good) RoundImageView mIvGood; @Nullable @Bind(R.id.tv_title) TextView mTvTitle; @Nullable @Bind(R.id.tv_money) TextView mTvMoney; @Nullable @Bind(R.id.iv_goods_reduce) ImageView mIvGoodsReduce; @Nullable @Bind(R.id.tv_goods_count) TextView mTvGoodsCount; @Nullable @Bind(R.id.iv_goods_add) ImageView mIvGoodsAdd; public ProductHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); mIvGoodsReduce.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // 获取减小图标的位置 reduceLeft = mIvGoodsReduce.getLeft(); mIvGoodsReduce.getViewTreeObserver().removeOnGlobalLayoutListener(this); } }); mIvGoodsReduce.setOnClickListener(this); mIvGoodsAdd.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // 获取增长图标的位置 addLeft = mIvGoodsAdd.getLeft(); mIvGoodsAdd.getViewTreeObserver().removeOnGlobalLayoutListener(this); } }); mIvGoodsAdd.setOnClickListener(this); } @Override public void onClick(View view) { if (mOnItemClickListener != null) { mOnItemClickListener.onItemClick(view, this.getAdapterPosition()); } switch (view.getId()){ case R.id.iv_goods_reduce: mData.get(this.getAdapterPosition()).count--; // 防止过快点击出现多个关闭动画 if (mData.get(this.getAdapterPosition()).count == 0) { animClose(mIvGoodsReduce); mTvGoodsCount.setText(""); // 考虑到用户点击过快 } else if (mData.get(this.getAdapterPosition()).count < 0) { // 防止过快点击出现商品数为负数 mData.get(this.getAdapterPosition()).count = 0; } else { mTvGoodsCount.setText(String.valueOf(mData.get(this.getAdapterPosition()).count)); } break; case R.id.iv_goods_add: mData.get(this.getAdapterPosition()).count++; // if (allCount > 0) { // mTvShoppingCartCount.setVisibility(View.VISIBLE); // } // mTvShoppingCartCount.setText(String.valueOf(allCount)); if (mData.get(this.getAdapterPosition()).count == 1) { mIvGoodsReduce.setVisibility(View.VISIBLE); animOpen(mIvGoodsReduce); } //addGoods2CartAnim(iv_goods_add); mTvGoodsCount.setText(String.valueOf(mData.get(this.getAdapterPosition()).count)); break; } } } public void animOpen(final ImageView imageView) { AnimatorSet animatorSet = new AnimatorSet(); ObjectAnimator translationAnim = ObjectAnimator.ofFloat(imageView, "translationX", addLeft - reduceLeft, 0); ObjectAnimator rotationAnim = ObjectAnimator.ofFloat(imageView, "rotation", 0, 180); animatorSet.play(translationAnim).with(rotationAnim); animatorSet.setDuration(TIME).start(); } public void animClose(final ImageView imageView) { AnimatorSet animatorSet = new AnimatorSet(); ObjectAnimator translationAnim = ObjectAnimator.ofFloat(imageView, "translationX", 0, addLeft - reduceLeft); ObjectAnimator rotationAnim = ObjectAnimator.ofFloat(imageView, "rotation", 0, 180); animatorSet.play(translationAnim).with(rotationAnim); animatorSet.setDuration(TIME).start(); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { // TODO: 2018/5/19 由于属性动画会改变位置,因此当结束的时候,要回退的到原来的位置,同时用补间动画的位移很差控制 ObjectAnimator oa = ObjectAnimator.ofFloat(imageView, "translationX", addLeft - reduceLeft, 0); oa.setDuration(0); oa.start(); imageView.setVisibility(View.GONE); } }); } }
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:id="@+id/snack_layout" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <include layout="@layout/ui_view_toolbar" /> <android.support.v7.widget.RecyclerView android:id="@+id/recycle_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="none"></android.support.v7.widget.RecyclerView> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true"> <!--这个是下面去结算的页面--!> <include layout="@layout/shop_car" /> </LinearLayout> </RelativeLayout>
购物车页面布局效果布局
购物车页面学习
public class ProductActivity extends RecycleViewActivity<ListsPresenter, ProductAdapter, ProductBean> { @Bind(R.id.snack_layout) RelativeLayout mSnackLayout; @Bind(R.id.tv_shopping_cart_count) TextView mTvShoppingCartCount; @Bind(R.id.iv_shopping_cart) ImageView iv_shopping_cart; private int allCount; // 贝塞尔曲线中间过程点坐标 private float[] mCurrentPosition = new float[2]; @Override protected int provideContentViewId() { return R.layout.activity_product; } @Override protected String provideTitle() { return "购物车"; } @Override protected String provideButton() { return null; } @Override protected void initAllMembersView() { super.initAllMembersView(); /*===================本身造的数据 begin====================*/ List<ProductBean> productBeans = new ArrayList<>(); ProductBean bean = new ProductBean(); bean.name = "化妆品1"; bean.price = "199.00"; productBeans.add(bean); ProductBean bean1 = new ProductBean(); bean1.name = "化妆品2"; bean1.price = "99.00"; productBeans.add(bean1); addNews(productBeans, 2); /*===================本身造的数据 end====================*/ } @Override protected RecyclerView.LayoutManager provideLayoutManager() { return new LinearLayoutManager(getContext()); } @Override protected ProductAdapter provideAdapter() { return new ProductAdapter(getContext()); } @Override public ListsPresenter createPresenter() { return new ListsPresenter(); } @Override public void onItemClick(View view, int position) { switch (view.getId()) { case R.id.iv_goods_reduce: allCount--; // 商品的数量是否显示 if (allCount <= 0) { allCount = 0; mTvShoppingCartCount.setVisibility(View.GONE); } else { mTvShoppingCartCount.setText(String.valueOf(allCount)); mTvShoppingCartCount.setVisibility(View.VISIBLE); } break; case R.id.iv_goods_add: allCount++; if (allCount > 0) { mTvShoppingCartCount.setVisibility(View.VISIBLE); } mTvShoppingCartCount.setText(String.valueOf(allCount)); addGoods2CartAnim((ImageView) view); break; } } /** * 贝塞尔曲线动画 * * @param goodsImageView */ public void addGoods2CartAnim(ImageView goodsImageView) { final ImageView goods = new ImageView(ProductActivity.this); goods.setImageResource(R.mipmap.icon_goods_add); int size = TextUtil.dp2px(ProductActivity.this, 24); ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(size, size); goods.setLayoutParams(lp); //mSnackLayout是整个页面布局id mSnackLayout.addView(goods); // 控制点的位置 int[] recyclerLocation = new int[2]; mSnackLayout.getLocationInWindow(recyclerLocation); // 加入点的位置起始点 int[] startLocation = new int[2]; goodsImageView.getLocationInWindow(startLocation); // 购物车的位置终点 int[] endLocation = new int[2]; //iv_shopping_cart是动画运行轨迹最终结束的地方 我这里用的是购物车的图标控件 iv_shopping_cart.getLocationInWindow(endLocation); // TODO: 2018/5/21 0021 考虑到状态栏的问题,否则会往下偏移状态栏的高度 int startX = startLocation[0] - recyclerLocation[0]; int startY = startLocation[1] - recyclerLocation[1]; // TODO: 2018/5/21 0021 和上面同样 int endX = endLocation[0] - recyclerLocation[0]; int endY = endLocation[1] - recyclerLocation[1]; // 开始绘制贝塞尔曲线 Path path = new Path(); // 移动到起始点位置(即贝塞尔曲线的起点) path.moveTo(startX, startY); // 使用二阶贝塞尔曲线:注意第一个起始坐标越大,贝塞尔曲线的横向距离就会越大,通常按照下面的式子取便可 path.quadTo((startX + endX) / 2, startY, endX, endY); // mPathMeasure用来计算贝塞尔曲线的曲线长度和贝塞尔曲线中间插值的坐标,若是是true,path会造成一个闭环 final PathMeasure pathMeasure = new PathMeasure(path, false); // 属性动画实现(从0到贝塞尔曲线的长度之间进行插值计算,获取中间过程的距离值) ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, pathMeasure.getLength()); // 计算距离 int tempX = Math.abs(startX - endX); int tempY = Math.abs(startY - endY); // 根据距离计算时间 int time = (int) (0.3 * Math.sqrt((tempX * tempX) + tempY * tempY)); valueAnimator.setDuration(time); valueAnimator.start(); valueAnimator.setInterpolator(new AccelerateInterpolator()); 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此时就是中间距离点的坐标值 pathMeasure.getPosTan(value, mCurrentPosition, null); // 移动的商品图片(动画图片)的坐标设置为该中间点的坐标 goods.setTranslationX(mCurrentPosition[0]); goods.setTranslationY(mCurrentPosition[1]); } }); valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { // 移除图片 mSnackLayout.removeView(goods); // 购物车数量增长 mTvShoppingCartCount.setText(String.valueOf(allCount)); } }); } }
//mSnackLayout是整个页面布局id mSnackLayout.addView(goods);
//iv_shopping_cart是动画运行轨迹最终结束的地方 我这里用的是购物车的图标控件 iv_shopping_cart.getLocationInWindow(endLocation);
以上。动画