Slide 1

Slide 1 text

Advanced Shared Element Transition 3ZPUB/JJOPNJ "8"

Slide 2

Slide 2 text

!SOPNJ SOPNJ ৽ Ո ɹ ྄ ଠ /JJOPNJɹ3ZPUB $"ೖࣾ ίϛϡχςΟαʔϏεͷϑϩϯτΤϯυ։ൃ ೥ ೥ʙ "8"Ͱ"OESPJE։ൃ

Slide 3

Slide 3 text

Shared Element Transition?

Slide 4

Slide 4 text

ɾ̎ը໘ؒͷΪϟοϓΛͳ͘͢ ɾࢹઢΛ༠ಋ͢Δ ɾಡΈࠐΈॲཧ͔ΒҙࣝΛͦΒ͢ ɾ৺஍Α͞

Slide 5

Slide 5 text

IUUQTNBUFSJBMJPHVJEFMJOFTNPUJPODIPSFPHSBQIZIUNMDIPSFPHSBQIZDPOUJOVJUZ

Slide 6

Slide 6 text

Activity Transitions

Slide 7

Slide 7 text

Activity Transitions ɾ4IBSFE&MFNFOU5SBOTJUJPOΛ࣮ݱ͢Δ"1* ɾ"1*-FWFMʙ

Slide 8

Slide 8 text

Basic Implementation .BJO"DUJWJUZ%FUBJM"DUJWJUZ

Slide 9

Slide 9 text

Intent intent = new Intent(this, DetailActivity.class); Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation( context, sharedElementView, “shared_element_view” ).toBundle(); startActivity(intent, options); .BJO"DUJWJUZKBWB

Slide 10

Slide 10 text

@Override protected void onCreate(Bundle savedInstanceState){ ... View view = findViewById(R.id.animationView); ViewCompat.setTransitionName(view, "shared_element_view"); } %FUBJM"DUJWJUZKBWB

Slide 11

Slide 11 text

Activity Transitions Using Adapter 4IBSFE&MFNFOUͷର৅͕"EBQUFS಺ͷ7JFX

Slide 12

Slide 12 text

postponeEnterTransition(); // 待機 startPostponedEnterTransition(); // 再開

Slide 13

Slide 13 text

private ItemDetailAdapter.Listener mListener = new ItemDetailAdapter.Listener() { @Override public void onSharedElementViewPrepared() { startPostponedEnterTransition(); } }; @Override protected void onCreate(Bundle savedInstanceState) { ... postponeEnterTransition(); adapter.setListner(mListener); } %FUBJM"DUJWJUZKBWB

Slide 14

Slide 14 text

private ItemDetailAdapter.Listener mListener = new ItemDetailAdapter.Listener() { @Override public void onSharedElementViewPrepared() { startPostponedEnterTransition(); } }; @Override protected void onCreate(Bundle savedInstanceState) { ... postponeEnterTransition(); adapter.setListner(mListener); } %FUBJM"DUJWJUZKBWB

Slide 15

Slide 15 text

*UFN"EBQUFSKBWB @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_viewholder, parent, false); final ViewHolder vh = new ViewHolder(view); ViewCompat.setTransitionName(vh.thumb, "shared_element_view"); vh.thumb.getViewTreeObserver() .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { vh.thumb.getViewTreeObserver().removeOnPreDrawListener(this); mListener.onSharedElementViewPrepared(); return true; } }); return vh; }

Slide 16

Slide 16 text

@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_viewholder, parent, false); final ViewHolder vh = new ViewHolder(view); ViewCompat.setTransitionName(vh.thumb, "shared_element_view"); vh.thumb.getViewTreeObserver() .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { vh.thumb.getViewTreeObserver().removeOnPreDrawListener(this); mListener.onSharedElementViewPrepared(); return true; } }); return vh; } *UFN"EBQUFSKBWB

Slide 17

Slide 17 text

@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_viewholder, parent, false); final ViewHolder vh = new ViewHolder(view); ViewCompat.setTransitionName(vh.thumb, "shared_element_view"); vh.thumb.getViewTreeObserver() .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { vh.thumb.getViewTreeObserver().removeOnPreDrawListener(this); mListener.onSharedElementViewPrepared(); return true; } }); return vh; } *UFN"EBQUFSKBWB

