Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Advanced Shared Element Transition
Search
Ryota Takemoto
January 12, 2017
Technology
3.2k
4
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Advanced Shared Element Transition
Shared Element Transitionをいい感じに実装にするためのあれこれ。
Ryota Takemoto
January 12, 2017
More Decks by Ryota Takemoto
See All by Ryota Takemoto
NEORT1周年の振り返りとこれからの話
r21nomi
0
1.4k
デジタルアートのプラットフォームを開発してる話
r21nomi
1
550
設計にみるAWA Androidアプリのこれまでとこれから
r21nomi
6
2.5k
How to notify Dataset changed for RecyclerView
r21nomi
2
1.7k
Let's make Photo Frame with Android Things
r21nomi
0
2.5k
アプリのUX向上のためにAWAがやってきたこと
r21nomi
0
950
Other Decks in Technology
See All in Technology
オートロックマンションなのに、各部屋は施錠なし!? 攻撃者が組織内ネットワークで大暴れする理由 / The Front Door Is Locked, but the Rooms Are Wide Open: Why Attackers Move Freely Inside Enterprise Networks
nttcom
1
5.9k
AIコーディングの次。コードレビューと理解負荷を解消して組織の開発生産性を高める
moongift
PRO
2
2.6k
ラジオの科学
frievea
0
320
20分でわかるセキュアAPI
nwiizo
2
280
研究開発部の紹介 / Sansan R&D Profile
sansan33
PRO
4
24k
QAエンジニア起点で進める、SmartHRにおける信頼性向上について
kaomi_wombat
1
140
Sets in Go
ramalho
1
810
Model Studio CLI × Token Plan
maigo999
0
180
ガバメント AI 源内を地方自治体は活用できるのか可能性と課題、期待について
takeda_h
1
420
トヨタ⽣産⽅式(TPS)⼊⾨
recruitengineers
PRO
3
890
FDEの心得
noriakioji
5
6.1k
MulticaとPi Coding Agentで、小規模OSSを30本同時運用した流れ
eiei114
0
130
Featured
See All Featured
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
1
280
Optimising Largest Contentful Paint
csswizardry
37
3.9k
Joys of Absence: A Defence of Solitary Play
codingconduct
1
430
Designing for Timeless Needs
cassininazir
1
440
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
790
YesSQL, Process and Tooling at Scale
rocio
174
15k
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
490
KATA
mclloyd
PRO
35
15k
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
1
240
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
1.1k
The Curse of the Amulet
leimatthew05
2
14k
Transcript
Advanced Shared Element Transition 3ZPUB/JJOPNJ "8"
!SOPNJ SOPNJ ৽ Ո ɹ ྄ ଠ /JJOPNJɹ3ZPUB $"ೖࣾ ίϛϡχςΟαʔϏεͷϑϩϯτΤϯυ։ൃ
ʙ "8"Ͱ"OESPJE։ൃ
Shared Element Transition?
ɾ̎ը໘ؒͷΪϟοϓΛͳ͘͢ ɾࢹઢΛ༠ಋ͢Δ ɾಡΈࠐΈॲཧ͔ΒҙࣝΛͦΒ͢ ɾ৺Α͞
IUUQTNBUFSJBMJPHVJEFMJOFTNPUJPODIPSFPHSBQIZIUNMDIPSFPHSBQIZDPOUJOVJUZ
Activity Transitions
Activity Transitions ɾ4IBSFE&MFNFOU5SBOTJUJPOΛ࣮ݱ͢Δ"1* ɾ"1*-FWFMʙ
Basic Implementation .BJO"DUJWJUZ%FUBJM"DUJWJUZ
Intent intent = new Intent(this, DetailActivity.class); Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(
context, sharedElementView, “shared_element_view” ).toBundle(); startActivity(intent, options); .BJO"DUJWJUZKBWB
@Override protected void onCreate(Bundle savedInstanceState){ ... View view = findViewById(R.id.animationView);
ViewCompat.setTransitionName(view, "shared_element_view"); } %FUBJM"DUJWJUZKBWB
Activity Transitions Using Adapter 4IBSFE&MFNFOUͷର͕"EBQUFSͷ7JFX
postponeEnterTransition(); // 待機 startPostponedEnterTransition(); // 再開
private ItemDetailAdapter.Listener mListener = new ItemDetailAdapter.Listener() { @Override public void
onSharedElementViewPrepared() { startPostponedEnterTransition(); } }; @Override protected void onCreate(Bundle savedInstanceState) { ... postponeEnterTransition(); adapter.setListner(mListener); } %FUBJM"DUJWJUZKBWB
private ItemDetailAdapter.Listener mListener = new ItemDetailAdapter.Listener() { @Override public void
onSharedElementViewPrepared() { startPostponedEnterTransition(); } }; @Override protected void onCreate(Bundle savedInstanceState) { ... postponeEnterTransition(); adapter.setListner(mListener); } %FUBJM"DUJWJUZKBWB
*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; }
@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
@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
Layout Problem
4UBUVT#BS /BWJHBUJPO#BSͷલ໘ʹදࣔ
4IBSFE&MFNFOU5BSHFUT 4UBUVT#BS 4IBSFE&MFNFOU7JFX /BWJHBUJPO#BS
View statusBar = activity.findViewById(android.R.id.statusBarBackground); View navigationBar = activity.findViewById(android.R.id.navigationBarBackground); List<Pair<View, String>>
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
View statusBar = activity.findViewById(android.R.id.statusBarBackground); View navigationBar = activity.findViewById(android.R.id.navigationBarBackground); List<Pair<View, String>>
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
View statusBar = activity.findViewById(android.R.id.statusBarBackground); View navigationBar = activity.findViewById(android.R.id.navigationBarBackground); List<Pair<View, String>>
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
4UBUVT#BS /BWJHBUJPO#BSͷഎ໘ʹදࣔ
Set callback of Transition
Detect animationEnd 5SBOTJUJPO5SBOTJUJPO-JTUFOFS "DUJWJUZTFU&OUFS4IBSFE&MFNFOU$BMMCBDL "DUJWJUZPO&OUFS"OJNBUJPO$PNQMFUF ✖ ˕ ✖
getWindow() .getSharedElementEnterTransition() .addListener(new Transition.TransitionListener() { @Override public void onTransitionEnd(Transition transition)
{ // Do something after shared element transition end. } … });
Interpolator
None
&910@*/@065 #"$,@065 &-"45*$@065
@Override protected void onCreate(Bundle savedInstanceState){ … Interpolator interpolator = new
FastOutSlowInInterpolator(); Transition transition = new ChangeBounds().setInterpolator(interpolator); getWindow().setSharedElementEnterTransition(transition); }
IUUQSPCFSUQFOOFSDPNFBTJOH Robert Penner's Easing Functions
IUUQTHJUIVCDPNSOPNJ"OESPJE31*OUFSQPMBUPS AndroidRPInterpolator
Activity Transitions Using ViewPager
.BJO"DUJWJUZKBWB %FUBJM"DUJWJUZKBWB 3FDZDMFS7JFX 7JFX1BHFS qJDLˎO ʁ
None
4IBSFE&MFNFOU7JFXΛೖΕସ͑ SharedElementCallback.onMapSharedElements()
setEnterSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List<String> names, Map<String, View>
sharedElements) { int position = mViewPager.getCurrentItem(); View view = mViewPager.findViewWithTag(ItemPagerAdapter.getTag(position)); sharedElements.clear(); sharedElements.put("shared_element_view", view); } }); %FUBJM"DUJWJUZKBWB
setEnterSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List<String> names, Map<String, View>
sharedElements) { int position = mViewPager.getCurrentItem(); View view = mViewPager.findViewWithTag(ItemPagerAdapter.getTag(position)); sharedElements.clear(); sharedElements.put("shared_element_view", view); } }); %FUBJM"DUJWJUZKBWB
%FUBJM"DUJWJUZKBWB postponeEnterTransition(); mViewPager.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() {
mViewPager.getViewTreeObserver().removeOnPreDrawListener(this); startPostponedEnterTransition(); return true; } }); 7JFX1BHFSͷඳը४උྃΛͭ
%FUBJM"DUJWJUZKBWB postponeEnterTransition(); mViewPager.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() {
mViewPager.getViewTreeObserver().removeOnPreDrawListener(this); startPostponedEnterTransition(); return true; } }); 7JFX1BHFSͷඳը४උྃΛͭ
%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); } … });
%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); } … });
4IBSFE&MFNFOU5SBOTJUJPOͷόοΫ࣌ʹEBUBͱڞʹݺΕΔ Activity.onActivityReenter(…, Intent data)
.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<String> names, Map<String, View> sharedElements) { sharedElements.clear(); sharedElements.put("shared_element_view", sharedElementView); setExitSharedElementCallback((SharedElementCallback) null); } }); }
.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<String> names, Map<String, View> sharedElements) { sharedElements.clear(); sharedElements.put("shared_element_view", sharedElementView); setExitSharedElementCallback((SharedElementCallback) null); } }); }
.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<String> names, Map<String, View> sharedElements) { sharedElements.clear(); sharedElements.put("shared_element_view", sharedElementView); setExitSharedElementCallback((SharedElementCallback) null); } }); }
None
Summary ɾ4IBSFE&MFNFOUͷରʹ4UBUVT#BSɺ/BWJHBUJPO#BSΛؚΊΔ ɾదٓɺQPTUQPOF&OUFS5SBOTJUJPOͰΞχϝʔγϣϯΛԆͤ͞Δ ɾ0O1SF%SBX-JTUFOFSPO1SF%SBXΛ͏ ɾదٓɺPO.BQ4IBSFE&MFNFOUTͰ4IBSFE&MFNFOUରΛೖΕସ͑
Self Implementation for Shared Element Transition
None
.BJO"DUJWJUZ WJFXͷTJ[F QPTJUJPOΛ%FUBJM"DUJWJUZʹ͢ %FUBJM"DUJWJUZ 4IBSFEFMFNFOUWJFXʹTJ[F QPTJUJPOΛઃఆ Ξχϝʔγϣϯ
int[] location = new int[2]; view.getLocationOnScreen(location); location[0]; // left location[1];
// top .BJO"DUJWJUZ WJFXͷTJ[F QPTJUJPOΛ%FUBJM"DUJWJUZʹ͢
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ʹ͢
// 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Λઃఆ
// 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Λઃఆ
// 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Λઃఆ
4IBSFE&MFNFOU7JFXͷUPQʔTUBUVT#BSͷߴ͞
QPTJUJPOHFU5PQ ͭ·ΓMPDBUJPO<> TUBUVTCBSͷߴ͞ΛؚΉ Point
// 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Λઃఆ
view.setY(top); ˕ ✖ view.setTranslationY(top);
view.setY(top); ˕ ✖ view.setTranslationY(top); ݩͷҐஔ͔Βܭࢉ ج४͔Βܭࢉ
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 Ξχϝʔγϣϯ
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 Ξχϝʔγϣϯ
“translationY" == setTranslationY() %FUBJM"DUJWJUZ Ξχϝʔγϣϯ
%FUBJM"DUJWJUZ Ξχϝʔγϣϯ “translationY” ˕ ✖ “y”
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 Ξχϝʔγϣϯ
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 Ξχϝʔγϣϯ
8JEUI&WBMVBUPS ΞχϝʔγϣϯΛXJEUIʹઃఆ
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; } }
Summary ɾ7JFXHFU-PDBUJPO0O4DSFFO JOU<>PVU-PDBUJPO ͰҐஔΛऔಘ ɾ0O1SF%SBX-JTUFOFSPO1SF%SBXͰ7JFXͷܭࢉྃΛͭ ɾॳظҐஔͷઃఆTFU: Ͱ ɾ:࠲ඪͷઃఆʹTUBUVTCBSΛߟྀ͢Δ
Thank you !SOPNJ SOPNJ