2023.02.21 - 안드로이드 앱개발자 과정

CHA·2023년 2월 24일
0

Android



Toolbar

액션바를 대체하기 위한 View 입니다. 앞선 예제들에서도 경험해봤듯이,액션바는 액티비티가 만들어내는 영역이기 때문에 액션바는 activity_main.xml 으로 제어할 수 없습니다. 그렇다보니 액션바에 패딩을 준다거나 등의 커스텀이 불가능했으며 디자인적으로 훌륭하지도 않습니다. 만일 건드린다고 해도, 테마를 건드려서 제어해야하기 때문에 여간 귀찮은게 아니었습니다. 그래서 기존의 액션바 대신 액션바의 기능을 가지고 있는 ToolBar 라는 view를 이용하기 시작했습니다. 즉, ToolBar 는 view 의 일종이기 때문에 우리가 직접 activity_main.xml 에서 관리할 수 있게되었습니다.


ToolBar 만들기

액션바 없애기

먼저 툴바를 만들기 위해서는 액션바를 없애주어야 합니다.액션바는 자동으로 지정되어있기 때문에 없애주는 작업부터 합시다.

themes.xml 파일을 열고 <style name="Theme.Ex33_Toolbar" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> 에서 DarkActionBar 를 NoActionBar 로 고쳐주기만 하면 액션바는 사라집니다.

xml 로 툴바 생성하기

xml 에서 다음과 같이 작성해주면 툴바 하나를 생성할 수 있습니다.

<androidx.appcompat.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="56dp"
    android:background="@color/purple_500"
    app:titleTextColor="@color/white"/>

단, xml 에서 작성한것 만으로 툴바를 액션바처럼 사용할 수는 없기때문에 자바코드를 이용하여 툴바를 액션바로 설정해주는 작업이 필요합니다.

java 로 액션바 설정

onCreate() 메서드안에 다음 2줄의 코드를 작성합시다.

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

xml 로 작성한 툴바 객체의 id 값을 받아온 후, setSupportActionBar() 로 툴바를 액션바로 지정해줍니다. 우리는 androidx의 툴바를 생성했으므로, Support 가 붙은 메서드를 호출했습니다.


ToolBar 옵션메뉴 만들기

툴바를 액션바로 지정해주었으므로 옵션메뉴를 만들 수 있습니다. 옵션메뉴를 한번 만들어봅시다.

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:id="@+id/menu_aa"
        android:title="aa"/>
    <item android:id="@+id/menu_bb"
        android:title="bb"/>
    <item android:id="@+id/menu_search"
        android:title="search"
        android:icon="@drawable/ic_action_search"
        app:showAsAction="ifRoom"/>
</menu>
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater menuInflater = getMenuInflater();
    menuInflater.inflate(R.menu.option,menu);
    return super.onCreateOptionsMenu(menu);
}

앞서 배웠던 옵션메뉴 만드는 방법과 동일하게 옵션메뉴를 만들어줄 수 있습니다. 이렇게 옵션메뉴를 만들 수 있다는것이 툴바가 액션바의 역할을 한다는것을 알 수 있게 해줍니다.

더보기 메뉴 아이콘의 색상도 바꿔줄 수 있습니다. themes.xml 파일에서 다음 코드 한줄을 써넣으면 됩니다.
<item name="colorControlNormal">@color/teal_700</item>


ToolBar 는 View 이다!

ToolBar 는 액션바의 역할을 수행하지만 그와 동시에 View 이기 때문에 액션바와는 달리 속성들을 이용할 수 있습니다.

------------- bg_actionbar.xml


<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <stroke android:width="2dp"
        android:color="#AC91DD"
        />
    <solid android:color="@color/white"/>
    <corners android:radius="10dp"/>

</shape>
--------------- activity_main.xml
<androidx.appcompat.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="16dp"
    android:background="@drawable/bg_actionbar">

</androidx.appcompat.widget.Toolbar>

툴바의 배경을 드로어블을 이용하여 지정해 보았습니다. 이외에도 여러가지 속성들을 활용할 수 있다는 점만 알아둡시다.

액션바 제목글씨 없애기

앞서 툴바는 액션바를 대체하는 뷰라고 이야기 했습니다. 다만, 액션바의 모든 기능을 가지고 있지는 않습니다. 그래서 액션바의 제목글씨를 지우기 위해서는 액션바에게 명령을 내려줘야 합니다. 코드를 봅시다.

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

