탭(Tab) : 몇 개의 버튼을 두고, 그 중 하나의 버튼을 눌러 서브 화면을 전환하는 방식.

               내비게이션(Navigation) 위젯이라고 불리기도 한다.

 

상단 탭 : 액션바에 탭 기능을 넣어 보여주는 방식으로 제공됨

하단 탭 : 별도의 위젯으로 제공됨(BottomNavigationView) (사실 이걸로 상단탭을 만들어도 됨. 탭의 위치를 옮길 수 있기 때문)

 

<상단 탭 만들기>

1. activity_main을 아래와 같은 형태로 만들어준다.

<activity_main.xml>

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

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="#A71E1E"
                android:elevation="1dp"
                android:theme="@style/ThemeOverlay.AppCompat.Dark">

                <TextView
                    android:id="@+id/titleText"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="타이틀"
                    android:textAppearance="@style/Base.TextAppearance.Widget.AppCompat.Toolbar.Title"
                    android:textSize="24sp"
                    android:textStyle="bold"
                    android:typeface="serif" />
            </androidx.appcompat.widget.Toolbar>

            <com.google.android.material.tabs.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/background_light"
                android:elevation="1dp"
                app:tabGravity="fill"
                app:tabMode="fixed"
                app:tabSelectedTextColor="?colorAccent"
                app:tabTextColor="@color/black"/> //////////////////// 문제 발생 1
        </com.google.android.material.appbar.AppBarLayout>

        <FrameLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
        </FrameLayout>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</RelativeLayout>

 

CoordinatorLayout : 이 안에 여러 레이아웃을 함께 넣으면 알아서 레이아웃들의 간격이나 위치가 결정된다. 전체 화면의 위치를 잡아줌.

AppBarLayout : 액션바. 이 코드에선 Toolbar(타이틀이 나오는 가장 상단의 바), TabLayout(탭)을 추가함.

Toolbar : 텍스트뷰로 제목("타이틀") 표시. tabMode=fixed, tabGravity=fill로 설정하여 [탭] 버튼들이 동일한 크기를 갖게 함.

FrameLayout : 프래그먼트가 들어갈 곳.

 

문제 발생 1. 탭에서 선택한 버튼만 글자가 보임.

원인 : 책에서 나온 코드 그대로 작성했는데, 테마는 책이랑 다른 걸 사용함.

테마별로 ?colorAccent나 ?colorPrimaryDark 등 색이 다른가봄. 암튼 책에서 선택한 ?colorPrimaryDark 컬러가 이 테마에서는 하얀색이었던 것이다.. (위 사진의 탭을 자세히 살펴보면 글자가 하얀색으로 희미하게 보이는 것을 알 수 있다.)

눌려진 버튼의 글자색 (왼) / 눌려지지 않은 버튼의 글자색 (오)

해결 : 검정색으로 지정해줌.

탭 글자가 제대로 나오지 않는 에러인 줄 알았는데... 어이가 없군

 

문제 발생 2. 프래그먼트가 출력되지 않음

기본화면에서부터 프래그먼트가 나타나지 않았다.

 

원인 : AppbarLayout에서 layout_height = wrap_content가 아닌 match_parent로 작성했다.

해결 : wrap_content로 수정했다.

 

청사진을 보면 frameLayout(container)이 잘 배치된 것을 볼 수 있다. 

xml 파일에서 문제가 발생한 경우엔 코드뿐만 아니라 청사진도 한번 살펴보자.

 

2. 프래그먼트 만들기 (눌러진 탭에 따라 변경되는 프래그먼트)

<fragment_1.xml>

 

<Fragment1.java>

package org.techtown.tab;

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_1, container, false);
    }
}

 

프래그먼트의 형태는 매우 단순하다. 복사-붙여넣기로 Fragment2, Fragment3의 xml과 java 파일을 만들자.

프래그먼트의 xml에서 각각의 배경색만 바꾸어준다.(구분 가능하도록)

 

