본문 바로가기

Android/Basic

[Android] ViewPager + Tab


사진 크기 지못미..


이번에 들은 강의는 탭 만들기. 예전에 막 하다가 해본 것 같은데, 이번 강의를 들으면서 다시 정리해보려고한다.


위 캡쳐에 보이는 것 처럼, 액션바 밑에 탭이 있는데 탭을 누르면 화면이 전환되는 기능을 구현할 것이다.


자세히 공부한적은 없지만, 탭 부분도 하나의 레이아웃인줄 알고있었다.

하지만 그게 아니라, 액션바 밑에 붙이는 것이었다. (어째 프리뷰에서 안보이더라)


지난번 포스팅에서와 같이 액션바를 직접 커스텀하는걸 했었는데, 마찬가지로 탭도 그냥 액션바에 붙여주기만 하면 되는것이다.

이를 구성하기 위해서는 외부라이브러리를 추가해주어야한다.

build.gradle에서

implementation 'com.android.support:design:27.1.1'

을 추가해주면 되지만, 딸깍딸깍으로도 할 수 있다. (뭔가 이게 더 안정적일것이다)

File -> project structure -> app -> + 버튼으로 support design dependency 누르고 support design을 찾아 추가해주면 된다.


전체적인 구현 순서는

1. activity_main.xml 에서 레이아웃 구성하기

2. fragment xml, inflate 해주기

3. MainActivity 에서 기본적인것들 추가해주고 탭 추가하기!


필자는 이거 하기 전에 AndroidStudio 업그레이드를 했는데 (3.1.2로)

갑자기 Theme도 못찾고(cannot resolve Theme) render 문제도 발생하고 난리가 나서 별 짓을 다해봤는데 그냥 프로젝트를 다시 만들었더니 됐다.

이거 누르면 해결된다는 사람도 있었다. 근데 나는 안됐다....


각설하고, 메인 액티비티의 xml 구조는 다음과 같다.

<CoordinatorLayout>
   <AppBarLayout>
      <Toolbar>
      </Toolbar>
      <TabLayout>
      </TabLayout>
   </AppBarLayout>
   <FrameLayout>
   </FrameLayout>
</CoordinatorLayout>

여기서 CoordinatorLayout 을 처음 접했는데, 자세하게 이게 뭔지 잘 안다루셔서 구글링을 좀 해봤.....지만 내용이 생각보다 많아서 일단은 접어두기로 한다.

관련 내용 정리가 잘 되어있어서 나중에 참고할 블로그 : http://dktfrmaster.blogspot.com/2018/03/coordinatorlayout.html


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">

<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryDark"
android:theme="@style/ThemeOverlay.AppCompat.Dark"
android:elevation="1dp"
android:id="@+id/toolbar">
</android.support.v7.widget.Toolbar>

<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="1dp"
android:background="@android:color/background_light"
app:tabMode="fixed"
app:tabGravity="fill"
app:tabTextColor="@color/colorPrimary"
app:tabSelectedTextColor="@color/colorAccent">
</android.support.design.widget.TabLayout>
</android.support.design.widget.AppBarLayout>

<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/container">

</FrameLayout>
</android.support.design.widget.CoordinatorLayout>
</RelativeLayout>

★ app:layout_behavior="@string/appbar_scrolling_view_behavior" 이 속성은 절대로 빼먹으면 안된다! 이거 없으면 내용 표시가 안된다. background만 나온다.

★ styles.xml 에서 noActionBar 로 액션바를 없애주어야한다. illegal Exception 나온다. 이미 액션바가 있다고...

android: 이 아닌 app: 은 외부 라이브러리 속성을 사용한다는 의미로 해석된다.

그리고 탭 레이아웃의 속성 중에 tapMode 는 고정시키는 역할, tapGravity는 탭이 2개, 3개든 가로 방향으로 꽉 채워준다는 의미라고한다.

appBarLayout은 상단에 보여지는 부분이고, 액션바가 아닌 그 아래의 레이아웃 영역은 coordinator 레이아웃 영역이다.

그리고 툴바에 들어있는 elevation 속성은 앞으로 툭 튀어나와있는 효과를 준다고한다. 


위에가 1dp 일 때고 아래는 50dp.. 무슨 느낌인지 잘 모르겠다.

TabLayout에 있는 elevation 속성은 변화를 못느껴서 테스트하지 않

았다.

출처 : 부스트코스 강의

레이아웃 구성은 위와 같이 된 것이고, 액션바부분만 빠진것이다.


이제 다음으로 프레임레이아웃에 띄워 줄 프래그먼트 3개를 만들면 된다.


fragment1.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_blue_bright">

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="첫번째화면"
android:textSize="40sp"/>

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"/>
</LinearLayout>


참고 : @android:color 안드로이드에서 제공하는 기본적인 색. 아주 다양하게 있었다. (난 지금까지 대충 #004400 이런식으로 잡고 직접 지정했는데.. 유용하게 써야겠다.)


Fragment1.class

public class Fragment1 extends Fragment {

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState){
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment1, container, false);

return rootView;
}
}


2, 3도 그냥 숫자만 바꿔서 만들어주면 된다.


마지막으로 MainActivity.class

