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.6k
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.2k
デジタルアートのプラットフォームを開発してる話
r21nomi
1
480
設計にみるAWA Androidアプリのこれまでとこれから
r21nomi
7
2.2k
How to notify Dataset changed for RecyclerView
r21nomi
2
1.4k
Let's make Photo Frame with Android Things
r21nomi
0
2.4k
アプリのUX向上のためにAWAがやってきたこと
r21nomi
0
900
Other Decks in Technology
See All in Technology
サーバー間 GraphQL と webmock-graphql の話 / server-to-server graphql and webmock-graphql
qsona
2
190
アクセス制御にまつわる改善 / Improving access control
itkq
0
550
MapLibreとAmazon Location Service
dayjournal
1
160
20240418_Google ColabにLLMが搭載されたようなのでPython x データ分析の勉強方法を考えてみる
doradora09
0
140
ワールドカフェI /チューターを改良する / World Café I and Improving the Tutors
ks91
PRO
0
120
VS CodeでAWSを操作しよう
smt7174
8
1.7k
IaCジェネレーターとBedrockで詳細設計書を生成してみた
tsukasa_ishimaru
1
280
GrafanaMeetup_AmazonManagedGrafanaのアクセス制御機能とマルチテナント環境下でのアクセス制御について
daitak
0
240
Compose Compiler Metricsを使った実践的なコードレビュー
tomorrowkey
1
220
Java EE/Jakarta EEの現状と将来―クラウドネイティブ時代にJava EEは対応できるのか?―
takakiyo
1
170
Azureの基本的な権限管理の勉強会
yhana
0
590
レガシーをぶっ壊せ。AEONで始めるDevRelの話 / Qiita Night 2024-2-22
aeonpeople
3
1.3k
Featured
See All Featured
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
2
3.4k
Debugging Ruby Performance
tmm1
70
11k
Practical Orchestrator
shlominoach
182
9.7k
Clear Off the Table
cherdarchuk
84
310k
Faster Mobile Websites
deanohume
299
30k
Why You Should Never Use an ORM
jnunemaker
PRO
51
8.6k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
274
13k
Git: the NoSQL Database
bkeepers
PRO
422
63k
YesSQL, Process and Tooling at Scale
rocio
164
13k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
221
21k
A Tale of Four Properties
chriscoyier
151
22k
Being A Developer After 40
akosma
57
580k
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