KEMBAR78
다음웹툰의 UX(Animation, Transition, Custom View) | PDF
다음웹툰의 UX
함태윤(erkas.c)
DAUM WEBTOON COMPANY
Animation, Transition, CustomView
DAUM
WEBTOON
2.0
개선이 너무 잘 된 것 같습니다. 팝업 모션이 너무 좋아요!!! 댓글 스포일러 막는 것도 좋고
스크롤 내릴 때 캐릭터와 배경이 따로 분리되서 내려오는 것도 너무 좋아요 ㅠㅠ
진짜 매번 이걸로 웹툰 볼 때 느끼는 건데 깔끔하고 요일로 정리한 거랑 보려고 하는 웹툰
눌렀을 때? 웹툰이 올라오면서 켜지는거 너무 이뻐서 좋아요!! 솔직히 타 플랫폼은 비슷비슷한데
여긴 저렇게 깔끔하게 되어있어서 좋아요!!
다음웹툰도 즐겨보지만 UI가 매우 마음에 듭니다 개발자에게 전화를 걸었지만
모든 상담사가 통화중이라고 하더군요 혹시 개발자님과 이야기 하려면 어떻게 해야 하나요...?
KEYWORD
INTERACTION
Creative
Natural
Focusing
Meaningful
INTERACTION
KEYWORD
PROCESS
WORK
DESIGN
PROTOTYPING
STEP 1
CONSULTATION
STEP 2
DEVELOPMENT
PROTOTYPING
STEP 3
REVIEW
STEP 4
OUTPUT
STEP 6
GO BACK
STEP 5
WORK
PROCESS
01.
HOME
INTERACTION
SPLASH
SYMBOLIC ICON
LIST ANIMATION
02.
TITLE PAGE
SCENE TRANSITION
CUSTOM ENTER TRANSITION
CURVE ANIMATION
RIPPLE EFFECT
CONTENTS
INTERACTION
HOME
SPLASH
01.
DESIGN
PROTOTYPING
WEBTOON FRAME
VERTICAL
CONTENTS
CONTAINER
DESIGN
CONCEPT
사각형의 움직임은 3가지
가속도는 2가지
ISSUE
CONSULTATION
합져지고 줄어들고 늘어난다
CONSULTATION
CONSULTATION
CONSULTATION
protected void onDraw(Canvas canvas) {
...
if (offset < 0.5f) {
// 빨간 사각형
canvas.drawRect(left, top, right, bottom, mMainPaint);
// 하얀 사각형
if (animateLength <= mGapHeight) {
bottom = centerY - mContentHeight / 2;
top = bottom - mGapHeight + animateLength;
canvas.drawRect(left, top, right, bottom, mSubPaint);
top = centerY + mContentHeight / 2;
bottom = top + mGapHeight - animateLength;
canvas.drawRect(left, top, right, bottom, mSubPaint);
}
} else if (offset >= 0.5f) {
float offset = (this.offset - 0.5f) * 2f;
top = centerY - mEndHeight / 2;
bottom = centerY + mEndHeight / 2;
left = mPadding + (left - mPadding) * (1.f - offset);
right = right + (width - mPadding - right) * offset;
canvas.drawRect(left, top, right, bottom, mMainPaint);
}
...
}
DEVELOPMENT
PROTOTYPING
DEVELOPMENT
PROTOTYPING
INTERACTION
HOME
SYMBOLIC ICON
01.
REQUEST
동영상 또는 GIF 가 탭마다 플레이
ISSUE
빠르게 Tab 이동시 생기는 문제들
프레임 드랍, 메모리 이슈 등
SOLUTION
아이콘을 Path 로 구현
+ Tab 이동시 Path Morphing
DESIGN
PROTOTYPING
C A S H N O W T I M E P E R I O D C H O I C E
DESIGN
CONCEPT
<string name="shape_path_hour_glass">
M 57.6,10.5 L 16,10.5 C 16,10.5 78.6,99.5 78.6,99.5 L 120.3,99.5 C 120.3,99.5 57.6,10.5 57.6,10.5 z
M 120.3,99.5 L 78.6,99.5 C 78.6,99.5 16,188.4 16,188.4 L 57.6,188.4 C 57.6,188.4 120.3,99.5 120.3,99.5 z
M 78.6,99.5 L 120.3,99.5 C 120.3,99.5 183,10.5 183,10.5 L 141.3,10.5 C 141.3,10.5 78.6,99.5 78.6,99.5 z
M 141.3,188.4 L 183,188.4 C 183,188.4 120.3,99.5 120.3,99.5 L 78.6,99.5 C 78.6,99.5 141.3,188.4 141.3,188.4 z
M 40.2,154.5 L 16,188.4 C 16,188.4 183,188.4 183,188.4 L 158.7,154.5 C 158.7,154.5 40.2,154.5 40.2,154.5 z
</string>
<string name="shape_path_hour_glass_end">
M 158.7,44 L 183,10.5 C 183,10.5 16,10.5 16,10.5 L 40.2,44 C 40.2,44 158.7,44 158.7,44 z
</string>
<string name="shape_path_circle">
M 70.2,48.8 L 52.2,17.7 C 24.0,34.0 5.0,64.4 4.9,99.5 L 41.0,99.6 C 40.9,77.9 52.8,59.0 70.2,48.8 z
M 41.1,99.5 L 4.9,99.5 C 6.1,131.5 22.0,163.8 52.2,181.4 L 70.2,150.1 C 51.5,139.3 41.1,119.7 41.1,99.5 z
M 158.1,99.5 L 194.1,99.5 C 194.0,66.9 177.0,35.2 146.7,17.7 L 128.7,48.9 C 147.5,59.7 157.9,79.3 157.9,99.5 z
M 128.8,150.2 L 146.8,181.3 C 175.0,165.0 194.0,134.6 194.1,99.5 L 158.1,99.5 C 158.1,121.1 146.2,140.0 128.8,150.2 z
M 70.3,150.1 L 52.2,181.4 C 80.5,197.6 116.4,198.8 146.8,181.3 L 128.8,150.2 C 110.0,161.0 87.8,160.2 70.3,150.1 z
</string>
<string name="shape_path_circle_end">
M 128.7,48.9 L 146.7,17.7 C 118.5,1.4 82.6,0.2 52.2,17.7 L 70.2,48.8 C 89.0,38.0 111.2,38.8 128.7,48.9 z
</string>
L 52.2,17.7 C 24.0,34.0 5.0,64.4 4.9,99.5
L 16,10.5 C 16,10.5 78.6,99.5 78.6,99.5
DEVELOPMENT
PROTOTYPING
switch (mPathIndex) {
case 0: // C
canvas.rotate(90.f * animateOffset + 90.f, centerX, centerY);
break;
case 2: // N
canvas.rotate(90.f * animateOffset + 180.f, centerX, centerY);
break;
case 4: // hourglass
canvas.rotate(90.f * animateOffset + 0.f, centerX, centerY);
break;
case 8: // ice
canvas.rotate(90.f * animateOffset + 90.f, centerX, centerY);
break;
case 6: // circle
default:
canvas.rotate(90.f * animateOffset, centerX, centerY);
break;
}
DEVELOPMENT
PROTOTYPING
DEVELOPMENT
PROTOTYPING
CONSULTATION
CONSULTATION
DEVELOPMENT
PROTOTYPING
OUTPUT
INTERACTION
HOME
LIST ANIMATION
01.
DESIGN
PROTOTYPING
DEFAULT
LIST
ANIMATION
DEVELOPMENT
PROTOTYPING
android.support.v7.widget.DefaultItemAnimator
코드를 복사해서 callback 추가
DEVELOPMENT
PROTOTYPING
CUSTOM
ANIMATION
CustomItemAnimator animator = new CustomItemAnimator();
animator.setItemAnimatorListener(new CustomItemAnimator.ItemAnimatorListener() {
@Override
public void onAddPreStart(RecyclerView.ViewHolder viewHolder) {
View view = viewHolder.itemView;
ViewCompat.setAlpha(view, 0f);
ViewCompat.setTranslationY(view, view.getHeight() / 4);
}
@Override
public void onAddAnimatorPrepare(RecyclerView.ViewHolder viewHolder
, ViewPropertyAnimatorCompat animator) {
animator.alpha(1.f)
.translationY(0f)
.setInterpolator(DECELERATE_INTERPOLATOR);
int position = viewHolder.getAdapterPosition();
if (position >= 0 && position < 10) {
animator.setStartDelay(50 * position);
}
}
@Override
public void onAddFinished(RecyclerView.ViewHolder viewHolder) {
View view = viewHolder.itemView;
ViewCompat.setAlpha(view, 1f);
ViewCompat.setTranslationY(view, 0f);
}
});
DEVELOPMENT
PROTOTYPING
STEP 1
Data 교체
STEP 2
Add Animation start
STEP 3
Scroll Up
STEP 4
새로운 row 추가 (Add Animation 없음)
ISSUE
DEVELOPMENT
PROTOTYPING
public class CustomItemAnimator extends SimpleItemAnimator {
...
@Override
public void runPendingAnimations() {
...
ArrayList<RecyclerView.ViewHolder> pendingList
= adapter.getPendingViewHolder();
for (int i = additions.size(); i < pendingList.size(); i++) {
RecyclerView.ViewHolder holder = pendingList.get(i);
itemAnimatorListener.onAddPreStart(holder);
additions.add(holder);
}
adapter.clearPendingViewHolder();
...
}
...
}
additions.add(holder);
OUTPUT
SCENE TRANSITION
TITLE PAGE
CUSTOM
02.
DESIGN
PROTOTYPING
ActivityTransition
문제점
SharedElement
CONSULTATION
SharedElement 로 사용되는
이미지 로드가 느림
ISSUE 1
CONSULTATION
getLocationInWindow() 값이
StatusBar Height 만큼 어긋남
ISSUE 2
CONSULTATION
ChangeBounds 가 오동작하여
이미지 Scale 이 어긋남
ISSUE 3
CONSULTATION
overridePendingTransition(R.anim.fadein, R.anim.fadeout);
STEP 1
TitlePage Activity Start
STEP 2
HOME Activity, TitlePage Activity Cross Fade
Custom
EnterTransition
DEVELOPMENT
PROTOTYPING
STEP 3
이미지 Show(onPreDraw)
DEVELOPMENT
PROTOTYPING
Custom
EnterTransition
STEP 4
Animation Start
DEVELOPMENT
PROTOTYPING
Custom
EnterTransition
private void startEnterAnimation() {
Rect previewRect = mPreViewRect;
float scaleX = (float) previewRect.width() / mImagePreview.getWidth();
float scaleY = (float) previewRect.height() / mImagePreview.getHeight();
int transX = previewRect.left - mImagePreview.getLeft() -
(mImagePreview.getWidth() - previewRect.width()) / 2;
int transY = previewRect.top - mImagePreview.getTop();
ValueAnimator animator = ValueAnimator.ofFloat(0.f, 1.f);
animator.addUpdateListener(animation -> {
Float offset = (Float) animation.getAnimatedValue();
float reverseOffset = 1.f - offset;
ViewCompat.setTranslationX(mImagePreview, transX * reverseOffset);
ViewCompat.setTranslationY(mImagePreview, transY * reverseOffset);
ViewCompat.setScaleX(mImagePreview, (1.f - scaleX) * offset + scaleX);
ViewCompat.setScaleY(mImagePreview, (1.f - scaleY) * offset + scaleY);
});
animator.setInterpolator(DECELERATE_INTERPOLATOR);
animator.start();
}
DEVELOPMENT
PROTOTYPING
Custom
EnterTransition
DEVELOPMENT
PROTOTYPING
SCENE TRANSITION
TITLE PAGE
CURVE ANIMATION
02.
private void startEnterAnimation() {
Rect previewRect = mPreViewRect;
float scaleX = (float) previewRect.width() / mImagePreview.getWidth();
float scaleY = (float) previewRect.height() / mImagePreview.getHeight();
int transX = previewRect.left - mImagePreview.getLeft() -
(mImagePreview.getWidth() - previewRect.width()) / 2;
int transY = previewRect.top - mImagePreview.getTop();
ValueAnimator animator = ValueAnimator.ofFloat(0.f, 1.f);
animator.addUpdateListener(animation -> {
Float offset = (Float) animation.getAnimatedValue();
float reverseOffset = 1.f - offset;
ViewCompat.setTranslationX(mImagePreview, transX
* (1.f - DECELERATE_INTERPOLATOR.getInterpolation(offset)));
ViewCompat.setTranslationY(mImagePreview, transY * reverseOffset);
ViewCompat.setScaleX(mImagePreview, (1.f - scaleX) * offset + scaleX);
ViewCompat.setScaleY(mImagePreview, (1.f - scaleY) * offset + scaleY);
});
animator.setInterpolator(DECELERATE_INTERPOLATOR);
animator.start();
}
ViewCompat.setTranslationX(mImagePreview, transX
* (1.f - DECELERATE_INTERPOLATOR.getInterpolation(offset)));
Curve Animation
DEVELOPMENT
PROTOTYPING
SCENE TRANSITION
TITLE PAGE
RIPPLE EFFECT
02.
Ripple Effect
DEVELOPMENT
PROTOTYPING
@Override
protected void onDraw(Canvas canvas) {
int centerX = getMeasuredWidth() / 2;
int centerY = getMeasuredHeight() / 2;
float radius = (float) (Math.sqrt(centerX * centerX + centerY * centerY)
* animateOffset);
path.reset();
path.addCircle(centerX, centerY, radius, Path.Direction.CW);
path.close();
canvas.save();
canvas.clipPath(path);
mDrawable = getDrawable();
if (mDrawable == null) {
mBackground.draw(canvas);
} else {
mDrawable.draw(canvas);
}
canvas.restore();
}
canvas.clipPath(path);
Ripple Effect
DEVELOPMENT
PROTOTYPING
OUTPUT
ISSUE 1
TitlePage Activity Start
+ Image onPreDraw 시간만큼 딜레이 존재
DEVELOPMENT
PROTOTYPING
EnterTransition 을 Main 에서 처리
(lifecycle 활용)
SOLUTION 1
DEVELOPMENT
PROTOTYPING
SOLUTION 1
WebtoonHomeTransitionManager.instance.enterTrasition(lifecycle,
object : EnterTransitionListener {
override fun onActivityTransitionEnd() {
WebtoonHomeActivity.startActivity(activity, targetView)
}
override fun setContentAlpha(alpha: Float) {
id_home_drawer_container?.alpha = alpha
}
})
DEVELOPMENT
PROTOTYPING
VER. 2.1VER. 2.0
OUTPUT
Main Activity 로 돌아갈 때
Cross Fade 느낌이 없음
ISSUE 2
DEVELOPMENT
PROTOTYPING
ActivityTransition
Fade 사용
SOLUTION 2
DEVELOPMENT
PROTOTYPING
VER. 2.1VER. 2.0
OUTPUT
OUTPUT
Splash
Natural
Focusing
Meaningful
INTERACTION
KEYWORD
Creative
Symbolic Icon
All
Splash
Ripple, List
NEXT
DESIGN
PROTOTYPING
THANK YOU