Slide 18

Slide 18 text

Layout Problem

Slide 19

Slide 19 text

4UBUVT#BS /BWJHBUJPO#BSͷલ໘ʹදࣔ

Slide 20

Slide 20 text

4IBSFE&MFNFOU5BSHFUT 4UBUVT#BS 4IBSFE&MFNFOU7JFX /BWJHBUJPO#BS

Slide 21

Slide 21 text

View statusBar = activity.findViewById(android.R.id.statusBarBackground); View navigationBar = activity.findViewById(android.R.id.navigationBarBackground); List> pairs = new ArrayList<>(); pairs.add(Pair.create(statusBar, statusBar.getTransitionName())); pairs.add(Pair.create(navigationBar, navigationBar.getTransitionName())); pairs.add(Pair.create(sharedElementView, "shared_element_view")); Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation( this, pairs.toArray(new Pair[pairs.size()]) ).toBundle(); startActivity(intent, options); .BJO"DUJWJUZKBWB

Slide 22

Slide 22 text

View statusBar = activity.findViewById(android.R.id.statusBarBackground); View navigationBar = activity.findViewById(android.R.id.navigationBarBackground); List> pairs = new ArrayList<>(); pairs.add(Pair.create(statusBar, statusBar.getTransitionName())); pairs.add(Pair.create(navigationBar, navigationBar.getTransitionName())); pairs.add(Pair.create(sharedElementView, "shared_element_view")); Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation( this, pairs.toArray(new Pair[pairs.size()]) ).toBundle(); startActivity(intent, options); .BJO"DUJWJUZKBWB

Slide 23

Slide 23 text

View statusBar = activity.findViewById(android.R.id.statusBarBackground); View navigationBar = activity.findViewById(android.R.id.navigationBarBackground); List> pairs = new ArrayList<>(); pairs.add(Pair.create(statusBar, statusBar.getTransitionName())); pairs.add(Pair.create(navigationBar, navigationBar.getTransitionName())); pairs.add(Pair.create(sharedElementView, "shared_element_view")); Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation( this, pairs.toArray(new Pair[pairs.size()]) ).toBundle(); startActivity(intent, options); .BJO"DUJWJUZKBWB

Slide 24

Slide 24 text

4UBUVT#BS /BWJHBUJPO#BSͷഎ໘ʹදࣔ

Slide 25

Slide 25 text

Set callback of Transition

Slide 26

Slide 26 text

Detect animationEnd 5SBOTJUJPO5SBOTJUJPO-JTUFOFS "DUJWJUZTFU&OUFS4IBSFE&MFNFOU$BMMCBDL "DUJWJUZPO&OUFS"OJNBUJPO$PNQMFUF ✖ ˕ ✖

Slide 27

Slide 27 text

getWindow() .getSharedElementEnterTransition() .addListener(new Transition.TransitionListener() { @Override public void onTransitionEnd(Transition transition) { // Do something after shared element transition end. } … });

Slide 28

Slide 28 text

Interpolator

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

&910@*/@065 #"$,@065 &-"45*$@065

Slide 31

Slide 31 text

@Override protected void onCreate(Bundle savedInstanceState){ … Interpolator interpolator = new FastOutSlowInInterpolator(); Transition transition = new ChangeBounds().setInterpolator(interpolator); getWindow().setSharedElementEnterTransition(transition); }

Slide 32

Slide 32 text

IUUQSPCFSUQFOOFSDPNFBTJOH Robert Penner's Easing Functions

Slide 33

Slide 33 text

IUUQTHJUIVCDPNSOPNJ"OESPJE31*OUFSQPMBUPS AndroidRPInterpolator

Slide 34

Slide 34 text

Activity Transitions Using ViewPager

Slide 35

Slide 35 text

.BJO"DUJWJUZKBWB %FUBJM"DUJWJUZKBWB 3FDZDMFS7JFX 7JFX1BHFS qJDLˎO ʁ

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

4IBSFE&MFNFOU7JFXΛೖΕସ͑ SharedElementCallback.onMapSharedElements()

Slide 38

Slide 38 text

setEnterSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List names, Map sharedElements) { int position = mViewPager.getCurrentItem(); View view = mViewPager.findViewWithTag(ItemPagerAdapter.getTag(position)); sharedElements.clear(); sharedElements.put("shared_element_view", view); } }); %FUBJM"DUJWJUZKBWB

