Android共享元素场景切换动画的实现

安卓5.0系统引入了共享元素,能做出非常炫酷的场景切换效果,这让人非常兴奋同时非常蛋疼,因为低版本没法使用啊,所以今天就跟大家分享一下自己写的一个库,其实只有2个文件而已就可以兼容安卓5.0以下的版本。


重要的工具类

import android.animation.Animator;
import android.animation.TimeInterpolator;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.view.ViewTreeObserver;

import java.util.ArrayList;

/**
 * Tool for transition between two activities
 * <br/>
 * Created by huzn on 2017/5/8.
 */
public class EasyTransition {

    public static final String EASY_TRANSITION_OPTIONS = "easy_transition_options";
    public static final long DEFAULT_TRANSITION_ANIM_DURATION = 1000;

    /**
     * Start Activity with transition options
     *
     * @param intent  The intent to start
     * @param options Transition options, using {@link EasyTransitionOptions#makeTransitionOptions(Activity, View...)}
     *                to build your options
     */
    public static void startActivity(Intent intent, EasyTransitionOptions options) {
        options.update();
        intent.putParcelableArrayListExtra(EASY_TRANSITION_OPTIONS, options.getAttrs());
        Activity activity = options.getActivity();
        activity.startActivity(intent);
        activity.overridePendingTransition(0, 0);
    }

    /**
     * Start Activity for result, with transition options
     *
     * @param intent      The intent to start
     * @param requestCode If >= 0, this code will be returned in onActivityResult() when the activity exits,
     *                    see {@link Activity#startActivityForResult(Intent, int)}
     * @param options     Transition options, using {@link EasyTransitionOptions#makeTransitionOptions(Activity, View...)}
     *                    to build your options
     */
    public static void startActivityForResult(Intent intent, int requestCode, EasyTransitionOptions options) {
        options.update();
        intent.putParcelableArrayListExtra(EASY_TRANSITION_OPTIONS, options.getAttrs());
        Activity activity = options.getActivity();
        activity.startActivityForResult(intent, requestCode);
        activity.overridePendingTransition(0, 0);
    }

    /**
     * Enter the Activity, invoking this method to start enter transition animations
     *
     * @param activity     The Activity entering
     * @param duration     The duration of enter transition animation
     * @param interpolator The TimeInterpolator of enter transition animation
     * @param listener     Animator listener, normally you can do your initial after animation end
     */
    public static void enter(Activity activity, long duration, TimeInterpolator interpolator, Animator.AnimatorListener listener) {
        Intent intent = activity.getIntent();
        ArrayList<EasyTransitionOptions.ViewAttrs> attrs =
                intent.getParcelableArrayListExtra(EASY_TRANSITION_OPTIONS);
        runEnterAnimation(activity, attrs, duration, interpolator, listener);
    }

    /**
     * The same as {@link EasyTransition#enter(Activity, long, TimeInterpolator, Animator.AnimatorListener)}
     * with no interpolator
     */
    public static void enter(Activity activity, long duration, Animator.AnimatorListener listener) {
        enter(activity, duration, null, listener);
    }

    /**
     * The same as {@link EasyTransition#enter(Activity, long, TimeInterpolator, Animator.AnimatorListener)}
     * with default duration
     */
    public static void enter(Activity activity, TimeInterpolator interpolator, Animator.AnimatorListener listener) {
        enter(activity, DEFAULT_TRANSITION_ANIM_DURATION, interpolator, listener);
    }

    /**
     * The same as {@link EasyTransition#enter(Activity, long, TimeInterpolator, Animator.AnimatorListener)}
     * with default duration and no interpolator
     */
    public static void enter(Activity activity, Animator.AnimatorListener listener) {
        enter(activity, DEFAULT_TRANSITION_ANIM_DURATION, null, listener);
    }

    /**
     * The same as {@link EasyTransition#enter(Activity, long, TimeInterpolator, Animator.AnimatorListener)}
     * with default duration, no interpolator and no listener
     */
    public static void enter(Activity activity) {
        enter(activity, DEFAULT_TRANSITION_ANIM_DURATION, null, null);
    }

