MainActivity 属性动画常用操作
// 可操控的属性有:alpha;x/y;scaleX/scaleY;rotation/rotationX/rotationY;transitionX/transitionY;pivotX/pivotY
public class MainActivity extends ListActivity {
private ImageView iv_src;
private boolean b = true;
private String[] array = { "重启当前Activity\n启动一个新的Activity",//
"最简单的ObjectAnimator,控制scaleX\n最简单的ObjectAnimator,控制rotationX", //
"不存在get/set方法时不会有任何效果\n为Object的某个属性手动提供get/set方法", //
"只有set没有get方法时,get的值不存在,但是set可以正常使用", //
"监听动画更新:AnimatorUpdateListener\n监听动画状态:AnimatorListener", //
"组合动画:AnimatorSet.playTogether\n组合动画:AnimatorSet.with/before/after",//
"组合动画:AnimatorUpdateListener\n组合动画:PropertyValuesHolder",//
"组合动画:PVHolder + KeyFrame\nView的animate动画,最简洁的属性动画",//
"View的animate动画也可以组合动画\n可以在animate动画前/后执行一些操作",//
"最简单的ValueAnimator,控制translationY\n要明白ValueAnimator只是帮你计算插值的" };
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);//全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);//取消标题栏
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(array))));
iv_src = new ImageView(this);
iv_src.setBackgroundColor(0x330000ff);
iv_src.setImageResource(R.drawable.icon);
getListView().addHeaderView(iv_src);
}
@Override
protected void onListItemClick(ListView l, View view, int position, long id) {
switch (position) {
case 0:
b = !b;
Toast.makeText(this, "目前为:" + b, Toast.LENGTH_SHORT).show();
break;
case 1://重启当前Activity 启动一个新的Activity
if (b) {
startActivity(new Intent(this, MainActivity.class));
finish();
} else startActivity(new Intent(this, SecondActivity.class));
break;
case 2://最简单的ObjectAnimator,控制scaleX 最简单的ObjectAnimator,控制rotationX
if (b) ObjectAnimator.ofFloat(iv_src, "scaleX", 0.1f, 3f, 0.1f, 2f).setDuration(1000).start();//X轴缩放2倍
else ObjectAnimator.ofFloat(iv_src, "rotationX", 0.0f, 360.0f).setDuration(500).start();//沿X轴旋转360度
break;
case 3://不存在get/set方法时不会有任何效果 为Object的某个属性手动提供get/set方法
int i = new Random().nextInt(8) + 1;
if (b) ObjectAnimator.ofFloat(iv_src, "width", 100 * i).setDuration(500).start();//没任何效果,但并不会报错
else ObjectAnimator.ofInt(new WrapperView(iv_src), "width", 100 * i).setDuration(1000).start();
break;
case 4://只有set没有get方法时,get的值不存在,但是set可以正常使用
//View有setBackgroundColor方法,但是没有getBackgroundColor方法,所以getBackgroundColor结果为0
if (b) ObjectAnimator.ofArgb(iv_src, "backgroundColor", Color.RED).setDuration(3000).start();//startValue不存在
else ObjectAnimator.ofArgb(iv_src, "backgroundColor", Color.RED, Color.GREEN).setDuration(3000).start();
break;
case 5://监听动画更新:AnimatorUpdateListener 监听动画状态:AnimatorListener
addAnimListener();
break;
case 6://组合动画:AnimatorSet.playTogether 组合动画:AnimatorSet.with/before/after
if (b) combinedAnimPlayTogether();
else combinedAnimAfterBefore();
break;
case 7://组合动画:AnimatorUpdateListener 组合动画:PropertyValuesHolder
if (b) combinedAnimUpdate();
else propertyValuesHolder();
break;
case 8://组合动画:PVHolder + KeyFrame View的animate动画,最简洁的属性动画
if (b) pVHolderKeyFrame();
else iv_src.animate().setDuration(500).y(100 * (new Random().nextInt(12)));//链式调用返回值类型为ViewPropertyAnimator
break;
case 9://View的animate动画也可以组合动画 可以在animate动画前/后执行一些操作
int j = new Random().nextInt(8) + 1;
if (b) iv_src.animate().setDuration(1000).rotation(360 * j).scaleX(j * 0.5f).setInterpolator(new DecelerateInterpolator());
else iv_src.animate().alpha(0).y(1200).setDuration(500).withStartAction(new Runnable() {
@Override
public void run() {
iv_src.setX(200);
}
}).withEndAction(new Runnable() {
@Override
public void run() {
iv_src.setX(0);
iv_src.setY(0);
iv_src.setAlpha(1f);
}
}).start();
break;
case 10://最简单的ValueAnimator,控制translationY 要明白ValueAnimator只是帮你计算插值的
if (b) valueAnimator();
else valueAnimator(position);
break;
}
}
//监听动画绘制过程
public void addAnimListener() {
ObjectAnimator anim = ObjectAnimator.ofFloat(iv_src, "alpha", 0.1f).setDuration(1000);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float value = (Float) animation.getAnimatedValue();
iv_src.setRotationX((1 - value) * 360);
Log.i("bqt", "onAnimationUpdate--" + value);
}
});
//方法一,实现AnimatorListener接口,定义自己的开始、结束、被取消、重复事件
if (b) anim.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.i("bqt", "【onAnimationStart】");
}
@Override
public void onAnimationRepeat(Animator animation) {
Log.i("bqt", "【onAnimationRepeat】");
}
@Override
public void onAnimationEnd(Animator animation) {
Log.i("bqt", "【onAnimationEnd】");
}
@Override
public void onAnimationCancel(Animator animation) {
Log.i("bqt", "【onAnimationCancel】");
}
});
//方法二,继承AnimatorListenerAdapter,只实现自己想实现的事件
else anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
Log.i("bqt", "【继承AnimatorListenerAdapter--onAnimationEnd】");
}
});
anim.start();
}
public void combinedAnimPlayTogether() {
ObjectAnimator anim1 = ObjectAnimator.ofFloat(iv_src, "scaleX", 0.5f, 0.1f, 2f);
ObjectAnimator anim2 = ObjectAnimator.ofFloat(iv_src, "alpha", 0.5f, 1f, 0.5f);
ObjectAnimator anim3 = ObjectAnimator.ofFloat(iv_src, "rotationX", 500);
AnimatorSet animSet = new AnimatorSet().setDuration(2000);
animSet.playTogether(anim1, anim2, anim3);
animSet.start();
}
public void combinedAnimAfterBefore() {
ObjectAnimator anim1 = ObjectAnimator.ofFloat(iv_src, "scaleX", 1.0f, 2f);
ObjectAnimator anim2 = ObjectAnimator.ofFloat(iv_src, "scaleY", 0.1f, 1f);
ObjectAnimator anim3 = ObjectAnimator.ofFloat(iv_src, "x", 0, -iv_src.getWidth(), 0);
ObjectAnimator anim4 = ObjectAnimator.ofFloat(iv_src, "y", 0, iv_src.getY() + 500f, 0);
ObjectAnimator anim5 = ObjectAnimator.ofFloat(iv_src, "rotationX", 360 * 2);
AnimatorSet animSet = new AnimatorSet();
animSet.play(anim1).with(anim2);//anim1,anim2同时执行
animSet.play(anim2).with(anim3);//anim1,anim2,anim3同时执行
animSet.play(anim4).after(anim3).before(anim5);//anim4在anim1,anim2,anim3之后,在anim5之前
animSet.setDuration(1000);
animSet.start();
}
public void combinedAnimUpdate() {
ObjectAnimator anim = ObjectAnimator.ofFloat(iv_src, "包青天", 0.5f, 0.1f, 2f).setDuration(2000);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float cVal = (Float) animation.getAnimatedValue();
iv_src.setScaleX(cVal);
iv_src.setAlpha(cVal);
iv_src.setRotationX(cVal * 360);
}
});
anim.start();
}
public void propertyValuesHolder() {
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 0.2f, 1f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 0.8f, 0, 2f);
PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("rotationY", 360 * 2f, 0);
ObjectAnimator.ofPropertyValuesHolder(iv_src, pvhX, pvhY, pvhZ).setDuration(1000).start();//三个动画是同时执行的
}
public void pVHolderKeyFrame() {
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
kf1.setInterpolator(new AccelerateInterpolator());
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator.ofPropertyValuesHolder(iv_src, pvhRotation).setDuration(1000).start();
}
public void valueAnimator() {
ValueAnimator animator = ValueAnimator.ofFloat(0, 1200, 0).setDuration(500);//这里并没有设置要操作的【对象】及【对象的属性】
animator.addUpdateListener(new AnimatorUpdateListener() {//对ValueAnimator来说,这是必要的,否则没有任何意义
@Override
public void onAnimationUpdate(ValueAnimator animation) {//在这里可以根据属性值定义自己想要的动作
Float f = (Float) animation.getAnimatedValue();//这里只能强转为of**时指定的类型,甚至不能强转为Integer类型(因为不是基本类型 int)
iv_src.setTranslationY(f);//设置要操作的对象的属性。或者你可以使用获取到的值做任何事情
}
});
animator.start();
}
public void valueAnimator(final int position) {
ValueAnimator animator = ValueAnimator.ofArgb(Color.RED).setDuration(5000);
animator.setTarget(iv_src);//设置要操作的对象,对ValueAnimator来说,这个方法是空方法,因为它不会作用在任何View上
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer color = (Integer) animation.getAnimatedValue();
iv_src.setRotationY(color);
for (int j = 1; j < position; j++) {
if (j % 2 == 0) getListView().getChildAt(j).setBackgroundColor(color);
else getListView().getChildAt(j).setBackgroundColor(color | Color.GREEN);
}
}
});
animator.start();
}
}SecondActivity 属性动画补充
包含以下演示
- 自定义TypeEvaluator实现抛物线动画效果
- 使用LayoutTransition为布局容器中子View的显示与消失设置过渡动画
- 使用LayoutAnimationController为布局容器中的控件播放同样的动画
public class SecondActivity extends ListActivity {
private final String[][] array = { INTERPOLATORS1, INTERPOLATORS2, INTERPOLATORS3, INTERPOLATORS4, INTERPOLATORS5, INTERPOLATORS6, };
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);//全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);//取消标题栏
String[] names = { "-1-", "-2-", "-3-", "-4-", "-5-", "-6-", //
"自定义TypeEvaluator实现抛物线动画效果", //
"使用LayoutTransition为布局容器中子View的显示与消失设置过渡动画", //
"使用LayoutAnimationController为布局容器中的控件播放同样的动画", };
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(names))));
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position) {
case 6:
startActivity(new Intent(this, TypeEvaluatorActivity.class));
break;
case 7:
startActivity(new Intent(this, LayoutTransitionActivity.class));
break;
case 8:
startActivity(new Intent(this, LayoutAnimationControllerActivity.class));
break;
default:
Intent intent = new Intent(this, InterpolatorActivity.class);
intent.putExtra(InterpolatorActivity.EXTRA_NAME, array[position]);
startActivity(intent);
break;
}
}
public static final String[] INTERPOLATORS1 = new String[] { "EaseBackInInterpolator", "EaseBackInOutInterpolator", "EaseBackOutInterpolator",
"EaseBounceInInterpolator", "EaseBounceInOutInterpolator", "EaseBounceOutInterpolator", "EaseBreathInterpolator", "EaseCircularInInterpolator",
"EaseCircularInOutInterpolator", "EaseCircularOutInterpolator", };
public static final String[] INTERPOLATORS2 = new String[] { "EaseCubicInInterpolator", "EaseCubicInOutInterpolator", "EaseCubicOutInterpolator",
"EaseExponentialInInterpolator", "EaseExponentialInOutInterpolator", "EaseExponentialOutInterpolator", "EaseInBackInterpolator",
"EaseInBounceInterpolator", "EaseInCircInterpolator", "EaseInCubicInterpolator", };
public static final String[] INTERPOLATORS3 = new String[] { "EaseInElasticInterpolator", "EaseInExpoInterpolator", "EaseInOutBackInterpolator",
"EaseInOutBounceInterpolator", "EaseInOutCircInterpolator", "EaseInOutCubicInterpolator", "EaseInOutElasticInterpolator",
"EaseInOutExpoInterpolator", "EaseInOutQuadInterpolator", "EaseInOutQuartInterpolator", };
public static final String[] INTERPOLATORS4 = new String[] { "EaseInOutQuintInterpolator", "EaseInOutSineInterpolator", "EaseInQuadInterpolator",
"EaseInQuartInterpolator", "EaseInQuintInterpolator", "EaseInSineInterpolator", "EaseOutBackInterpolator", "EaseOutBounceInterpolator",
"EaseOutCircInterpolator", "EaseOutCubicInterpolator", };
public static final String[] INTERPOLATORS5 = new String[] { "EaseOutElasticInterpolator", "EaseOutExpoInterpolator", "EaseOutQuadInterpolator",
"EaseOutQuartInterpolator", "EaseOutQuintInterpolator", "EaseOutSineInterpolator", "EaseQuadInInterpolator", "EaseQuadInOutInterpolator",
"EaseQuadOutInterpolator", "EaseQuartInInterpolator", };
public static final String[] INTERPOLATORS6 = new String[] { "EaseQuartInOutInterpolator", "EaseQuartOutInterpolator", "EaseQuintInInterpolator",
"EaseQuintInOutInterpolator", "EaseQuintOutInterpolator", };
}自定义TypeEvaluator 实现抛物线效果
public class TypeEvaluatorActivity extends Activity {
private ImageView iv_src;
private boolean b = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);//全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);//取消标题栏
Bitmap bitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setAntiAlias(true);
paint.setDither(true);
canvas.drawCircle(10, 10, 10, paint);
iv_src = new ImageView(this);
iv_src.setImageBitmap(bitmap);
setContentView(iv_src, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
b = !b;
Point point = new Point();
((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(point);
ValueAnimator animator = new ValueAnimator().setDuration(1000);
//通过new创建的ValueAnimator要明确的setObjectValues和setEvaluator,并且一定要先setObjectValues,再setEvaluator
if (b) animator.setObjectValues(new PointF(0, 0), new PointF(point.x - iv_src.getWidth(), point.y - iv_src.getHeight()));
else animator.setObjectValues(new PointF(0, point.y - iv_src.getHeight()), new PointF(point.x - iv_src.getWidth(), 0));
animator.setEvaluator(new PointFEvaluator());//PointFEvaluator为自定义的估值器,其实就是用来封装你需要的数据用的
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF point = (PointF) animation.getAnimatedValue();
iv_src.setX(point.x);
iv_src.setY(point.y);
}
});
animator.start();
}
return super.onTouchEvent(event);
}
}自定义的PointFEvaluator
public class PointFEvaluator implements TypeEvaluator<PointF> {
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
//只要能保证:当fraction=0时返回值为startValue,并且当fraction=1时返回值为endValue,就是一个比较合理的函数
PointF pointF = new PointF();
pointF.x = startValue.x + fraction * (endValue.x - startValue.x);// x方向匀速移动
pointF.y = startValue.y + fraction * fraction * (endValue.y - startValue.y);// y方向抛物线加速移动
return pointF;
}
}LayoutTransition 属性动画_布局动画
public class LayoutTransitionActivity extends Activity implements OnCheckedChangeListener, OnClickListener {
private GridLayout gl_container;//父布局
//布局动画,当容器中的视图发生变化时存在过渡的动画效果。默认为全部开启状态,使用的是默认的动画效果,但是这些都是可以更改的
private LayoutTransition mTransition = new LayoutTransition();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout);
gl_container = (GridLayout) findViewById(R.id.gl_container);
findViewById(R.id.btn).setOnClickListener(this);
((CheckBox) findViewById(R.id.appear)).setOnCheckedChangeListener(this);
((CheckBox) findViewById(R.id.change_appear)).setOnCheckedChangeListener(this);
((CheckBox) findViewById(R.id.disappear)).setOnCheckedChangeListener(this);
((CheckBox) findViewById(R.id.change_disappear)).setOnCheckedChangeListener(this);
((CheckBox) findViewById(R.id.layout_attr)).setOnCheckedChangeListener(this);
gl_container.setLayoutTransition(mTransition);
}
@Override
public void onCheckedChanged(CompoundButton view, boolean isChecked) {
if (view.getId() == R.id.layout_attr) {//等价于xml中【animateLayoutChanges】属性为false(默认)还是true
gl_container.setLayoutTransition(isChecked ? new LayoutTransition() : null);
return;
} else {
mTransition = new LayoutTransition();
switch (view.getId()) {
case R.id.appear://当一个View在ViewGroup中【出现】时,对【此View】设置的动画
mTransition.setAnimator(LayoutTransition.APPEARING, isChecked ? mTransition.getAnimator(LayoutTransition.APPEARING) : null);
break;
case R.id.change_appear://当一个View在ViewGroup中【出现】时,对此View对其他View位置造成影响,对【其他View】设置的动画
mTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, isChecked ? mTransition.getAnimator(LayoutTransition.CHANGE_APPEARING) : null);
break;
case R.id.disappear://当一个View在ViewGroup中【消失】时,对【此View】设置的动画
mTransition.setAnimator(LayoutTransition.DISAPPEARING, isChecked ? mTransition.getAnimator(LayoutTransition.DISAPPEARING) : null);
break;
case R.id.change_disappear:////当一个View在ViewGroup中【消失】时,对此View对其他View位置造成影响,对【其他View】设置的动画
mTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, isChecked ? mTransition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING) : null);
break;
}
gl_container.setLayoutTransition(mTransition);
}
}
@Override
public void onClick(View view) {
final Button button = new Button(this);
button.setText(gl_container.getChildCount() + 1 + "");
gl_container.addView(button, gl_container.getChildCount());//放置在最后那个位置
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
gl_container.removeView(button);
}
});
}
}LayoutAnimationController 布局动画
public class LayoutAnimationControllerActivity extends ListActivity {
String[] array = { "添加HeaderView", "移除HeaderView", "添加移除View时没任何卵用",//
"delay位间隔时间,单位为秒", "animation为设置动画的文件", "animationOrder为进入方式" };
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(array))));
Animation animation = AnimationUtils.loadAnimation(this, android.R.anim.slide_in_left);//先加载【补间动画】
LayoutAnimationController lac = new LayoutAnimationController(animation);//再设置布局动画
lac.setOrder(LayoutAnimationController.ORDER_NORMAL);//设置控件显示的顺序;normal=0 默认,reverse=1 倒序,random=2 随机
lac.setDelay(0.6f);//设置控件显示间隔时间,注意单位是秒;可以为70%,也可以是一个浮点数
getListView().setLayoutAnimation(lac);//在XML中设置时是使用 android:layoutAnimation="@anim/layout_anim"
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position - getListView().getHeaderViewsCount()) {
case 0:
ImageView iv = new ImageView(this);
iv.setImageResource(R.drawable.ic_launcher);
getListView().addHeaderView(iv);
break;
case 1:
if (getListView().getHeaderViewsCount() > 0) getListView().removeHeaderView(getListView().getChildAt(0));
break;
}
}
}自定义Interpolator 实现各种加速效果
public class InterpolatorActivity extends ListActivity {
private ImageView iv_src;
private ObjectAnimator mAnimator;
private static final String IN_PG_NAME = "com.bqt.anim.interpolator.";
public static final String EXTRA_NAME = "interpolators";
private String[] mInterpolators;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);//全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);//取消标题栏
mInterpolators = getIntent().getStringArrayExtra(EXTRA_NAME);
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(mInterpolators))));
iv_src = new ImageView(this);
iv_src.setBackgroundColor(0x330000ff);
iv_src.setImageResource(R.drawable.icon);
getListView().addHeaderView(iv_src);
DisplayMetrics metric = new DisplayMetrics();
((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metric);
mAnimator = ObjectAnimator.ofFloat(iv_src, "y", 0, metric.heightPixels, 0).setDuration(1500);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
if (position == 0) {
Intent intent = new Intent(this, InterpolatorActivity.class);
intent.putExtra(InterpolatorActivity.EXTRA_NAME, mInterpolators);
startActivity(intent);
finish();
} else selectDrawerItem(position-1);
}
private void selectDrawerItem(int position) {
String name = mInterpolators[position];
TimeInterpolator interpolator = null;
try {
Class<?> clazz = Class.forName(IN_PG_NAME + name);
interpolator = (TimeInterpolator) clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (interpolator != null) {
mAnimator.cancel();
mAnimator.setInterpolator(interpolator);
mAnimator.start();
}
}
}附件列表