Android 利用2次贝塞尔曲线模仿购物车添加物品抛物线动画
0.首先,先给出1张效果gif图。
1.贝塞尔曲线原理及相干公式参考:http://www.jianshu.com/p/c0d7ad796cee 作者:许方镇 。
2.原理:计算被点击 view、购物车view 和他们所在父容器相对屏幕的坐标。
3.在呗点击View坐标位置 父容器通过addView 增加需要完成动画的imgview。
4.自定义估值器 通过2次贝塞尔曲线公式(2个数据点,1个控制点)完成抛物线路径上的点xy坐标计算。
5.利用属性动画 +自定义估值器 完成imgview在父容器内部的抛物线动画。
6.先给布局,其中包括1个ListView、 1个ImageView 、需要用到的父容器。
<?xml version="1.0" encoding="utf⑻"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00ffe1"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<RelativeLayout
android:id="@+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/main_lv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#0011ff"
android:dividerHeight="2dp"/>
<!-- shop img-->
<ImageView
android:id="@+id/main_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:layout_marginLeft="20dp"
android:src="@mipmap/shop"/>
</RelativeLayout>
</LinearLayout>
7. 给出ListView Item 布局:
<?xml version="1.0" encoding="utf⑻"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFF"
android:padding="30dp">
<TextView
android:id="@+id/item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="#F00"
android:textSize="20sp"/>
<ImageView
android:id="@+id/item_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:src="@mipmap/add"/>
</RelativeLayout>
8.给出ListView Adapter代码 仅仅是点击时增加了回调接口:
public class ItemAdapter extends BaseAdapter implements View.OnClickListener {
List<String> data = new ArrayList<>();
Context mContext;
public ItemAdapter(Context context) {
mContext = context;
for (int i = 0; i < 30; i++) {
data.add("item+" + i);
}
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.item, parent, false);
convertView.setTag(new ViewH(convertView));
}
ViewH holder = (ViewH) convertView.getTag();
holder.tv.setText(data.get(position));
holder.img.setOnClickListener(this);
return convertView;
}
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.add(v);
}
}
private AddClickListener mListener;
public void setListener(AddClickListener listener) {
mListener = listener;
}
public interface AddClickListener {
void add(View v);
}
public static class ViewH {
private ImageView img;
private TextView tv;
public ViewH(View view) {
img = ((ImageView) view.findViewById(R.id.item_img));
tv = ((TextView) view.findViewById(R.id.item_text));
}
}
}
9.其中自定义MoveImageView仅仅是增加了1个set方法方便属性动画 update时调用。
public class MoveImageView extends ImageView {
public MoveImageView(Context context) {
super(context);
}
public void setMPointF(PointF pointF) {
setX(pointF.x);
setY(pointF.y);
}
}
10.重要的实现在Activity部份:
public class MainActivity extends AppCompatActivity implements ItemAdapter.AddClickListener, Animator.AnimatorListener {
private ImageView shopImg;//购物车 IMG
private RelativeLayout container;//ListView 购物车View的父布局
private ListView itemLv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViews();
initViews();
}
private void initViews() {
ItemAdapter adapter = new ItemAdapter(this);
//当前Activity实现 adapter内部 点击的回调
adapter.setListener(this);
itemLv.setAdapter(adapter);
}
/**
* ListView + 点击回调方法
*/
@Override
public void add(View addV) {
int[] childCoordinate = new int[2];
int[] parentCoordinate = new int[2];
int[] shopCoordinate = new int[2];
//1.分别获得被点击View、父布局、购物车在屏幕上的坐标xy。
addV.getLocationInWindow(childCoordinate);
container.getLocationInWindow(parentCoordinate);
shopImg.getLocationInWindow(shopCoordinate);
//2.自定义ImageView 继承ImageView
MoveImageView img = new MoveImageView(this);
img.setImageResource(R.mipmap.heart1);
//3.设置img在父布局中的坐标位置
img.setX(childCoordinate[0] - parentCoordinate[0]);
img.setY(childCoordinate[1] - parentCoordinate[1]);
//4.父布局添加该Img
container.addView(img);
//5.利用 2次贝塞尔曲线 需首先计算出 MoveImageView的2个数据点和1个控制点
PointF startP = new PointF();
PointF endP = new PointF();
PointF controlP = new PointF();
//开始的数据点坐标就是 addV的坐标
startP.x = childCoordinate[0] - parentCoordinate[0];
startP.y = childCoordinate[1] - parentCoordinate[1];
//结束的数据点坐标就是 shopImg的坐标
endP.x = shopCoordinate[0] - parentCoordinate[0];
endP.y = shopCoordinate[1] - parentCoordinate[1];
//控制点坐标 x等于 购物车x;y等于 addV的y
controlP.x = endP.x;
controlP.y = startP.y;
//启动属性动画
ObjectAnimator animator = ObjectAnimator.ofObject(img, "mPointF",
new PointFTypeEvaluator(controlP), startP, endP);
animator.setDuration(1000);
animator.addListener(this);
animator.start();
}
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
//动画结束后 父布局移除 img
Object target = ((ObjectAnimator) animation).getTarget();
container.removeView((View) target);
//shopImg 开始1个放大动画
Animation scaleAnim = AnimationUtils.loadAnimation(this, R.anim.shop_scale);
shopImg.startAnimation(scaleAnim);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
/**
* 自定义估值器
*/
public class PointFTypeEvaluator implements TypeEvaluator<PointF> {
/**
* 每一个估值器对应1个属性动画,每一个属性动画仅对应唯逐一个控制点
*/
PointF control;
/**
* 估值器返回值
*/
PointF mPointF = new PointF();
public PointFTypeEvaluator(PointF control) {
this.control = control;
}
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
return getBezierPoint(startValue, endValue, control, fraction);
}
/**
* 2次贝塞尔曲线公式
*
* @param start 开始的数据点
* @param end 结束的数据点
* @param control 控制点
* @param t float 0⑴
* @return 不同t对应的PointF
*/
private PointF getBezierPoint(PointF start, PointF end, PointF control, float t) {
mPointF.x = (1 - t) * (1 - t) * start.x + 2 * t * (1 - t) * control.x + t * t * end.x;
mPointF.y = (1 - t) * (1 - t) * start.y + 2 * t * (1 - t) * control.y + t * t * end.y;
return mPointF;
}
}
private void findViews() {
shopImg = (ImageView) findViewById(R.id.main_img);
container = (RelativeLayout) findViewById(R.id.main_container);
itemLv = (ListView) findViewById(R.id.main_lv);
}
}
11.购物车有1个Scale的补间动画:
<?xml version="1.0" encoding="utf⑻"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200"
android:repeatCount="1"
android:repeatMode="reverse">
<scale
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.2"
android:toYScale="1.2"/>
</set>
12.供参考~完~~