3. <MainActivity.java>

package org.techtown.tab;

public class MainActivity extends AppCompatActivity {

    Toolbar toolbar;
    Fragment1 fragment1;
    Fragment2 fragment2;
    Fragment3 fragment3;

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

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

        ActionBar actionBar = getSupportActionBar();
        actionBar.setDisplayShowTitleEnabled(false);
        // setDisplayShow Custom Enabled 메서드로 잘못 사용했음..
        // 위 메서드를 사용하면 프로젝트명이 보이지 않는다.

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

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

        TabLayout tabs = findViewById(R.id.tabs);
        tabs.addTab(tabs.newTab().setText("통화 기록"));
        tabs.addTab(tabs.newTab().setText("스팸 기록"));
        tabs.addTab(tabs.newTab().setText("연락처"));

        tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                int position = tab.getPosition();
                Log.d("MainActibity", "선택된 탭 : " + position);

                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) {
            }
        });
    }
}

 

3 - (1) Toolbar 객체를 액션바로 설정하기 (setSupportActionBar)

Toolbar toolbar;

처음에 Toolbar 객체를 참조할 때 빨간색 표시가 뜬다. Toolbar 클래스가 여러 개이기 때문에 import를 자동으로 진행할 수 없어서 발생하는 문제이다.

아래와 같이 Alt+Enter를 눌러서 androidx로 시작되는 패키지 안의 Toolbar를 import 해주도록 한다.

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

setSupportActionBar는 액션바가 없을 경우에만 동작한다. 즉, 액션바가 없는 테마를 선택해야한다.

res/values/themes 안의 themes.xml에서 테마를 대충 뒤에 NoActionBar가 포함된 걸로 설정해준다.

 

3 - (2) 액션바에 프로젝트명 안 보이게 하기

ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayShowTitleEnabled(false);

setDisplayShowTitleEnabled()에 false를 넣어주면 액션바의 Title이 보이지 않는다.

(실수한 것 : 여기서 대충 자동입력해주는 거로 치다가.. 나중에 보니 Title이 아닌 Custom을 가려주는 메서드를 사용하고 있었다는 것을 깨달았다.)

 

3 - (3) 기본 화면 설정

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

fragment1이 기본 화면으로 나타나게 설정한다.

 

3 - (4) 탭 만들기 (tabs.addTab)

TabLayout tabs = findViewById(R.id.tabs);
tabs.addTab(tabs.newTab().setText("통화 기록"));
tabs.addTab(tabs.newTab().setText("스팸 기록"));
tabs.addTab(tabs.newTab().setText("연락처"));

 

3 - (5) 선택된 탭에 따라 보여지는 프래그먼트 바꾸기

탭 리스너를 설정.

  • onTabSelected : tab의 상태가 선택 상태로 변경.
  • onTabUnselected : tab의 상태가 선택되지 않음으로 변경.
  • onTabResected : 이미 선택된 상태의 tab이 사용자에 의해 다시 선택됨.
 

안드로이드 탭 만들기. [TabLayout] (Android TabLayout)

1. 안드로이드 탭. TabHost, TabWidget, TabSpec [안드로이드 탭 기본 사용법]에서, 탭호스트(TabHost)와 탭위젯(TabWidget), 그리고 프레임레이아웃(FrameLayout)을 사용하여 안드로이드 탭을 만드는 방법에 대해

recipes4dev.tistory.com

 

이 중 onTabSelected에 코드를 작성한다.

tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
        @Override
        public void onTabSelected(TabLayout.Tab tab) {
            int position = tab.getPosition();
            Log.d("MainActibity", "선택된 탭 : " + position);

            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) {}
});

 

getPosition으로 어느 탭이 눌렸는지 그 값을 가져온다. (왼쪽 탭부터 순서대로 0, 1, 2, ...)

 

4. 실행 결과

 

