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
4
2.7k
Advanced Shared Element Transition
Shared Element Transitionをいい感じに実装にするためのあれこれ。
Ryota Takemoto
January 12, 2017
Tweet
Share
More Decks by Ryota Takemoto
See All by Ryota Takemoto
NEORT1周年の振り返りとこれからの話
r21nomi
0
1.3k
デジタルアートのプラットフォームを開発してる話
r21nomi
1
520
設計にみるAWA Androidアプリのこれまでとこれから
r21nomi
7
2.3k
How to notify Dataset changed for RecyclerView
r21nomi
2
1.5k
Let's make Photo Frame with Android Things
r21nomi
0
2.4k
アプリのUX向上のためにAWAがやってきたこと
r21nomi
0
910
Other Decks in Technology
See All in Technology
統計データで2024年の クラウド・インフラ動向を眺める
ysknsid25
2
840
AWS re:Invent 2024 ふりかえり
kongmingstrap
0
130
サイバー攻撃を想定したセキュリティガイドライン 策定とASM及びCNAPPの活用方法
syoshie
3
1.2k
GitHub Copilot のテクニック集/GitHub Copilot Techniques
rayuron
26
11k
サイボウズフロントエンドエキスパートチームについて / FrontendExpert Team
cybozuinsideout
PRO
5
38k
Wvlet: A New Flow-Style Query Language For Functional Data Modeling and Interactive Data Analysis - Trino Summit 2024
xerial
1
110
マルチプロダクト開発の現場でAWS Security Hubを1年以上運用して得た教訓
muziyoshiz
2
2.2k
生成AIをより賢く エンジニアのための RAG入門 - Oracle AI Jam Session #20
kutsushitaneko
4
220
LINEスキマニにおけるフロントエンド開発
lycorptech_jp
PRO
0
330
alecthomas/kong はいいぞ / kamakura.go#7
fujiwara3
1
300
Amazon Kendra GenAI Index 登場でどう変わる? 評価から学ぶ最適なRAG構成
naoki_0531
0
100
1等無人航空機操縦士一発試験 合格までの道のり ドローンミートアップ@大阪 2024/12/18
excdinc
0
150
Featured
See All Featured
Speed Design
sergeychernyshev
25
670
Documentation Writing (for coders)
carmenintech
66
4.5k
How GitHub (no longer) Works
holman
311
140k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
5
440
A Philosophy of Restraint
colly
203
16k
Git: the NoSQL Database
bkeepers
PRO
427
64k
Designing for Performance
lara
604
68k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
Typedesign – Prime Four
hannesfritz
40
2.4k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
229
52k
Why Our Code Smells
bkeepers
PRO
335
57k
Embracing the Ebb and Flow
colly
84
4.5k
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