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.9k
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
530
設計にみるAWA Androidアプリのこれまでとこれから
r21nomi
6
2.4k
How to notify Dataset changed for RecyclerView
r21nomi
2
1.6k
Let's make Photo Frame with Android Things
r21nomi
0
2.5k
アプリのUX向上のためにAWAがやってきたこと
r21nomi
0
920
Other Decks in Technology
See All in Technology
Contributing to Rails? Start with the Gems You Already Use
yahonda
2
120
TLSから見るSREの未来
atpons
2
280
SREの次のキャリアの道しるべ 〜SREがマネジメントレイヤーに挑戦して、 気づいたこととTips〜
coconala_engineer
1
3.8k
shake-upを科学する
rsakata
7
960
How to Quickly Call American Airlines®️ U.S. Customer Care : Full Guide
flyaahelpguide
0
240
Introduction to Bill One Development Engineer
sansan33
PRO
0
260
VGGT: Visual Geometry Grounded Transformer
peisuke
1
660
オフィスビルを監視しよう:フィジカル×デジタルにまたがるSLI/SLO設計と運用の難しさ / Monitoring Office Buildings: The Challenge of Physical-Digital SLI/SLO Design & Operation
bitkey
1
370
AWS 怖い話 WAF編 @fillz_noh #AWSStartup #AWSStartup_Kansai
fillznoh
0
110
AIエージェントが書くのなら直接CloudFormationを書かせればいいじゃないですか何故AWS CDKを使う必要があるのさ
watany
18
7.3k
ビジネス職が分析も担う事業部制組織でのデータ活用の仕組みづくり / Enabling Data Analytics in Business-Led Divisional Organizations
zaimy
1
330
VS CodeとGitHub Copilotで爆速開発!アップデートの波に乗るおさらい会 / Rapid Development with VS Code and GitHub Copilot: Catch the Latest Wave
yamachu
2
390
Featured
See All Featured
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
108
19k
Documentation Writing (for coders)
carmenintech
72
4.9k
Done Done
chrislema
184
16k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
45
7.5k
Facilitating Awesome Meetings
lara
54
6.5k
Product Roadmaps are Hard
iamctodd
PRO
54
11k
The Power of CSS Pseudo Elements
geoffreycrofte
77
5.9k
Building a Scalable Design System with Sketch
lauravandoore
462
33k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
How GitHub (no longer) Works
holman
314
140k
KATA
mclloyd
30
14k
Keith and Marios Guide to Fast Websites
keithpitt
411
22k
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