누른 탭에 따라 아래쪽 화면(프래그먼트)이 변경되는 것을 확인할 수 있었다.

 

<하단 탭 만들기>

1. 새 프로젝트를 만든 뒤, res 안에 menu 폴더를 만든다.

menu 폴더 안에 menu_bottom이라는 xml 파일을 추가한다.

<menu_bottom.xml>

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/tab1"
        app:showAsAction="ifRoom"
        android:enabled="true"
        android:icon="@android:drawable/ic_dialog_email"
        android:title="이메일"/>

    <item
        android:id="@+id/tab2"
        app:showAsAction="ifRoom"
        android:enabled="true"
        android:icon="@android:drawable/ic_dialog_info"
        android:title="정보"/>

    <item
        android:id="@+id/tab3"
        app:showAsAction="ifRoom"
        android:enabled="true"
        android:icon="@android:drawable/ic_dialog_map"
        android:title="위치"/>

</menu>

 

xmlns:app을 추가한다.

<item> 태그 3개를 추가하여 탭 3개의 id, title, icon, showAsAction 등을 설정해준다.

 

2. activity_main을 작성한다.

대충 아래와 같이 디자인뷰로 만든 뒤 코드뷰에서 수정하면 편하다.

 

<activity_main.xml>

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

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

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#A71E1E"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:itemIconTint="@color/black"
        app:itemTextColor="@color/white"
        app:menu="@menu/menu_bottom" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

app:layout_behavior가 궁금하긴 한데 검색해보니 좀 내용이 깊은 것 같다. 나중에 알아보자

 

bottom_navigation

  • itemIconTint : 아이콘 색상
  • itemTextColor : 텍스트 색상

3. 상단 탭 만들기 프로젝트에서 만든 fragment1,2,3 파일을 복사하여 하단 탭 만들기 프로젝트에 붙여넣기한다.

 

4. <MainActivity.java>

package org.techtown.chapter5_4_2;

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);

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

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

        BottomNavigationView bottomNavigation = findViewById(R.id.bottom_navigation);
        bottomNavigation.setOnNavigationItemSelectedListener(
                new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                int itemId = item.getItemId();
                if (itemId == R.id.tab1) {
                    getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment1).commit();
                    Toast.makeText(getApplicationContext(), "첫번째 탭 선택 됨", Toast.LENGTH_LONG).show();
                    return true;
                } else if (itemId == R.id.tab2) {
                    getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment2).commit();
                    Toast.makeText(getApplicationContext(), "두번째 탭 선택 됨", Toast.LENGTH_LONG).show();
                    return true;
                } else if (itemId == R.id.tab3) {
                    getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment3).commit();
                    Toast.makeText(getApplicationContext(), "세번째 탭 선택 됨", Toast.LENGTH_LONG).show();
                    return true;
                }
                return false;
            }
        });
    }
}

 

주의 1. onNavigationItemSelected이다. 자동입력 시 Reselected로 될 수 있으니.. 주의하기

 

주의 2. switch case 문에서 Constraint expression required 에러가 발생한다.

(Android Gradle 플러그인 8.0.0부터 최적화된 빌드 속도를 위해 리소스(ex. R.id)가 더 이상 선언되지 않기에 발생하는 오류라고 한다.)

해결하는 방법이 여러개가 있는데, 나는 if문으로 바꾸는 방식을 택했다.

switch문에 커서를 올린 뒤 Alt+Enter로 switch->if로 간단하게 바꿀 수 있다.

 

 

[Android/Error] Constant expression required

갑자기 Switch ~ Case 문에서 ‘Constant expression required’ 오류가 발생하였다. 기본적으로 Android Gradle 플러그인 8.0.0부터 최적화된 빌드 속도를 위해 리소스(예 : 상수 표현식) R.id. ...가 더 이상 선언

anovice-dp.tistory.com

 

5. 실행 결과

 

리스너만 나오면 코드가 너무 길어지니까 일단 복잡해보인다..