public class MainActivity extends AppCompatActivity {
Fragment1 fragment1;
Fragment2 fragment2;
Fragment3 fragment3;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

fragment1 = new Fragment1();
fragment2 = new Fragment2();
fragment3 = new Fragment3();

getSupportFragmentManager().beginTransaction().add(R.id.container, fragment1).commit();

TabLayout tabs = findViewById(R.id.tabs);
tabs.addTab(tabs.newTab().setText("친구"));
tabs.addTab(tabs.newTab().setText("1:1채팅"));
tabs.addTab(tabs.newTab().setText("기타"));

tabs.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
int position = tab.getPosition();

Fragment selected = null;
if(position==0) {
selected = fragment1;
} else if(position == 1){
selected = fragment2;
} else if(position == 2){
selected = fragment3;
}

getSupportFragmentManager().beginTransaction().replace(R.id.container, selected).commit();
}

@Override
public void onTabUnselected(TabLayout.Tab tab) {

}

@Override
public void onTabReselected(TabLayout.Tab tab) {

}
});

}
}


보면 setOnSelectedListener가 deprecate 되어있는데, 아무 문제 없다고하셨다. (정말일까.. 다른 대안이 없을까? 의심)


프래그먼트를 자주 만들어서 사용해보니, 이제 좀 익숙해지는 느낌이 들어서 좋다.

하지만 아직 안보고는 못한다.

프래그먼트 : xml, java, inflate 순서 잊지말자!


아무튼 프레임레이아웃 부분을 슬라이딩 하면 넘어가는 것을 본적이 있는데 현재 내가 만든 예제는 그렇게 동작하지 않는다. 내가 원하는 기능은 뷰페이저였던가..?

곧 공부할 내용이므로 다시 포스팅하도록 할 것이다.


+ 번외편

첫 번째 탭 안에 있는 버튼을 눌렀을 때 두 번째 탭 화면이 보이도록 하고 싶다면 어떻게 하면 될까?


생각해보기 2번을 풀어보기로했다.

메인액티비티 자체(context?)를 참조하는 아주 간단한 방법이 있지만, 필자는 interface를 이용해 이벤트를 전달하는 방법으로 해보기로 했다.


우선 Fragment1.class 에 내부 interface를 선언하고, 멤버변수로 리스너를 만들고 onAttach 메소드에서 리스너를 등록하도록 했다.


변경된 Fragment1.class

public class Fragment1 extends Fragment {
changeButtonEvent listener;

public interface changeButtonEvent{
void onChangeButtonListener();
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState){
final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment1, container, false);

Button button = rootView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
listener.onChangeButtonListener();
}
});

return rootView;
}

@Override
public void onAttach(Context context) {
super.onAttach(context);
listener = (changeButtonEvent) context;
}
}


이벤트 전달 과정을 설명해봐! 라고 하면 잘 못하겠다... 뭘 주고받고 하는것 같은데 명확하게 설명이 안된다. 차차 알아가야지....  (미루면 안되지만ㅠㅠ)


아무튼, 다음으로 메인 액티비티에서 저 interface의 메소드를 구현하면 된다.

그래서 changeButtonEvent 인터페이스에 context를 다운캐스팅하는건가....?


public class MainActivity extends AppCompatActivity implements Fragment1.changeButtonEvent{
...
@Override
public void onChangeButtonListener() {
viewPager.setCurrentItem(1);
}


원해 replace 메소드만 있었는데, 탭 선택은 안바뀌고 프래그먼트만 전환되는 난감한 상황이 발생했다. 두번째 문제는 다시 첫번째 탭을 눌러도 전환되지 않고, 세번째 탭을 누르고 다시 첫번째 탭을 눌러야 전환되었다. (탭 선택 포지션이 변하지않아 탭 선택 이벤트도 발생하지 않았다.)


Q. 저는 interface를 이용해 이벤트를 전달해보았는데요, 프래그먼트 전환은 잘 되지만 탭 레이아웃 선택은 바뀌지않습니다. 그리고 첫번째 탭을 눌러도 첫번째 화면이 나오지않고 두번째 탭을 누른 뒤 다시 첫번째 탭을 눌러야하더라구요.. 이벤트 전달 방식이 잘못됐거나 아니면 탭에서 다른 처리를 해주어야하나요? 


A. 첫 번째 탭 안에 있는 버튼을 눌렀을 때 프래그먼트 전환이 아닌 두 번째 탭이 눌린 것과 같은 효과를 준다면 어떻게 될까요? 그렇게 하면 탭 레이아웃도 변경이 될 것 같습니다. 


질문에 대한 답변을 보고 혼자 쥐어짜낸 방법이 탭 선택 강제로 바꾸기.

@Override
public void onChangeButtonListener() {
tabs.setScrollPosition(1, 0, true);
viewPager.setCurrentItem(1);
}


탭 선택은 바뀌었지만 두번째 문제가 해결되지 않았다. 여전히 탭 선택 이벤트가 발생하지 않는 것이었다.


그래서 조언을 구했는데, 다른 메소드를 사용해보라는 것이었다. (구글링 하면 다 나온다고 했다...)


진작에 해볼걸... 제일 먼저 나온게 한번에 해결됐다.


@Override
public void onChangeButtonListener() {
//tabs.setScrollPosition(1, 0, true);
TabLayout.Tab tab = tabs.getTabAt(1);
tab.select();
viewPager.setCurrentItem(1);

}


ㅜㅜ