ActionBar actionBar = getSupportActionBar(); 을 이용하면 툴바를 액션바의 참조변수로 넣어줄 수 있습니다.
actionBar.setDisplayShowTitleEnabled(false); 을 이용하여 제목줄을 지워줄 수 있습니다.


AppBarLayout


AppBarLayout 사용하기

AppBarLayout 은 제목줄을 좀 더 다양한 구조로 사용하기 위해 사용하는 레이아웃입니다. 보통 ToolBar 와 함께 사용됩니다. 그럼 아래 코드 예제를 통해 앱바 레이아웃을 어떻게 사용하는지 한번 알아봅시다.

<LinearLayout ... 중략 >

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:titleTextColor="@color/white"
            app:logo="@drawable/ic_action_logo"
            app:titleMarginStart="30dp"
            app:subtitle="This is subTitle"
            app:subtitleTextColor="#FFFFFF"/>
      
        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tab_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/white"
            app:tabTextColor="@color/black"
            app:tabSelectedTextColor="#A8A8A8"
            app:tabIndicatorColor="@color/white"
            app:tabIndicatorHeight="4dp"
            app:tabIndicatorFullWidth="false"
            app:tabMode="scrollable"/>
      
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

앱바 레이아웃 안에 툴바 하나와 탭 레이아웃 하나를 넣은 모습입니다. 툴바 하나만을 제목줄로 사용했을 때와 차이가 나는 모습을 볼 수 있습니다. 가볍게 탭 레이아웃의 속성 몇개만 알아보고 갑시다.

  • app:tabSelectedTextColor
    선택한 탭의 글씨 색을 설정할 수 있습니다. 이 속성을 이용하면, 선택된 탭과 선택되지 않은 탭의 구분을 명확하게 할 수 있습니다.
  • app:tabIndicatorColor

    탭의 표시자 색을 설정합니다. 위 그림에서 보라색 선이 탭의 표시자 입니다.

  • app:tabIndicatorHeight
    탭의 표시자의 두께를 설정할 수 있습니다.

  • app:tabIndicatorFullWidth
    탭의 표시자의 길이를 탭에 꽉 채울지의 여부를 설정할 수 있습니다.

  • app:tabMode
    탭모드는 탭이 많을 때, 스크롤의 여부를 설정합니다.

TabLayoutMediator

이번에는 각 탭 영역을 뷰페이저를 이용하여 화면구성을 하고, 각 탭의 뷰를 프래그먼트를 활용하여 화면을 설정해봅시다. 먼저, 프래그먼트의 화면과 자바 파일을 구성해봅시다.

------------ fragment_tab1.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TAB3 으로 이동"
        android:textColor="@color/white"
        android:layout_centerInParent="true"
        android:textSize="25sp"/>

</RelativeLayout>



------------ fragment_tab2.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TAB2"
        android:textColor="@color/black"
        android:layout_centerInParent="true"
        android:textSize="40sp"/>

</RelativeLayout>


------------ fragment_tab3.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TAB3"
        android:textColor="@color/black"
        android:layout_centerInParent="true"
        android:textSize="40sp"/>

</RelativeLayout>




----------------- Tab1Fragment.java
public class Tab1Fragment extends Fragment {

    Button btn;

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

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        btn = view.findViewById(R.id.btn);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                MainActivity mainActivity = (MainActivity) getActivity();
                TabLayout.Tab tab = mainActivity.tabLayout.getTabAt(2);
                mainActivity.tabLayout.selectTab(tab);
            }
        });
    }
}

위 코드는 onCreateView() 메서드를 이용하여 프래그먼트를 생성해주고, 뷰가 생성된 뒤 버튼이벤트를 구현했습니다. 버튼을 누르면 Tab3 로 이동하게 됩니다. 주의할 점은 탭 위치의 변경을 하기 위해서는 탭에게 요청을 해주어야 합니다. 그리고 그 탭이 있는곳은 MainActivity 이므로 MainActivity mainActivity = (MainActivity) getActivity() 를 이용하여 프래그먼트에서 메인액티비티의 객체를 소환해주어야 합니다.

그리고 MainActivity 에는 TabLayout 의 객체인 tabLayout 이 있습니다. 이 객체의 getTabAt() 메서드를 이용하여 탭의 위치 정보를 불러와 Tab 객체 tab 에 저장시켜 줍니다. 그리고 mainActivity.tabLayout.selectTab(tab); 를 이용하여 탭을 선택해주면 탭의 위치 변경이 마무리 됩니다. 프래그먼트에서도 메인액티비티에 접근할 수 있다는 점과 탭의 위치변경이 코드로 가능하다는 점을 기억합시다.