    private static void runEnterAnimation(Activity activity,
                                          ArrayList<EasyTransitionOptions.ViewAttrs> attrs,
                                          final long duration,
                                          final TimeInterpolator interpolator,
                                          final Animator.AnimatorListener listener) {
        if (null == attrs || attrs.size() == 0)
            return;

        for (final EasyTransitionOptions.ViewAttrs attr : attrs) {
            final View view = activity.findViewById(attr.id);

            if (null == view)
                continue;

            view.getViewTreeObserver()
                    .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                        @Override
                        public boolean onPreDraw() {
                            view.getViewTreeObserver().removeOnPreDrawListener(this);

                            int[] location = new int[2];
                            view.getLocationOnScreen(location);
                            view.setPivotX(0);
                            view.setPivotY(0);
                            view.setScaleX(attr.width / view.getWidth());
                            view.setScaleY(attr.height / view.getHeight());
                            view.setTranslationX(attr.startX - location[0]); // xDelta
                            view.setTranslationY(attr.startY - location[1]); // yDelta

                            view.animate()
                                    .scaleX(1)
                                    .scaleY(1)
                                    .translationX(0)
                                    .translationY(0)
                                    .setDuration(duration)
                                    .setInterpolator(interpolator)
                                    .setListener(listener);
                            return true;
                        }
                    });
        }
    }

    /**
     * Exit the Activity, invoke this method to start exit transition animation,
     * the shared views must have same ids, or it will throws NullPointerException
     *
     * @param activity     The Activity Exiting
     * @param interpolator The TimeInterpolator of exit transition animation
     * @param duration     The duration of exit transition animation
     * @throws NullPointerException throws if shared views not found in The Activity Exiting
     */
    public static void exit(Activity activity, long duration, TimeInterpolator interpolator) {
        Intent intent = activity.getIntent();
        ArrayList<EasyTransitionOptions.ViewAttrs> attrs = intent.getParcelableArrayListExtra(EASY_TRANSITION_OPTIONS);
        runExitAnimation(activity, attrs, duration, interpolator);
    }

    /**
     * The same as {@link EasyTransition#exit(Activity, long, TimeInterpolator)}
     * with default duration
     */
    public static void exit(Activity activity, TimeInterpolator interpolator) {
        exit(activity, DEFAULT_TRANSITION_ANIM_DURATION, interpolator);
    }

    /**
     * The same as {@link EasyTransition#exit(Activity, long, TimeInterpolator)}
     * with no interpolator
     */
    public static void exit(Activity activity, long duration) {
        exit(activity, duration, null);
    }

    /**
     * The same as {@link EasyTransition#exit(Activity, long, TimeInterpolator)}
     * with default duration and no interpolator
     */
    public static void exit(Activity activity) {
        exit(activity, DEFAULT_TRANSITION_ANIM_DURATION, null);
    }

    private static void runExitAnimation(final Activity activity,
                                         ArrayList<EasyTransitionOptions.ViewAttrs> attrs,
                                         long duration,
                                         TimeInterpolator interpolator) {
        if (null == attrs || attrs.size() == 0)
            return;

        for (final EasyTransitionOptions.ViewAttrs attr : attrs) {
            View view = activity.findViewById(attr.id);
            int[] location = new int[2];
            view.getLocationOnScreen(location);
            view.setPivotX(0);
            view.setPivotY(0);

            view.animate()
                    .scaleX(attr.width / view.getWidth())
                    .scaleY(attr.height / view.getHeight())
                    .translationX(attr.startX - location[0])
                    .translationY(attr.startY - location[1])
                    .setInterpolator(interpolator)
                    .setDuration(duration);
        }

        activity.findViewById(attrs.get(0).id).postDelayed(new Runnable() {
            @Override
            public void run() {
                activity.finish();
                activity.overridePendingTransition(0, 0);
            }
        }, duration);
    }
}
import android.app.Activity;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;

import java.util.ArrayList;

/**
 * Transition options, using {@link EasyTransitionOptions#makeTransitionOptions(Activity, View...)}
 * to build your options
 * <br/>
 * Created by huzn on 2017/5/8.
 */
public class EasyTransitionOptions {

    private Activity activity;
    private View[] views;
    private ArrayList<ViewAttrs> attrs;

    public EasyTransitionOptions(Activity activity, View[] views) {
        this.activity = activity;
        this.views = views;
    }