Slide 39

Slide 39 text

setEnterSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List names, Map sharedElements) { int position = mViewPager.getCurrentItem(); View view = mViewPager.findViewWithTag(ItemPagerAdapter.getTag(position)); sharedElements.clear(); sharedElements.put("shared_element_view", view); } }); %FUBJM"DUJWJUZKBWB

Slide 40

Slide 40 text

%FUBJM"DUJWJUZKBWB postponeEnterTransition(); mViewPager.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { mViewPager.getViewTreeObserver().removeOnPreDrawListener(this); startPostponedEnterTransition(); return true; } }); 7JFX1BHFSͷඳը४උ׬ྃΛ଴ͭ

Slide 41

Slide 41 text

%FUBJM"DUJWJUZKBWB postponeEnterTransition(); mViewPager.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { mViewPager.getViewTreeObserver().removeOnPreDrawListener(this); startPostponedEnterTransition(); return true; } }); 7JFX1BHFSͷඳը४උ׬ྃΛ଴ͭ

Slide 42

Slide 42 text

%FUBJM"DUJWJUZKBWB qJDLͰ.BJO"DUJWJUZʹ௨஌͢ΔQPTJUJPOΛߋ৽ mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageSelected(int position) { Intent intent = new Intent(); Bundle bundle = new Bundle(); bundle.putInt("position", position); intent.putExtras(bundle); setResult(Activity.RESULT_OK, intent); } … });

Slide 43

Slide 43 text

%FUBJM"DUJWJUZKBWB qJDLͰ.BJO"DUJWJUZʹ௨஌͢ΔQPTJUJPOΛߋ৽ mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageSelected(int position) { Intent intent = new Intent(); Bundle bundle = new Bundle(); bundle.putInt("position", position); intent.putExtras(bundle); setResult(Activity.RESULT_OK, intent); } … });

Slide 44

Slide 44 text

4IBSFE&MFNFOU5SBOTJUJPOͷόοΫ࣌ʹEBUBͱڞʹݺ͹ΕΔ Activity.onActivityReenter(…, Intent data)

Slide 45

Slide 45 text

.BJO"DUJWJUZKBWB @Override public void onActivityReenter(int resultCode, Intent data) { super.onActivityReenter(resultCode, data); int position = data.getIntExtra("position", 0); ItemAdapter.ViewHolder viewHolder = (ItemAdapter.ViewHolder) mRecyclerView.findViewHolderForAdapterPosition(position); final View sharedElementView = (viewHolder == null) ? null : viewHolder.thumb; setExitSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List names, Map sharedElements) { sharedElements.clear(); sharedElements.put("shared_element_view", sharedElementView); setExitSharedElementCallback((SharedElementCallback) null); } }); }

Slide 46

Slide 46 text

.BJO"DUJWJUZKBWB @Override public void onActivityReenter(int resultCode, Intent data) { super.onActivityReenter(resultCode, data); int position = data.getIntExtra("position", 0); ItemAdapter.ViewHolder viewHolder = (ItemAdapter.ViewHolder) mRecyclerView.findViewHolderForAdapterPosition(position); final View sharedElementView = (viewHolder == null) ? null : viewHolder.thumb; setExitSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List names, Map sharedElements) { sharedElements.clear(); sharedElements.put("shared_element_view", sharedElementView); setExitSharedElementCallback((SharedElementCallback) null); } }); }

Slide 47

Slide 47 text

.BJO"DUJWJUZKBWB @Override public void onActivityReenter(int resultCode, Intent data) { super.onActivityReenter(resultCode, data); int position = data.getIntExtra("position", 0); ItemAdapter.ViewHolder viewHolder = (ItemAdapter.ViewHolder) mRecyclerView.findViewHolderForAdapterPosition(position); final View sharedElementView = (viewHolder == null) ? null : viewHolder.thumb; setExitSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List names, Map sharedElements) { sharedElements.clear(); sharedElements.put("shared_element_view", sharedElementView); setExitSharedElementCallback((SharedElementCallback) null); } }); }

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