----------------- Tab2Fragment.java
public class Tab2Fragment extends Fragment {

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




----------------- Tab3Fragment.java
public class Tab3Fragment extends Fragment {

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

Tab2 와 Tab3 의 프래그먼트를 생성하는 코드 입니다.





------------------ MyAdapter.java
public class MyAdapter extends FragmentStateAdapter {
    Fragment[] fragments = new Fragment[3];

    public MyAdapter(@NonNull FragmentActivity fragmentActivity) {
        super(fragmentActivity);
        fragments[0] = new Tab1Fragment();
        fragments[1] = new Tab2Fragment();
        fragments[2] = new Tab3Fragment();
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return fragments[position];
    }

    @Override
    public int getItemCount() {
        return fragments.length;
    }
}



---------------- MainActivity.java
public class MainActivity extends AppCompatActivity {

    ViewPager2 pager;
    MyAdapter adapter;
    TabLayout tabLayout;

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

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

        pager = findViewById(R.id.pager);
        adapter = new MyAdapter(this);
        pager.setAdapter(adapter);


        tabLayout = findViewById(R.id.tab_layout);
        String[] titles = new String[] {"tab1","tab2","tab3"};
        TabLayoutMediator mediator = new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy() {
            @Override
            public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
                tab.setText(titles[position]);
            }
        });
        mediator.attach();
    }
}

MyAdapter 에서는 프래그먼트를 정보로 받아, 뷰페이저의 요청에 맞게 프래그먼트를 생성해줍니다. 메인 액티비티에서는 툴바 하나를 만들어 주었으며, 어댑터를 붙여주었습니다. 우리가 볼 부분은 탭 레이아웃의 중재자 객체입니다.

우리가 만든 뷰페이저는 화면을 스르륵 넘길 수 있는 화면구성입니다. 하지만 그에 맞게 탭 레이아웃 또한 위치 변경이 가능해야 하는데, 그 역할을 해주는 객체가 TabLayoutMediator 객체 입니다. 이 중재자 객체는 뷰페이져와 탭 레이아웃을 연결해주는 역할을 하게 됩니다.

TabLayoutMediator 의 객체 하나를 생성해주고, 생성자의 파라미터 중 첫번째 파라미터로는 TabLayout 의 객체를, 두번째 파라미터로는 뷰페이져의 객체를 전달합니다. 그리고 마지막 파라미터로 탭 레이아웃의 설정값을 설정하는 TabConfigurationStratgy 객체를 전달해줍니다. 전달해줄때 익명클래스를 이용하여 추상메서드를 하나 정의해야 합니다. 이 메서드 내에서 각 탭의 속성들을 제어할 수 있습니다.

그리고 마지막으로 TabLayoutMediator 객체의 메서드인 attach() 를 이용하여 탭 레이아웃과 뷰페이져의 연결을 마무리짓습니다.


많은 앱들에서 삼단바 형태의 버튼을 볼 수 있습니다. 보통 햄버거 아이콘이라고 부르는데, 이 버튼을 누르게 되면 숨겨져 있던 메뉴 화면이 스륵 하고 나타납니다. 아니면 앱의 가장자리를 드래그하면 나오게 되는 화면이죠. 이러한 화면구성을 NavigationView 라고 합니다. 이러한 네비게이션뷰를 사용하기 위해서는 DrawerLayout 으로 감싸주어야 합니다. 이렇게 감싸진 뷰들에서는 네비게이션 뷰들을 사용할 수 있습니다. 그리고 네비게이션뷰의 내부는 헤더부분과 콘텐츠부분으로 나뉘기 때문에 우리는 헤더부분의 xml 파일과 콘텐츠 부분의 xml 을 따로 만든 뒤, 네비게이션뷰에 포함시켜주어야 합니다.

먼저, 토글버튼이나 리스너를 하기 이전에 네비게이션 뷰를 만들어 봅시다.


header.xml 만들기