다음웹툰의 UX(Animation, Transition, Custom View)

  • 1.
    다음웹툰의 UX 함태윤(erkas.c) DAUM WEBTOONCOMPANY Animation, Transition, CustomView
  • 2.
  • 3.
    개선이 너무 잘된 것 같습니다. 팝업 모션이 너무 좋아요!!! 댓글 스포일러 막는 것도 좋고 스크롤 내릴 때 캐릭터와 배경이 따로 분리되서 내려오는 것도 너무 좋아요 ㅠㅠ 진짜 매번 이걸로 웹툰 볼 때 느끼는 건데 깔끔하고 요일로 정리한 거랑 보려고 하는 웹툰 눌렀을 때? 웹툰이 올라오면서 켜지는거 너무 이뻐서 좋아요!! 솔직히 타 플랫폼은 비슷비슷한데 여긴 저렇게 깔끔하게 되어있어서 좋아요!! 다음웹툰도 즐겨보지만 UI가 매우 마음에 듭니다 개발자에게 전화를 걸었지만 모든 상담사가 통화중이라고 하더군요 혹시 개발자님과 이야기 하려면 어떻게 해야 하나요...?
  • 5.
  • 6.
  • 7.
  • 8.
    DESIGN PROTOTYPING STEP 1 CONSULTATION STEP 2 DEVELOPMENT PROTOTYPING STEP3 REVIEW STEP 4 OUTPUT STEP 6 GO BACK STEP 5 WORK PROCESS
  • 9.
    01. HOME INTERACTION SPLASH SYMBOLIC ICON LIST ANIMATION 02. TITLEPAGE SCENE TRANSITION CUSTOM ENTER TRANSITION CURVE ANIMATION RIPPLE EFFECT CONTENTS
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
    protected void onDraw(Canvascanvas) { ... if (offset < 0.5f) { // 빨간 사각형 canvas.drawRect(left, top, right, bottom, mMainPaint); // 하얀 사각형 if (animateLength <= mGapHeight) { bottom = centerY - mContentHeight / 2; top = bottom - mGapHeight + animateLength; canvas.drawRect(left, top, right, bottom, mSubPaint); top = centerY + mContentHeight / 2; bottom = top + mGapHeight - animateLength; canvas.drawRect(left, top, right, bottom, mSubPaint); } } else if (offset >= 0.5f) { float offset = (this.offset - 0.5f) * 2f; top = centerY - mEndHeight / 2; bottom = centerY + mEndHeight / 2; left = mPadding + (left - mPadding) * (1.f - offset); right = right + (width - mPadding - right) * offset; canvas.drawRect(left, top, right, bottom, mMainPaint); } ... } DEVELOPMENT PROTOTYPING
  • 18.
  • 19.
  • 20.
    REQUEST 동영상 또는 GIF가 탭마다 플레이 ISSUE 빠르게 Tab 이동시 생기는 문제들 프레임 드랍, 메모리 이슈 등 SOLUTION 아이콘을 Path 로 구현 + Tab 이동시 Path Morphing DESIGN PROTOTYPING
  • 21.
    C A SH N O W T I M E P E R I O D C H O I C E DESIGN CONCEPT
  • 22.
    <string name="shape_path_hour_glass"> M 57.6,10.5L 16,10.5 C 16,10.5 78.6,99.5 78.6,99.5 L 120.3,99.5 C 120.3,99.5 57.6,10.5 57.6,10.5 z M 120.3,99.5 L 78.6,99.5 C 78.6,99.5 16,188.4 16,188.4 L 57.6,188.4 C 57.6,188.4 120.3,99.5 120.3,99.5 z M 78.6,99.5 L 120.3,99.5 C 120.3,99.5 183,10.5 183,10.5 L 141.3,10.5 C 141.3,10.5 78.6,99.5 78.6,99.5 z M 141.3,188.4 L 183,188.4 C 183,188.4 120.3,99.5 120.3,99.5 L 78.6,99.5 C 78.6,99.5 141.3,188.4 141.3,188.4 z M 40.2,154.5 L 16,188.4 C 16,188.4 183,188.4 183,188.4 L 158.7,154.5 C 158.7,154.5 40.2,154.5 40.2,154.5 z </string> <string name="shape_path_hour_glass_end"> M 158.7,44 L 183,10.5 C 183,10.5 16,10.5 16,10.5 L 40.2,44 C 40.2,44 158.7,44 158.7,44 z </string> <string name="shape_path_circle"> M 70.2,48.8 L 52.2,17.7 C 24.0,34.0 5.0,64.4 4.9,99.5 L 41.0,99.6 C 40.9,77.9 52.8,59.0 70.2,48.8 z M 41.1,99.5 L 4.9,99.5 C 6.1,131.5 22.0,163.8 52.2,181.4 L 70.2,150.1 C 51.5,139.3 41.1,119.7 41.1,99.5 z M 158.1,99.5 L 194.1,99.5 C 194.0,66.9 177.0,35.2 146.7,17.7 L 128.7,48.9 C 147.5,59.7 157.9,79.3 157.9,99.5 z M 128.8,150.2 L 146.8,181.3 C 175.0,165.0 194.0,134.6 194.1,99.5 L 158.1,99.5 C 158.1,121.1 146.2,140.0 128.8,150.2 z M 70.3,150.1 L 52.2,181.4 C 80.5,197.6 116.4,198.8 146.8,181.3 L 128.8,150.2 C 110.0,161.0 87.8,160.2 70.3,150.1 z </string> <string name="shape_path_circle_end"> M 128.7,48.9 L 146.7,17.7 C 118.5,1.4 82.6,0.2 52.2,17.7 L 70.2,48.8 C 89.0,38.0 111.2,38.8 128.7,48.9 z </string> L 52.2,17.7 C 24.0,34.0 5.0,64.4 4.9,99.5 L 16,10.5 C 16,10.5 78.6,99.5 78.6,99.5 DEVELOPMENT PROTOTYPING
  • 23.
    switch (mPathIndex) { case0: // C canvas.rotate(90.f * animateOffset + 90.f, centerX, centerY); break; case 2: // N canvas.rotate(90.f * animateOffset + 180.f, centerX, centerY); break; case 4: // hourglass canvas.rotate(90.f * animateOffset + 0.f, centerX, centerY); break; case 8: // ice canvas.rotate(90.f * animateOffset + 90.f, centerX, centerY); break; case 6: // circle default: canvas.rotate(90.f * animateOffset, centerX, centerY); break; } DEVELOPMENT PROTOTYPING
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
    CustomItemAnimator animator =new CustomItemAnimator(); animator.setItemAnimatorListener(new CustomItemAnimator.ItemAnimatorListener() { @Override public void onAddPreStart(RecyclerView.ViewHolder viewHolder) { View view = viewHolder.itemView; ViewCompat.setAlpha(view, 0f); ViewCompat.setTranslationY(view, view.getHeight() / 4); } @Override public void onAddAnimatorPrepare(RecyclerView.ViewHolder viewHolder , ViewPropertyAnimatorCompat animator) { animator.alpha(1.f) .translationY(0f) .setInterpolator(DECELERATE_INTERPOLATOR); int position = viewHolder.getAdapterPosition(); if (position >= 0 && position < 10) { animator.setStartDelay(50 * position); } } @Override public void onAddFinished(RecyclerView.ViewHolder viewHolder) { View view = viewHolder.itemView; ViewCompat.setAlpha(view, 1f); ViewCompat.setTranslationY(view, 0f); } }); DEVELOPMENT PROTOTYPING
  • 34.
    STEP 1 Data 교체 STEP2 Add Animation start STEP 3 Scroll Up STEP 4 새로운 row 추가 (Add Animation 없음) ISSUE DEVELOPMENT PROTOTYPING
  • 35.
    public class CustomItemAnimatorextends SimpleItemAnimator { ... @Override public void runPendingAnimations() { ... ArrayList<RecyclerView.ViewHolder> pendingList = adapter.getPendingViewHolder(); for (int i = additions.size(); i < pendingList.size(); i++) { RecyclerView.ViewHolder holder = pendingList.get(i); itemAnimatorListener.onAddPreStart(holder); additions.add(holder); } adapter.clearPendingViewHolder(); ... } ... } additions.add(holder); OUTPUT
  • 36.
  • 37.
  • 38.
  • 39.
    SharedElement 로 사용되는 이미지로드가 느림 ISSUE 1 CONSULTATION
  • 40.
    getLocationInWindow() 값이 StatusBar Height만큼 어긋남 ISSUE 2 CONSULTATION
  • 41.
    ChangeBounds 가 오동작하여 이미지Scale 이 어긋남 ISSUE 3 CONSULTATION
  • 42.
    overridePendingTransition(R.anim.fadein, R.anim.fadeout); STEP 1 TitlePageActivity Start STEP 2 HOME Activity, TitlePage Activity Cross Fade Custom EnterTransition DEVELOPMENT PROTOTYPING
  • 43.
  • 44.
  • 45.
    private void startEnterAnimation(){ Rect previewRect = mPreViewRect; float scaleX = (float) previewRect.width() / mImagePreview.getWidth(); float scaleY = (float) previewRect.height() / mImagePreview.getHeight(); int transX = previewRect.left - mImagePreview.getLeft() - (mImagePreview.getWidth() - previewRect.width()) / 2; int transY = previewRect.top - mImagePreview.getTop(); ValueAnimator animator = ValueAnimator.ofFloat(0.f, 1.f); animator.addUpdateListener(animation -> { Float offset = (Float) animation.getAnimatedValue(); float reverseOffset = 1.f - offset; ViewCompat.setTranslationX(mImagePreview, transX * reverseOffset); ViewCompat.setTranslationY(mImagePreview, transY * reverseOffset); ViewCompat.setScaleX(mImagePreview, (1.f - scaleX) * offset + scaleX); ViewCompat.setScaleY(mImagePreview, (1.f - scaleY) * offset + scaleY); }); animator.setInterpolator(DECELERATE_INTERPOLATOR); animator.start(); } DEVELOPMENT PROTOTYPING Custom EnterTransition
  • 46.
  • 47.
  • 48.
    private void startEnterAnimation(){ Rect previewRect = mPreViewRect; float scaleX = (float) previewRect.width() / mImagePreview.getWidth(); float scaleY = (float) previewRect.height() / mImagePreview.getHeight(); int transX = previewRect.left - mImagePreview.getLeft() - (mImagePreview.getWidth() - previewRect.width()) / 2; int transY = previewRect.top - mImagePreview.getTop(); ValueAnimator animator = ValueAnimator.ofFloat(0.f, 1.f); animator.addUpdateListener(animation -> { Float offset = (Float) animation.getAnimatedValue(); float reverseOffset = 1.f - offset; ViewCompat.setTranslationX(mImagePreview, transX * (1.f - DECELERATE_INTERPOLATOR.getInterpolation(offset))); ViewCompat.setTranslationY(mImagePreview, transY * reverseOffset); ViewCompat.setScaleX(mImagePreview, (1.f - scaleX) * offset + scaleX); ViewCompat.setScaleY(mImagePreview, (1.f - scaleY) * offset + scaleY); }); animator.setInterpolator(DECELERATE_INTERPOLATOR); animator.start(); } ViewCompat.setTranslationX(mImagePreview, transX * (1.f - DECELERATE_INTERPOLATOR.getInterpolation(offset))); Curve Animation DEVELOPMENT PROTOTYPING
  • 49.
  • 50.
  • 51.
    @Override protected void onDraw(Canvascanvas) { int centerX = getMeasuredWidth() / 2; int centerY = getMeasuredHeight() / 2; float radius = (float) (Math.sqrt(centerX * centerX + centerY * centerY) * animateOffset); path.reset(); path.addCircle(centerX, centerY, radius, Path.Direction.CW); path.close(); canvas.save(); canvas.clipPath(path); mDrawable = getDrawable(); if (mDrawable == null) { mBackground.draw(canvas); } else { mDrawable.draw(canvas); } canvas.restore(); } canvas.clipPath(path); Ripple Effect DEVELOPMENT PROTOTYPING
  • 52.
  • 53.
    ISSUE 1 TitlePage ActivityStart + Image onPreDraw 시간만큼 딜레이 존재 DEVELOPMENT PROTOTYPING
  • 54.
    EnterTransition 을 Main에서 처리 (lifecycle 활용) SOLUTION 1 DEVELOPMENT PROTOTYPING
  • 55.
    SOLUTION 1 WebtoonHomeTransitionManager.instance.enterTrasition(lifecycle, object :EnterTransitionListener { override fun onActivityTransitionEnd() { WebtoonHomeActivity.startActivity(activity, targetView) } override fun setContentAlpha(alpha: Float) { id_home_drawer_container?.alpha = alpha } }) DEVELOPMENT PROTOTYPING
  • 56.
  • 57.
    Main Activity 로돌아갈 때 Cross Fade 느낌이 없음 ISSUE 2 DEVELOPMENT PROTOTYPING
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.