    /**
     * Make options for transition
     *
     * @param activity The activity who contains shared views
     * @param views    Shared views, which must contains same id between two activities
     * @return A new transition options that will be used to build our transition animations
     */
    public static EasyTransitionOptions makeTransitionOptions(Activity activity, View... views) {
        return new EasyTransitionOptions(activity, views);
    }

    public void update() {
        if (null == views)
            return;

        attrs = new ArrayList<>();
        for (View v : views) {
            int[] location = new int[2];
            v.getLocationOnScreen(location);
            attrs.add(new ViewAttrs(
                    v.getId(),
                    location[0],
                    location[1],
                    v.getWidth(),
                    v.getHeight()
            ));
        }
    }

    public Activity getActivity() {
        return activity;
    }

    public ArrayList<ViewAttrs> getAttrs() {
        return attrs;
    }

    public static class ViewAttrs implements Parcelable {
        public int id;
        public float startX;
        public float startY;
        public float width;
        public float height;

        public ViewAttrs(int id, float startX, float startY, float width, float height) {
            this.id = id;
            this.startX = startX;
            this.startY = startY;
            this.width = width;
            this.height = height;
        }

        // Parcelable
        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(this.id);
            dest.writeFloat(this.startX);
            dest.writeFloat(this.startY);
            dest.writeFloat(this.width);
            dest.writeFloat(this.height);
        }

        protected ViewAttrs(Parcel in) {
            this.id = in.readInt();
            this.startX = in.readFloat();
            this.startY = in.readFloat();
            this.width = in.readFloat();
            this.height = in.readFloat();
        }

        public static final Parcelable.Creator<ViewAttrs> CREATOR = new Parcelable.Creator<ViewAttrs>() {
            @Override
            public ViewAttrs createFromParcel(Parcel source) {
                return new ViewAttrs(source);
            }

            @Override
            public ViewAttrs[] newArray(int size) {
                return new ViewAttrs[size];
            }
        };
    }
}

 场景使用:

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private ArrayList<String> list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        list = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            list.add("name" + i);
        }
        ListView listView = (ListView) findViewById(R.id.lv);
        listView.setAdapter(new MyAdapter());
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                // ready for intent
                Intent intent = new Intent(MainActivity.this, DetailActivity.class);
                intent.putExtra("name", list.get(position));

                // ready for transition options
                EasyTransitionOptions options =
                        EasyTransitionOptions.makeTransitionOptions(
                                MainActivity.this,
                                view.findViewById(R.id.iv_icon),
                                view.findViewById(R.id.tv_name),
                                findViewById(R.id.v_top_card));

                // start transition
                EasyTransition.startActivity(intent, options);
            }
        });
    }


    private class MyAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            int count = 0;
            if (null != list)
                count = list.size();
            return count;
        }

        @Override
        public Object getItem(int position) {
            return list.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = null;
            if (null != convertView) {
                view = convertView;
            } else {
                view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_main_list, null, false);
            }
            TextView tvName = (TextView) view.findViewById(R.id.tv_name);
            tvName.setText(list.get(position));
            return view;
        }
    }
}
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;

public class DetailActivity extends AppCompatActivity {

