Material Design - Curved motion

在 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>  

你可能感兴趣的:(Material Design - Curved motion)