옥션이나 지마켓을 보면 쇼핑몰들은 다이나믹한 레이아웃을 사용해 사용자들의 시선을 끄는 어플이 대세인것 같다.
최근에 사업팀이 이쪽에 꼿혀서 만들어달라고 제안이 들어왔는데 그때 해본걸 정리하려고한다.
상단동작
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:animateLayoutChanges="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/top_app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.MaterialComponents.ActionBar"
android:background="@color/colorPrimary"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways|snap">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="75dp"
android:background="@color/dark_gray"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/main_recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_anchor="@id/main_bottom_navbar"
app:layout_anchorGravity="top" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/main_bottom_navbar"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_gravity="bottom"
app:labelVisibilityMode="labeled"
app:itemIconTint="@color/bottom_nav_color"
app:itemTextColor="@color/bottom_nav_color"
app:menu="@menu/bottom_nav_bar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
- 구글이 빠른 개발속도를 위해 편리한 위젯을 많이 만들어 놓고있다 그중에서도 가장많이 쓰이는것이 AppbarLayout,collapsingToolbarLayout,Toolbar조합일 것이다.
- 꼭 coordinatorLayout안쪽에 구성을 해야한다. 그이유는 리사이클뷰에
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
layout_behavior 속성을 사용하기위해서이다.
- 눈여겨 봐야할 부분은 첫번째로 collapsingToolbarLayout에
app:layout_scrollFlags="scroll|enterAlways|snap"
이부분이다. 레이아웃이 반쯤 들어가면 자동으로 사라지고 아니면 자동으로 이전 레이아웃으로 복구가된다.
-두번째는 recyclerView에
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
이부분이다 . 이부분의 터치에따라 appbar와 bottomNavigationbar가 보여지고 사라진다.
하단동작
public class TopNavbarBehavior extends CoordinatorLayout.Behavior<View> {
private static final String TAG = "BottomNavBehavior";
private float originHeight;
private Context mContext;
private Activity mActivity;
private float endTransitionY;
public TopNavbarBehavior() {
}
public TopNavbarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
}
public TopNavbarBehavior(Context context){
this.mContext = context;
}
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
originHeight = Math.max(0f, Math.min(Float.parseFloat(String.valueOf(child.getHeight())), child.getMeasuredHeight()));
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;//수직일때 작동
}
@Override
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
//Behavior을 가진 View(child)
//setTranslationY: 상단 위치를 기준으로 수직 위치를 설정합니다.
//getTranslationY: 상단 위치를 기준으로 하고 Layout에 배치된 위치에 추가하여 개체의 위치를 효과적으로 지정할 수 있습니다.
//dyConsumed: 타겟의 스크롤 조작으로 인해 소비된 수직 픽셀
float transitionY = Math.max(0f, Math.min(Float.parseFloat(String.valueOf(child.getHeight())), child.getTranslationY() + dy));
child.setTranslationY(transitionY);
endTransitionY = transitionY;
}
@Override
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int type) {
//반이거나 반이상이면 숨기기
if ((originHeight/2) >= endTransitionY ) {
child.setTranslationY(0);
}
//반이하면 보이기
else if ((originHeight/2) < endTransitionY ){
child.setTranslationY(originHeight);
}
}
}
-coordinatorLayout을 사용하는 이유가 여기서 하나더있다.
CoordinatorLayout.Behavior<View>
이녀석을 상속받기위해 사용한다.
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
originHeight = Math.max(0f, Math.min(Float.parseFloat(String.valueOf(child.getHeight())), child.getMeasuredHeight()));
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;//수직일때 작동
}
- 현재위치를 초기화시켜주고, 방향을 결정한다.
axes == 이부분은 onNestedPreScroll,onStopNestedScroll을 작동시킬껀지 결정해주는 부분이다.
@Override
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
//Behavior을 가진 View(child)
//setTranslationY: 상단 위치를 기준으로 수직 위치를 설정합니다.
//getTranslationY: 상단 위치를 기준으로 하고 Layout에 배치된 위치에 추가하여 개체의 위치를 효과적으로 지정할 수 있습니다.
//dyConsumed: 타겟의 스크롤 조작으로 인해 소비된 수직 픽셀
float transitionY = Math.max(0f, Math.min(Float.parseFloat(String.valueOf(child.getHeight())), child.getTranslationY() + dy));
child.setTranslationY(transitionY);
endTransitionY = transitionY;
}
- 솔직히 수학을 못해서 무슨원리로 작동하는지는 이해가 안되지만 위에 주석을 보고 어렴풋히 알것도 같다.
@Override
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int type) {
//반이거나 반이상이면 숨기기
if ((originHeight/2) >= endTransitionY ) {
child.setTranslationY(0);
}
//반이하면 보이기
else if ((originHeight/2) < endTransitionY ){
child.setTranslationY(originHeight);
}
}
- 상단동작부분에
app:layout_scrollFlags="scroll|enterAlways|snap"
이런 부분이 있엇을것이다. 위에서 설명했듯이 손가락으로 드래그해서 사라진 레이아웃이 반이상이면 부드럽게 사라지고 아니면 보이게되는 부분인데 아래쪽도 아주 똑같지는 않지만 비슷하게 동작하도록 계산을해서 보이고 숨기고를 구성해보았다.
조립하기
public class MainActivity extends AppCompatActivity {
BottomNavigationView bottomNavbar;
AppBarLayout toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//리사클뷰
RecyclerView rv = findViewById(R.id.main_recycle_view);
MainRecyclerview adapter = new MainRecyclerview(this);
LinearLayoutManager lm = new LinearLayoutManager(this);
rv.setLayoutManager(lm);
rv.setAdapter(adapter);
//하단 네비게이션바
bottomNavbar = findViewById(R.id.main_bottom_navbar);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) bottomNavbar.getLayoutParams();
BottomNavBehavior bnb = new BottomNavBehavior(this);
params.setBehavior(bnb);
}
}
스크린샷은 추후에 업로드
언제나 고수분들 태클은 환영입니다.
감사합니다.