Summary ɾ4IBSFE&MFNFOUͷର৅ʹ͸4UBUVT#BSɺ/BWJHBUJPO#BSΛؚΊΔ ɾదٓɺQPTUQPOF&OUFS5SBOTJUJPOͰΞχϝʔγϣϯΛ஗Ԇͤ͞Δ ɾ0O1SF%SBX-JTUFOFSPO1SF%SBXΛ࢖͏ ɾదٓɺPO.BQ4IBSFE&MFNFOUTͰ4IBSFE&MFNFOUର৅ΛೖΕସ͑

Slide 50

Slide 50 text

Self Implementation for Shared Element Transition

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

.BJO"DUJWJUZ WJFXͷTJ[F QPTJUJPOΛ%FUBJM"DUJWJUZʹ౉͢ %FUBJM"DUJWJUZ 4IBSFEFMFNFOUWJFXʹTJ[F QPTJUJPOΛઃఆ Ξχϝʔγϣϯ

Slide 53

Slide 53 text

int[] location = new int[2]; view.getLocationOnScreen(location); location[0]; // left location[1]; // top .BJO"DUJWJUZ WJFXͷTJ[F QPTJUJPOΛ%FUBJM"DUJWJUZʹ౉͢

Slide 54

Slide 54 text

public class Position implements Parcelable { int left; int top; int width; int height; public Position(View view) { int[] location = new int[2]; view.getLocationOnScreen(location); left = location[0]; top = location[1]; width = view.getWidth(); height = view.getHeight(); } ... } .BJO"DUJWJUZ WJFXͷTJ[F QPTJUJPOΛ%FUBJM"DUJWJUZʹ౉͢

Slide 55

Slide 55 text

// Set size ViewGroup.LayoutParams param = view.getLayoutParams(); param.width = position.getWidth(); param.height = position.getHeight(); view.setLayoutParams(param); %FUBJM"DUJWJUZ 4IBSFE&MFNFOU7JFXʹTJ[F QPTJUJPOΛઃఆ

Slide 56

Slide 56 text

// Wait for view preparing view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { view.getViewTreeObserver().removeOnPreDrawListener(this); float top = position.getTop() - getStatusBarheight(); // Use setX / setY to set absolute position. view.setX(position.getLeft()); view.setY(top); startEnterAnimation(); return true; } }); %FUBJM"DUJWJUZ 4IBSFE&MFNFOU7JFXʹTJ[F QPTJUJPOΛઃఆ

Slide 57

Slide 57 text

// Wait for view preparing view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { view.getViewTreeObserver().removeOnPreDrawListener(this); float top = position.getTop() - getStatusBarheight(); // Use setX / setY to set absolute position. view.setX(position.getLeft()); view.setY(top); startEnterAnimation(); return true; } }); %FUBJM"DUJWJUZ 4IBSFE&MFNFOU7JFXʹTJ[F QPTJUJPOΛઃఆ

Slide 58

Slide 58 text

4IBSFE&MFNFOU7JFXͷUPQʔTUBUVT#BSͷߴ͞

Slide 59

Slide 59 text

QPTJUJPOHFU5PQ ͭ·ΓMPDBUJPO<> ͸TUBUVTCBSͷߴ͞ΛؚΉ Point

Slide 60

Slide 60 text

// Wait for view preparing view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { view.getViewTreeObserver().removeOnPreDrawListener(this); float top = position.getTop() - getStatusBarheight(); // Use setX / setY to set absolute position. view.setX(position.getLeft()); view.setY(top); startEnterAnimation(); return true; } }); %FUBJM"DUJWJUZ 4IBSFE&MFNFOU7JFXʹTJ[F QPTJUJPOΛઃఆ

Slide 61

Slide 61 text

view.setY(top); ˕ ✖ view.setTranslationY(top);

Slide 62

Slide 62 text

view.setY(top); ˕ ✖ view.setTranslationY(top); ݩͷҐஔ͔Βܭࢉ ج४఺͔Βܭࢉ

Slide 63

Slide 63 text

