74일차(4)/Android App(28) : Bottom Navigation 예제
- 새 모듈 생성
Step11bottomnavi
- Bottom Navigation 이라는 Activity 선택 : 하단에 네비메뉴가 있다!
- 홈 / 대시보드 / 알림이 기본 레이아웃이다. 이것도 모두 프래그먼트이다.
- 이것을 기본 틀로 해서 변경해서 쓰라는 뜻이다.
- 이렇게 프래그먼트가 만들어져 있다. 하나하나의 프래그먼트가 화면을 제어하고 있다.
- 이렇게 Fragment별로 ui에 패키지가 따로 있다.
- 모듈의 구조 보기. Bottom Navigation으로 이동할 수 있는 3개의 탭별로
Fragment와 ViewModel이 각각의 클래스로 만들어져 있다.
- HomeFragment 에서 사용하는 데이터는 HomeViewModel 에서 가지고있다!
- BottomNavigationView 객체를 가져와서 home, dashboard, notification의 id를 얻어와서
NavController에 가져온 참조값 넣어주기
- acitivity_main.xml을 보면, 이 nav_view 가 프래그먼트를 하나하나 바꿔주고 있다.
button_nav_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_dashboard"
android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_dashboard" />
<item
android:id="@+id/navigation_notifications"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_notifications" />
</menu>
- menu 폴더안에 들어있는 xml 파일!
- 옵션 만들때만 사용하는건 아니고 뭔가 다른것들을 만들 때 사용할 수도 있다.
- navbar의 아이콘, 타이틀 정보가 들어있다.
- 아이콘 : drawable 폴더에 들어있다.
- 제목 : values 폴더에 들어있다.
- menu 에서 이것들을 모두 가져다 쓸 수 있다.
- 이미 존재하는 것을 참조할 때는 @ 로 시작하고,
없는 것을 새로 만들 때에는 @+ 으로 시작한다.(새로 등록하는 개념)
- drawable 폴더에는 이런 벡터 그래픽이 들어있다.
- 안드로이드에서 이런 벡터 그래픽을 사용하고 싶으면, drawable 안에 벡터 그래픽이 들어있는 xml문서를 넣어놓고
이미지처럼 가져다 쓰면 된다!
- 웹용, 안드로이드용 구분해서 다운받을 수 있다. 그 문서를 drawable에 넣어놓고 사용하면 된다.
- 직접 선택해서 만들고 싶다면 res/drawable => 우클릭 => new Vector Asset 에서 만들수도있다.
- 색상, 투명도, 개별 아이콘에 대한 outline 설정 등등도 따로 할 수 있다.
- 하단 아이콘을 이렇게 바꿔볼 수 있다. xml문서에서 다른 아이콘을 참조하도록 바꾸기만 하면 된다!
MainActivity
package com.example.step11bottomnavi;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.example.step11bottomnavi.databinding.ActivityMainBinding;
import com.google.android.material.bottomnavigation.BottomNavigationView;
/*
icon으로 사용할 vector xml 문서 다운받는 곳
https://fonts.google.com/icons?icon.platform=android
https://materialdesignicons.com/
- 직접 선택해서 만들고 싶다면
res/drawable => 우클릭 => new Vector Asset
*/
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//화면 레이아웃 구성하기
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//하단 네비바의 참조값 얻어오기
BottomNavigationView navView = findViewById(R.id.nav_view);
//하단 메뉴바 설정 객체의 참조값 얻어오기
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
.build();
//네비게이션 컨트롤러와 하단 메뉴바가 동작하기 위한 초기화 작업하기
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);
}
}
- binding 객체로 화면 꽉 채우도록 구성하기
acitivity_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"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />
<fragment
android:id="@+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
- 하단 네비에 메뉴로 쓸 참조값들을 전달함 (xml에서)
- 생성자를 보면 가변 인자이다. 인자로 받는 갯수의 제한이 없다는 뜻이다.
- 인자를 하나 전달해도 되고, 여러개 전달해도 상관없다는 뜻!
- 인자의 개수, 이름, 어떤 동작을 할 것인지 정도만 변경해서 사용하면 된다.
- 주로 수정하는건 fragment가 될 것이다! fragment 의 동작과 레이아웃을 정의하면 된다.
HomeFragment
package com.example.step11bottomnavi.ui.home;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import com.example.step11bottomnavi.databinding.FragmentHomeBinding;
public class HomeFragment extends Fragment {
private FragmentHomeBinding binding;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
//HomeViewModel 을 사용할 준비
//new ViewModelProvider( ViewModelStoreOwner type )
//ViewModelStoreOwner interface type => 프래그먼트 or 액티비티
HomeViewModel homeViewModel =
new ViewModelProvider(this).get(HomeViewModel.class);
binding = FragmentHomeBinding.inflate(inflater, container, false);
View root = binding.getRoot();
final TextView textView = binding.textHome;
homeViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
@Override
public void onChanged(String s) {
textView.setText(s);
}
});
textView.setOnClickListener(v -> {
homeViewModel.setText("Clicked!");
});
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
HomeViewModel
package com.example.step11bottomnavi.ui.home;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class HomeViewModel extends ViewModel {
private final MutableLiveData<String> mText;
public HomeViewModel() {
mText = new MutableLiveData<>();
mText.setValue("This is home fragment");
}
//라이브 데이터를 수정하는 메소드
public void setText(String text){
mText.setValue(text);
}
public LiveData<String> getText() {
return mText;
}
}
- 폰에서 네비요소가 밑에 있는 이유는? 손으로 사용하니까. 손으로 누르기 편하라고!
- 네비바를 손으로 누르면 컨텐츠가 바뀌는 구조이다.
- TextView를 클릭하면 바꿀 수 있도록 LiveData를 바꾸면 된다.
- HomeFragment에서는 ViewModel을 사용할 준비를 하는 것
→ ViewModelProvider에 owner를 전달해줘야 한다.
- ViewModelStoreOwner 를 받아야 하는데, fragment가 ViewModelStoreOwner 인터페이스를 구현했기 때문에 this로 받을 수 있다.
- ViewModelStoreOwner 인터페이스 타입으로 주로 사용되는 것은 fragment 또는 Activity이다.
- activity, fragment 는 활성화될 수도 있고 비활성화될 수도 있다.
- 그런 lifecycle을 고려해서 UI를 업데이트 해주겠다는 것. 이것이 ViewModel 을 쓰는 이유!
- Activity도 ViewModelStoreOwner 인터페이스 타입이 될 수 있다.
- AppCompatActivity가 FragmentActivity 상속
→ FragmentActivity가 ComponentActivity 상속
→ ComponentActivity가 ViewModelStoreOwner 상속
- 그래서 Activity에서도 ViewModel을 this 로 전달해서 사용할 수 있다.
- ViewModelProvider 객체를 생성해서 ViewModel 클래스를 전달하면 사용할 준비가 된다.
textView : : setText
- 위 표현식의 줄인 표현!
- setText를 호출하면서 바꾸겠다는 구조이다.
- HomeViewModel 메소드 추가
- 이 메소드를 호출하면서 새로운 문자열을 전달하면 수정될 것
- ViewModel에 변화를 가하면 자동으로 실행 순서가 들어온다.
- ViewModel이 가지고 있는 라이브 데이터 내용이 바뀌면 onchanged가 자동으로 호출된다.
- 이것을 람다식으로 바꿔보겠다!
- 이러한 형태가 된다. 이 안에 setText 메소드 넣어주기
DashboardFragment 수정
- 이렇게 한줄로 쓸 수도 있다.
//1번
dashboardViewModel.getText().observe(getViewLifecycleOwner(), s->{
textView.setText(s)
});
//2번
dashboardViewModel.getText().observe(getViewLifecycleOwner(), s-> textView.setText(s) );
//3번
dashboardViewModel.getText().observe(getViewLifecycleOwner(), textView::setText);
- 위 세가지는 모두 같다.
- 람다식에서 매개변수를 중복으로 쓰는 불편함을 없애는 이중콜론 :: 연산자
- textView::setText : 인자로 전달받은 값을 textView 객체의 setText 메소드를 호출하면서 전달을 해라 라는 의미
DashBoardFragment
package com.example.step11bottomnavi.ui.dashboard;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.example.step11bottomnavi.databinding.FragmentDashboardBinding;
public class DashboardFragment extends Fragment {
private FragmentDashboardBinding binding;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
DashboardViewModel dashboardViewModel =
new ViewModelProvider(this).get(DashboardViewModel.class);
binding = FragmentDashboardBinding.inflate(inflater, container, false);
View root = binding.getRoot();
final TextView textView = binding.textDashboard;
//dashboardViewModel.getText().observe(getViewLifecycleOwner(), s->{textView.setText(s)});
//람다식에서 매개변수를 중복으로 쓰는 불편함을 없애는 이중콜론 :: 연산자
//textView::setText 인자로 전달받은 값을 textView 객체의 setText 메소드를 호출하면서 전달을 해라 라는 의미
dashboardViewModel.getText().observe(getViewLifecycleOwner(), textView::setText);
textView.setOnClickListener(v -> {
dashboardViewModel.setText("Clicked!");
});
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
DashBoardViewModel
package com.example.step11bottomnavi.ui.dashboard;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class DashboardViewModel extends ViewModel {
private final MutableLiveData<String> mText;
public DashboardViewModel() {
mText = new MutableLiveData<>();
mText.setValue("This is dashboard fragment");
}
public void setText(String text){
mText.setValue(text);
}
public LiveData<String> getText() {
return mText;
}
}
NotificationFragment
package com.example.step11bottomnavi.ui.notifications;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.example.step11bottomnavi.databinding.FragmentNotificationsBinding;
public class NotificationsFragment extends Fragment {
private FragmentNotificationsBinding binding;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
NotificationsViewModel notificationsViewModel =
new ViewModelProvider(this).get(NotificationsViewModel.class);
binding = FragmentNotificationsBinding.inflate(inflater, container, false);
View root = binding.getRoot();
final TextView textView = binding.textNotifications;
notificationsViewModel.getText().observe(getViewLifecycleOwner(), textView::setText);
textView.setOnClickListener(v -> {
notificationsViewModel.setText("Clicked!");
});
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
NotificationViewModel
package com.example.step11bottomnavi.ui.notifications;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class NotificationsViewModel extends ViewModel {
private final MutableLiveData<String> mText;
public NotificationsViewModel() {
mText = new MutableLiveData<>();
mText.setValue("This is notifications fragment");
}
public void setText(String text){
mText.setValue(text);
}
public LiveData<String> getText() {
return mText;
}
}
- 다른 fragment 페이지에서도 setText메소드를 동일하게 처리해주면 된다.
- 그러면 모든 탭에서 텍스트뷰를 클릭하면 Clicked! 라는 메시지가 뜬다.
'국비교육(22-23)' 카테고리의 다른 글
75일차(1)/Android App(30) : Kotlin loop, try-catch (0) | 2023.01.25 |
---|---|
74일차(5)/Android App(29) : Broadcast Receiver (0) | 2023.01.24 |
74일차(3)/Android App(27) : ViewPager 예제 (0) | 2023.01.24 |
74일차(2)/Android App(26) : View 로 미니 슈팅게임 만들기(4) (0) | 2023.01.20 |
74일차(1)/Android App(25) : Kotlin 연산자 / apply / when (0) | 2023.01.20 |