네비게이션 뷰의 헤더부분에 들어갈 모양을 설계해봅시다.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="160dp"
    android:padding="16dp"
    android:background="@color/purple_500">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@mipmap/ic_launcher"/>
    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="android"
        android:textSize="20sp"
        android:textColor="@color/white"
        android:layout_below="@id/iv"
        android:textStyle="bold"
        android:layout_marginLeft="8dp"/>
    <TextView
        android:id="@+id/tv_email"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="android@google.com"
        android:textColor="@color/white"
        android:layout_below="@id/tv_name"
        android:layout_marginLeft="8dp"/>

</RelativeLayout>

네비게이션 뷰의 컨텐츠 부분에 들어갈 모양을 설계해봅시다.

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:id="@+id/menu_gallery"
        android:title="Gallery"
        android:icon="@drawable/ic_action_photo"/>

    <item android:id="@+id/info"
        android:title="info"/>
    <item android:title="Your Account">
        <menu>
            <item android:id="@+id/menu_aa" android:title="aa"/>
            <item android:id="@+id/menu_bb" android:title="bb"/>
        </menu>
    </item>
</menu>
<com.google.android.material.navigation.NavigationView
    android:id="@+id/nav"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    app:headerLayout="@layout/navi_header"
    app:menu="@menu/navi_menu"/>


app:headerLayout="@layout/navi_header"app:menu="@menu/navi_menu" 을 이용하여 헤더와 컨텐츠 부분의 모양을 잡아 줍시다.


토글 버튼 만들기

우리는 화면을 드래그하여 네비게이션뷰를 쉽게 불러오고 숨길 수 있지만, 사용자에게 좀 더 직관적으로 메뉴를 표시하고 이를 조작할 수 있도록 액션바 토글을 구현할것을 권장합니다.

툴바 만들기

먼저 토글 버튼이 들어갈 툴바 하나를 생성합시다.

------------ activity_main.xml
<com.google.android.material.appbar.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:titleTextColor="@color/white"/>

</com.google.android.material.appbar.AppBarLayout>

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

툴바는 액션바의 모든 기능을 사용할 수 없기 때문에 액션바로 지정해주는 코드 입니다.

drawerToggle 버튼 객체 생성하기

우리가 원하는 햄버거 아이콘을 만들기 위해 먼저, 버튼의 객체부터 만들어 줍시다. 그러기 위해 DrawerLayout 과 NavigationView 의 객체부터 만들어줍니다.

        drawerLayout = findViewById(R.id.drawer_layout);
        navigationView = findViewById(R.id.nav);

그리고 토글 버튼 객체를 생성해줍시다.

public class MainActivity extends AppCompatActivity {

    DrawerLayout drawerLayout;
    NavigationView navigationView;
    ActionBarDrawerToggle drawerToggle;

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

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

        drawerLayout = findViewById(R.id.drawer_layout);
        navigationView = findViewById(R.id.nav);

        drawerToggle = new ActionBarDrawerToggle(this,drawerLayout,toolbar,R.string.open,R.string.close);
    }
}

버튼 보이게 하기

        ActionBar actionBar = getSupportActionBar();
        actionBar.setDisplayHomeAsUpEnabled(true);

위 코드를 이용하여 만들어준 토글 버튼을 보이게끔 해줍니다.

햄버거 아이콘

drawerToggle.syncState(); 을 이용하면 햄버거 아이콘으로 만들어 줄 수 있습니다.

햄버거 아이콘, 화살표 아이콘 자동변환

drawerLayout.addDrawerListener(drawerToggle); 을 이용하면, 열리기 전에는 햄버거 아이콘, 열린 후에는 화살표 아이콘으로 자동적으로 변하는 모습을 볼 수 있습니다.


아이템 선택 리스너

  • 네비게이션뷰의 아이템선택리스너 입니다. 각 아이템들의 ID 값과 xml 상의 id 값을 비교하여 if 문으로 처리해주면 됩니다.
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {

        if( item.getItemId() == R.id.menu_gallery ){
            Toast.makeText(MainActivity.this, "Gallery", Toast.LENGTH_SHORT).show();

        }else if( item.getItemId() == R.id.menu_send ){
            Toast.makeText(MainActivity.this, "Send", Toast.LENGTH_SHORT).show();

        }else if( item.getItemId() == R.id.menu_aa ){
            Toast.makeText(MainActivity.this, "aa", Toast.LENGTH_SHORT).show();

        }else if( item.getItemId() == R.id.menu_bb ){
            Toast.makeText(MainActivity.this, "bb", Toast.LENGTH_SHORT).show();

        }

        drawerLayout.closeDrawer(navigationView);
        return false;
    }
});

profile
Developer

0개의 댓글