private void startEnterAnimation() { int targetWidth = getTargetWidth(); int targetHeight = targetWidth * position.getWidth() / position.getHeight(); AnimatorSet animSet = new AnimatorSet(); animSet.playTogether( view.getToRectAnimator(), // Use translationX / translationY to translate to relative position. ObjectAnimator.ofFloat(view, "translationX", 0), ObjectAnimator.ofFloat(view, "translationY", 0), ValueAnimator.ofObject(new WidthEvaluator(view), position.getWidth(), targetWidth), ValueAnimator.ofObject(new HeightEvaluator(view), position.getHeight(), targetHeight) ); animSet.start(); } %FUBJM"DUJWJUZ Ξχϝʔγϣϯ

Slide 64

Slide 64 text

private void startEnterAnimation() { int targetWidth = getTargetWidth(); int targetHeight = targetWidth * position.getWidth() / position.getHeight(); AnimatorSet animSet = new AnimatorSet(); animSet.playTogether( view.getToRectAnimator(), // Use translationX / translationY to translate to relative position. ObjectAnimator.ofFloat(view, "translationX", 0), ObjectAnimator.ofFloat(view, "translationY", 0), ValueAnimator.ofObject(new WidthEvaluator(view), position.getWidth(), targetWidth), ValueAnimator.ofObject(new HeightEvaluator(view), position.getHeight(), targetHeight) ); animSet.start(); } %FUBJM"DUJWJUZ Ξχϝʔγϣϯ

Slide 65

Slide 65 text

“translationY" == setTranslationY() %FUBJM"DUJWJUZ Ξχϝʔγϣϯ

Slide 66

Slide 66 text

%FUBJM"DUJWJUZ Ξχϝʔγϣϯ “translationY” ˕ ✖ “y”

Slide 67

Slide 67 text

private void startEnterAnimation() { int targetWidth = getTargetWidth(); int targetHeight = targetWidth * position.getWidth() / position.getHeight(); AnimatorSet animSet = new AnimatorSet(); animSet.playTogether( view.getToRectAnimator(), // Use translationX / translationY to translate to relative position. ObjectAnimator.ofFloat(view, "translationX", 0), ObjectAnimator.ofFloat(view, "translationY", 0), ValueAnimator.ofObject(new WidthEvaluator(view), position.getWidth(), targetWidth), ValueAnimator.ofObject(new HeightEvaluator(view), position.getHeight(), targetHeight) ); animSet.start(); } %FUBJM"DUJWJUZ Ξχϝʔγϣϯ

Slide 68

Slide 68 text

private void startEnterAnimation() { int targetWidth = getTargetWidth(); int targetHeight = targetWidth * position.getWidth() / position.getHeight(); AnimatorSet animSet = new AnimatorSet(); animSet.playTogether( view.getToRectAnimator(), // Use translationX / translationY to translate to relative position. ObjectAnimator.ofFloat(view, "translationX", 0), ObjectAnimator.ofFloat(view, "translationY", 0), ValueAnimator.ofObject(new WidthEvaluator(view), position.getWidth(), targetWidth), ValueAnimator.ofObject(new HeightEvaluator(view), position.getHeight(), targetHeight) ); animSet.start(); } %FUBJM"DUJWJUZ Ξχϝʔγϣϯ

Slide 69

Slide 69 text

8JEUI&WBMVBUPS Ξχϝʔγϣϯ஋ΛXJEUIʹઃఆ

Slide 70

Slide 70 text

public class WidthEvaluator extends IntEvaluator { private View mView; public WidthEvaluator(View view) { mView = view; } @Override public Integer evaluate(float fraction, Integer startValue, Integer endValue) { Integer num = super.evaluate(fraction, startValue, endValue); ViewGroup.LayoutParams params = mView.getLayoutParams(); params.width = num; mView.setLayoutParams(params); return num; } }

Slide 71

Slide 71 text

Summary ɾ7JFXHFU-PDBUJPO0O4DSFFO JOU<>PVU-PDBUJPO ͰҐஔΛऔಘ ɾ0O1SF%SBX-JTUFOFSPO1SF%SBXͰ7JFXͷܭࢉ׬ྃΛ଴ͭ ɾॳظҐஔͷઃఆ͸TFU: Ͱ ɾ:࠲ඪͷઃఆʹ͸TUBUVTCBSΛߟྀ͢Δ

Slide 72

Slide 72 text

Thank you !SOPNJ SOPNJ