본문 바로가기

안드로이드/레이아웃 구현과 활용

[안드로이드/Android] Brunch 메인 UX 구현하기

반응형

 



안녕하세요 이번 포스팅은 '브런치 메인 UX 구현하기' 입니다.
이번 포스팅을 계기로 주기적으로 현재 서비스중인 앱들을 분석해 구현해보는 포스팅을 종종 올릴 예정입니다! 
혹시 댓글로 신청 해주시면 다음 포스팅은 신청받은 앱으로 진행하겠습니다!

브런치 앱 다운받기 - >링크

 

브런치(Brunch)란?


브런치는 2017년 구글이 선정한 올해를 빛낸앱 소셜부문 최우수상을 수상한 앱으로 좋은 글을 구독할 수 있는 앱입니다.

다음 카카오에서 서비스중인 앱입니다.

 

 

분석하기


분석이라는 단어가 과분하긴 하지만, 구현할 메인 UX 부분을 살펴볼 필요는 있습니다. ( 직접 작성하신 글이라서 모자이크 처리 했습니다. )

앱을 다운받아서 보신분들이나 기존에 사용중이신 분들은 아시겠지만, 메인의 글 목록을 살펴보는 UX로 Vertical Viewpager가 사용됐습니다.

기본 Viewpager는 Horizontal 방식이기 때문에 Viewpager를 커스텀해서 구현하겠습니다.

 

 

구현하기


다음과 같은 순서대로 구현하겠습니다.

  • 좌↔우 페이지 변환을 상↔하 페이지 변환으로 변경
  • 좌↔우 슬라이드시 페이지 전환 되는 것을 상↔하 슬라이드시 페이지 전환 되도록 변경
  • 페이지 전환시 효과 주기

 

페이지 전환 변경


해당 포스팅은 Viewpager의 'pageTransformer'를 사용해서 구현하기 때문에 먼저 간단하게 알아보도록 하겠습니다.

ViewPager.PageTransformer 인터페이스의 'transformPage' 메소드는 뷰페이저의 페이지 변화가 일어나면 호출되는 메소드로 페이지 이동에따라 컨트롤이 가능해, 해당 포스팅에서 다룬 브런치 스타일 외에도 여러가지가 가능합니다. 

 

'transformPage' 메소드의 파라미터는 page 와 position으로 페이지 체인지가 발생할때 각각 움직이는 페이지들의 포지션값을 나타냅니다.

각 페이지별로 따로 발생하는게 아니기 때문에 분기처리가 필요합니다.

처음 뷰페이저를 그리면 뷰 페이저들의 'transformPage' 메소드 position 값은 0,1 입니다. ( 기본 뷰페이저는 좌우 한페이지씩 미리 로딩합니다. ) 

이후 페이지가 왼쪽으로 넘어갈 경우 기준값 에서 값이 감소해 페이지 전환이 완료돼면 기준값 -1 값으로 변경됩니다.

예) 초기값 0(기준),1  - > -1,0,1

binding.vpMain.setPageTransformer(false, new ViewPager.PageTransformer() {
@Override
public void transformPage(View page, float position) {
if (position < -1) {
page.setAlpha(0);

} else if (position <= 1) {
page.setAlpha(1);
page.setTranslationX(page.getWidth() * -position);
page.setTranslationY(page.getHeight()* position);
} else {
page.setAlpha(0);
}
}
});

간단히 페이지 변환시 눈에 보이는 실제 뷰의 값은 -1 ~ 1 사이 값이라고 생각하시면 될것 같습니다. 

-1 , 0 , 1 의 경우 각각 좌표는 -1 일때 (0 , -page.getHeight()) , 0 일때 (0,0) , 1 일때 (0,page.getHeight) 로 아래 그림과 같이 변환됩니다.

좌우 슬라이드시 뷰페이저의 페이지들은 상하로 움직이는걸 확인 하실 수 있습니다.

 

슬라이드 이벤트 변경 


뷰페이저의 기본 이벤트인 좌우 슬라이드시 페이지가 변경됐던 부분을 상하 슬라이드시 페이지가 변경되도록 구현하겠습니다.

터치 이벤트의 xy 좌표를 전환하는 방법입니다.

