原文网址
根据以上方法结合自己的框架修改实现效果。
0 效果
1 Item布局
<?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布局效果如下图所示,主要是对加号和减号做动画。
2 Adapter写法
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);
}
});
}
}
3 购物车页面布局
<?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>
购物车页面布局效果
4 功能实现
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));
}
});
}
}
特别注意:
(1)
//mSnackLayout是整个页面布局id
mSnackLayout.addView(goods);
(2)
//iv_shopping_cart是动画运行轨迹最终结束的地方 我这里用的是购物车的图标控件
iv_shopping_cart.getLocationInWindow(endLocation);
以上。