    private LinearLayout layoutAbout;
    private ImageView ivAdd;
    private boolean finishEnter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);

        // pre init some views and data
        initViews();

        // if re-initialized, do not play any anim
        long transitionDuration = 800;
        if (null != savedInstanceState)
            transitionDuration = 0;

        // transition enter
        finishEnter = false;
        EasyTransition.enter(
                this,
                transitionDuration,
                new DecelerateInterpolator(),
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        // init other views after transition anim
                        finishEnter = true;
                        initOtherViews();
                    }
                });
    }

    private void initViews() {
        TextView tvName = (TextView) findViewById(R.id.tv_name);
        tvName.setText(getIntent().getStringExtra("name"));
    }

    private void initOtherViews() {
        layoutAbout = (LinearLayout) findViewById(R.id.layout_about);
        layoutAbout.setVisibility(View.VISIBLE);
        layoutAbout.setAlpha(0);
        layoutAbout.setTranslationY(-30);
        layoutAbout.animate()
                .setDuration(300)
                .alpha(1)
                .translationY(0);

        ivAdd = (ImageView) findViewById(R.id.iv_add);
        ivAdd.setVisibility(View.VISIBLE);
        ivAdd.setScaleX(0);
        ivAdd.setScaleY(0);
        ivAdd.animate()
                .setDuration(200)
                .scaleX(1)
                .scaleY(1);
    }

    @Override
    public void onBackPressed() {
        if (finishEnter) {
            finishEnter = false;
            startBackAnim();
        }
    }

    private void startBackAnim() {
        // forbidden scrolling
        ScrollView sv = (ScrollView) findViewById(R.id.sv);
        sv.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return true;
            }
        });

        // start our anim
        ivAdd.animate()
                .setDuration(200)
                .scaleX(0)
                .scaleY(0);

        layoutAbout.animate()
                .setDuration(300)
                .alpha(0)
                .translationY(-30)
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        // transition exit after our anim
                        EasyTransition.exit(DetailActivity.this, 800, new DecelerateInterpolator());
                    }
                });
    }
}

 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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"
    tools:context="com.hzn.easytransition.MainActivity"
    >

    <TextView
        android:id="@+id/title_bar"
        android:layout_width="match_parent"
        android:layout_height="55dp"
        android:background="@color/colorPrimary"
        android:gravity="center_vertical"
        android:paddingLeft="16sp"
        android:text="TITLE"
        android:textColor="#ffffff"
        android:textSize="25sp"
        />

    <View
        android:id="@+id/v_top_card"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_below="@id/title_bar"
        android:background="@color/colorPrimary"
        />

    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/title_bar"
        />
</RelativeLayout>

activity_detail.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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"
    tools:context="com.hzn.easytransition.MainActivity"
    >

    <TextView
        android:id="@+id/title_bar"
        android:layout_width="match_parent"
        android:layout_height="55dp"
        android:background="@color/colorPrimary"
        android:gravity="center_vertical"
        android:paddingLeft="16sp"
        android:text="TITLE"
        android:textColor="#ffffff"
        android:textSize="25sp"
        />

    <View
        android:id="@+id/v_top_card"
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:layout_below="@+id/title_bar"
        android:background="@color/colorPrimary"
        />

    <ImageView
        android:id="@+id/iv_icon"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp"
        android:src="@mipmap/avatar_male"
        />

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/iv_icon"
        android:layout_centerHorizontal="true"
        android:layout_marginLeft="10dp"
        android:textColor="#5b5b5b"
        android:textSize="50sp"
        />

    <ScrollView
        android:id="@+id/sv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/tv_name"
        android:overScrollMode="never"
        android:scrollbars="none"
        >

        <LinearLayout
            android:id="@+id/layout_about"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:orientation="vertical"
            android:paddingBottom="40dp"
            android:paddingTop="20dp"
            android:visibility="invisible"
            >

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:drawableLeft="@mipmap/icon_phone"
                android:drawablePadding="10dp"
                android:gravity="center_vertical"
                android:text="151-2121-2121"
                android:textColor="#446880"
                android:textSize="16sp"
                />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:drawableLeft="@mipmap/icon_email"
                android:drawablePadding="10dp"
                android:gravity="center_vertical"
                android:text="243666666@gmail.com"
                android:textColor="#446880"
                android:textSize="16sp"
                />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:drawableLeft="@mipmap/icon_location"
                android:drawablePadding="10dp"
                android:gravity="center_vertical"
                android:text="China, Guangdong, Shenzhen"
                android:textColor="#446880"
                android:textSize="16sp"
                />
        </LinearLayout>
    </ScrollView>

    <ImageView
        android:id="@+id/iv_add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:padding="16dp"
        android:src="@mipmap/icon_add"
        android:visibility="gone"
        />
</RelativeLayout>

item_main_list.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                android:padding="16dp"
    >

    <ImageView
        android:id="@+id/iv_icon"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@mipmap/avatar_male"
        />

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@+id/iv_icon"
        android:text="name"
        android:textColor="#5b5b5b"
        android:textSize="20sp"
        />

</RelativeLayout>

 效果图:


学习来源:http://blog.csdn.net/u012199331/article/details/72137112


 
原文地址:https://www.cnblogs.com/loaderman/p/6957030.html