private MotionEvent swapXY(MotionEvent event) {
float width = getWidth();
float height = getHeight();
float newX = (event.getY() / height) * width;
float newY = (event.getX() / width) * height;

event.setLocation(newX, newY);
return event;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(mSwipeOrientation == VERTICAL ? swapXY(event) : event);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mSwipeOrientation == VERTICAL) {
boolean intercepted = super.onInterceptHoverEvent(swapXY(ev));
swapXY(ev);
return intercepted;
}
return super.onInterceptTouchEvent(ev);
}

위의 코드를 커스텀하는 viewpager에 추가하시면 됩니다.

 

 

전환 효과 적용하기


브런치앱을 살펴보면 페이지 변환시 기존에 있던 페이지는 새로 올라오는 페이지의 뒤로 사라지는 형식입니다. 


private class VerticalPageTransformer implements ViewPager.PageTransformer {

@Override
public void transformPage(View page, float position) {
if (position <= -1) {
page.setAlpha(0);
} else if (position <= 1) {
if (position < 0) {
float rate = 1f + (0.3f * position);
page.setScaleX(rate);
page.setScaleY(rate);
page.setAlpha(rate);
} else {
page.setTranslationY(page.getHeight() * position);
page.setAlpha(1);
page.setScaleX(1);
page.setScaleY(1);
}
page.setTranslationX(page.getWidth() * -position);
} else {
page.setAlpha(0);
}
}
}

저는 기존 크기의 0.3 만큼 줄어들게 했습니다.

 

이와 같은 코드로 테스트를 해보시면 확인하실 수 있습니다.

 

커스텀 뷰페이저의 전체 소스는 아래를 확인해주세요.

 

더보기


public class VerticalViewpager extends ViewPager {

private String TAG = "VerticalViewpager";
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;

private int mSwipeOrientation;

public VerticalViewpager(Context context) {
super(context);
mSwipeOrientation = HORIZONTAL;
}

public VerticalViewpager(Context context, AttributeSet attrs) {
super(context, attrs);
setSwipeOrientation(context, attrs);

}

private MotionEvent swapXY(MotionEvent event) {
float width = getWidth();
float height = getHeight();

float newX = (event.getY() / height) * width;
float newY = (event.getX() / width) * height;
event.setLocation(newX, newY);
return event;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(mSwipeOrientation == VERTICAL ? swapXY(event) : event);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mSwipeOrientation == VERTICAL) {
boolean intercepted = super.onInterceptHoverEvent(swapXY(ev));
swapXY(ev);
return intercepted;
}
return super.onInterceptTouchEvent(ev);
}

// public void setSwipeOrientation(int swipeOrientation) {
// if (swipeOrientation == HORIZONTAL || swipeOrientation == VERTICAL)
// mSwipeOrientation = swipeOrientation;
// else
// throw new IllegalStateException("Swipe Orientation can be either CustomViewPager.HORIZONTAL" +
// " or CustomViewPager.VERTICAL");
// initSwipeMethods();
// }

private void setSwipeOrientation(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomViewPager);
mSwipeOrientation = typedArray.getInteger(R.styleable.CustomViewPager_swipe_orientation, 0);
typedArray.recycle();
initSwipeMethods();
}


private void initSwipeMethods() {
if (mSwipeOrientation == VERTICAL) {
setPageTransformer(false, new VerticalPageTransformer());
setOverScrollMode(OVER_SCROLL_NEVER);
}
}


private class VerticalPageTransformer implements ViewPager.PageTransformer {

@Override
public void transformPage(View page, float position) {
if (position <= -1) {
page.setAlpha(0);
} else if (position <= 1) {
if (position < 0) {
float rate = 1f + (0.3f * position);
page.setScaleX(rate);
page.setScaleY(rate);
page.setAlpha(rate);
} else {
page.setTranslationY(page.getHeight() * position);
page.setAlpha(1);
page.setScaleX(1);
page.setScaleY(1);
}
page.setTranslationX(page.getWidth() * -position);
} else {
page.setAlpha(0);
}
}
}
}

 

 

 

 

 

해당 포스팅은 지극히 주관적인 내용으로 저자의 복기 목적으로 작성된 것이고, 내용에대해 수정이나 추가 요청은 언제든 환영합니다. 

 

 

 

 

 

반응형