在 5.0 上 提供了很多动画效果方面的 优化 和 设置
在android5.0(api21)及以上,允许自定义这些动画:
1. Touch feedback 触摸反馈
2. Circular Reveal 圆形显示
3. Activity transitions 过渡动画
4. Curved motion 曲线运动
5. View state changes 视图状态变化
4. Curved motion 曲线运动
Material design中的动画依靠曲线,这个曲线适用于时间插值器和控件运动模式。
PathInterpolator类是一个基于贝塞尔曲线(Bézier curve)或路径(Path)对象上的新的插值器。这个插值器指定了一个1x1的方形运动曲线,用(0,0)点和(1,1)定位点还有控制点来作为构造方法中的参数。你还可以定义一个XML资源的路径插值器:
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" android:controlX1="0.4" android:controlY1="0" android:controlX2="1" android:controlY2="1"/>
在materialdesign规范中,系统提供了三个基本的曲线:
@interpolator/fast_out_linear_in.xml
@interpolator/fast_out_slow_in.xml
@interpolator/linear_out_slow_in.xml
你可以传递一个PathInterpolator对象给Animator.setInterpolator()方法。。
ObjectAnimator类有的构造方法,使你能够一次能同时使用两个或多个属性去绘制动画的路径。
例如,下面的动画使用一个Path对象进行视图X和Y属性的动画绘制:
ObjectAnimator mAnimator;
mAnimator = ObjectAnimator.ofFloat(view, View.X, View.Y, path);
…
mAnimator.start();
ObjectAnimator.ofArgb
ObjectAnimator.ofFloat
ObjectAnimator.ofInt
ObjectAnimator.ofMultiFloat
ObjectAnimator.ofObject
ObjectAnimator.ofPropertyValuesHolder
4.2相关代码示例可以参考:
android-sdk-windows\samples\android-21\legacy\ApiDemos\src\com\example\android\apis\animation\PathAnimations.java
PathAnimation.java
/** This application demonstrates the use of Path animation. */
public class PathAnimations extends Activity implements
RadioGroup.OnCheckedChangeListener, View.OnLayoutChangeListener {
final static Path sTraversalPath = new Path();
final static float TRAVERSE_PATH_SIZE = 7.0f;
final static Property<PathAnimations, Point> POINT_PROPERTY
= new Property<PathAnimations, Point>(Point.class, "point") {
@Override
public Point get(PathAnimations object) {
View v = object.findViewById(R.id.moved_item);
return new Point(Math.round(v.getX()), Math.round(v.getY()));
}
@Override
public void set(PathAnimations object, Point value) {
object.setCoordinates(value.x, value.y);
}
};
static {
float inverse_sqrt8 = FloatMath.sqrt(0.125f);
RectF bounds = new RectF(1, 1, 3, 3);
sTraversalPath.addArc(bounds, 45, 180);
sTraversalPath.addArc(bounds, 225, 180);
bounds.set(1.5f + inverse_sqrt8, 1.5f + inverse_sqrt8, 2.5f + inverse_sqrt8,
2.5f + inverse_sqrt8);
sTraversalPath.addArc(bounds, 45, 180);
sTraversalPath.addArc(bounds, 225, 180);
bounds.set(4, 1, 6, 3);
sTraversalPath.addArc(bounds, 135, -180);
sTraversalPath.addArc(bounds, -45, -180);
bounds.set(4.5f - inverse_sqrt8, 1.5f + inverse_sqrt8, 5.5f - inverse_sqrt8, 2.5f + inverse_sqrt8);
sTraversalPath.addArc(bounds, 135, -180);
sTraversalPath.addArc(bounds, -45, -180);
sTraversalPath.addCircle(3.5f, 3.5f, 0.5f, Path.Direction.CCW);
sTraversalPath.addArc(new RectF(1, 2, 6, 6), 0, 180);
}
private CanvasView mCanvasView;
private ObjectAnimator mAnimator;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.path_animations);
mCanvasView = (CanvasView) findViewById(R.id.canvas);
mCanvasView.addOnLayoutChangeListener(this);
((RadioGroup) findViewById(R.id.path_animation_type)).setOnCheckedChangeListener(this);
}
public void setCoordinates(int x, int y) {
changeCoordinates((float) x, (float) y);
}
public void changeCoordinates(float x, float y) {
View v = findViewById(R.id.moved_item);
v.setX(x);
v.setY(y);
}
public void setPoint(PointF point) {
changeCoordinates(point.x, point.y);
}
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
startAnimator(checkedId);
}
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
int checkedId = ((RadioGroup)findViewById(R.id.path_animation_type)).getCheckedRadioButtonId();
if (checkedId != RadioGroup.NO_ID) {
startAnimator(checkedId);
}
}
private void startAnimator(int checkedId) {
if (mAnimator != null) {
mAnimator.cancel();
mAnimator = null;
}
View view = findViewById(R.id.moved_item);
Path path = mCanvasView.getPath();
if (path.isEmpty()) {
return;
}
switch (checkedId) {
case R.id.named_components:
// Use the named "x" and "y" properties for individual (x, y)
// coordinates of the Path and set them on the view object.
// The setX(float) and setY(float) methods are called on view.
// An int version of this method also exists for animating
// int Properties.
mAnimator = ObjectAnimator.ofFloat(view, "x", "y", path);
break;
case R.id.property_components:
// Use two Properties for individual (x, y) coordinates of the Path
// and set them on the view object.
// An int version of this method also exists for animating
// int Properties.
mAnimator = ObjectAnimator.ofFloat(view, View.X, View.Y, path);
break;
case R.id.multi_int:
// Use a multi-int setter to animate along a Path. The method
// setCoordinates(int x, int y) is called on this during the animation.
// Either "setCoordinates" or "coordinates" are acceptable parameters
// because the "set" can be implied.
mAnimator = ObjectAnimator.ofMultiInt(this, "setCoordinates", path);
break;
case R.id.multi_float:
// Use a multi-float setter to animate along a Path. The method
// changeCoordinates(float x, float y) is called on this during the animation.
mAnimator = ObjectAnimator.ofMultiFloat(this, "changeCoordinates", path);
break;
case R.id.named_setter:
// Use the named "point" property to animate along the Path.
// There must be a method setPoint(PointF) on the animated object.
// Because setPoint takes a PointF parameter, no TypeConverter is necessary.
// In this case, the animated object is PathAnimations.
mAnimator = ObjectAnimator.ofObject(this, "point", null, path);
break;
case R.id.property_setter:
// Use the POINT_PROPERTY property to animate along the Path.
// POINT_PROPERTY takes a Point, not a PointF, so the TypeConverter
// PointFToPointConverter is necessary.
mAnimator = ObjectAnimator.ofObject(this, POINT_PROPERTY,
new PointFToPointConverter(), path);
break;
}
mAnimator.setDuration(10000);
mAnimator.setRepeatMode(Animation.RESTART);
mAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.start();
}
public static class CanvasView extends FrameLayout {
Path mPath = new Path();
Paint mPathPaint = new Paint();
public CanvasView(Context context) {
super(context);
init();
}
public CanvasView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CanvasView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
setWillNotDraw(false);
mPathPaint.setColor(0xFFFF0000);
mPathPaint.setStrokeWidth(2.0f);
mPathPaint.setStyle(Paint.Style.STROKE);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
Matrix scale = new Matrix();
float scaleWidth = (right-left)/TRAVERSE_PATH_SIZE;
float scaleHeight= (bottom-top)/TRAVERSE_PATH_SIZE;
scale.setScale(scaleWidth, scaleHeight);
sTraversalPath.transform(scale, mPath);
}
}
public Path getPath() {
return mPath;
}
@Override
public void draw(Canvas canvas) {
canvas.drawPath(mPath, mPathPaint);
super.draw(canvas);
}
}
private static class PointFToPointConverter extends TypeConverter<PointF, Point> {
Point mPoint = new Point();
public PointFToPointConverter() {
super(PointF.class, Point.class);
}
@Override
public Point convert(PointF value) {
mPoint.set(Math.round(value.x), Math.round(value.y));
return mPoint;
}
}
}
path_animations.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
<ScrollView android:layout_width="match_parent" android:layout_height="wrap_content">
<RadioGroup android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/path_animation_type" >
<RadioButton android:id="@+id/named_components" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Named Components"/>
<RadioButton android:id="@+id/property_components" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Property Components"/>
<RadioButton android:id="@+id/multi_int" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Multi-int"/>
<RadioButton android:id="@+id/multi_float" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Multi-float"/>
<RadioButton android:id="@+id/named_setter" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Named Property"/>
<RadioButton android:id="@+id/property_setter" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Property"/>
</RadioGroup>
</ScrollView>
<view class="com.example.android.apis.animation.PathAnimations$CanvasView" android:id="@+id/canvas" android:layout_width="match_parent" android:layout_height="0px" android:layout_weight="1">
<ImageView android:id="@+id/moved_item" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/frog"/>
</view>
</LinearLayout>