[DSC] 여러 화면 간 전환하기

2020. 5. 24. 01:22Android

Inflation이란?

 인플레이션(inflation)이란 xml 레이아웃의 내용이 메모리에 객체화 되는 과정입니다.

이렇게 말하면 어렵게 들릴 수 있으니까 쉽게 말하자면 자바코드에서 setContentView()xml 파일이랑 연결시키는 역할을 한다했잖아요. 그래서 xml 파일이랑 연결하는 이 과정이 인플레이션이라고 생각하면 돼요.

그래서 setContentView()하기전에 xml에서 findViewById를 하려고하면 오류가 납니다.

 

그리고 화면 전체에 보여줄 xml 레이아웃이 아니라 부분 레이아웃을 소스 파일에 로딩하여 보여줄 수 있습니다.

그 역할을 하는게 LayoutInflater라는 클래스입니다.

실습 : LayoutInflater로 부분화면 만들기

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_inflater"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="inflater" />

    <LinearLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" />

</LinearLayout>

 

그리고 res/layout 폴더를 우클릭하여

 

새로운 Layout Resource File을 만들어 줍니다. xml 이름은 sub로 하겠습니다.

 

sub.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">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="부분화면" />

</LinearLayout>

sub.xml 파일이 부분화면 역할을 할 것입니다.

 

MainActivity.java

package com.example.week3;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

public class MainActivity extends AppCompatActivity {

    Button btn_inflater;
    LinearLayout container;

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

        btn_inflater = findViewById(R.id.btn_inflater);
        container = findViewById(R.id.container);
        
        btn_inflater.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                inflater.inflate(R.layout.sub, container, true);
            }
        });
    }
}

LayoutInflater가 R.layout.sub.xml을 container라는 LinearLayout에 추가해주는 코드입니다.

 

실행시켜 보겠습니다.

버튼을 누를 때마다 부분화면이 생성되는 것을 확인할 수 있습니다.

 


 

화면 전환하기

 앱에는 여러 화면으로 구성되어 있고 화면을 전환하며 실행됩니다. 화면은 액티비티로 구현합니다. , 화면을 띄우거나 닫는 과정은 액티비티를 전환하는 것과 같습니다. 따라서 액티비티에 대해서 공부하는 것은 중요합니다.

액티비티를 소스코드에서 띄울 때 startActicity() 함수를 사용하면 되는데 이 함수는 단순히 액티비티를 띄워 화면에 보이도록 하는 기능입니다. 하지만 화면을 닫고 원래의 화면으로 돌아올 때 응답을 받고 처리하는 코드가 필요합니다. 그때 사용하는 함수가 startActivityForResult()입니다.

 

실습 : 새 액티비티를 띄우고 닫으며 데이터 주고받기

 

activity_main.xml

<Button
    android:id="@+id/btn_newactivity"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="newactivity" />

 

그리고 또 res/layout 폴더에 새로운 Layout Resource File을 생성하고 이름은 activity_new로 하겠습니다.

 

activity_new.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">

    <Button
        android:id="@+id/btn_return"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="돌아가기" />

</LinearLayout>

 

 

이번엔 app/java/com.example.패키지명에 새로운 자바 클래스를 만들어보도록 합시다.

이름은 NewActivity로 하였습니다.

 

NewActivity.java

package com.example.week3;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class NewActivity extends AppCompatActivity {

    Button btn_return;

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

        btn_return = findViewById(R.id.btn_return);

        btn_return.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.putExtra("name", "rina"); //부가데이터
                setResult(RESULT_OK, intent); //응답 보내기
                finish(); //현재 액티비티 종료
            }
        });

    }
}

intent에 "name"이라는 키로 "rina"라는 데이터를 가진 Extra를 담아 응답을 보냅니다.

 

MainActivity.java

public static final int REQUEST_CODE_MENU = 101; //새 액티비티를 띄울 때 보낼 요청 코드

이 변수는 onCreate() 함수 위에 작성해주세요.

 

MainActivity.java

Button btn_newactivity = findViewById(R.id.btn_newactivity);
btn_newactivity.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    	Intent intent = new Intent(getApplicationContext(), NewActivity.class);
    	startActivityForResult(intent, REQUEST_CODE_MENU);
    }
});

이 코드는 현재 액티비티에서 NewActivity 액티비티로 화면을 전환해주는 코드입니다.

onCreate() 함수 안에 작성해주세요.

 

MainActivity.java

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode == REQUEST_CODE_MENU) {
        Toast.makeText(getApplicationContext(),
                "onActivityResult 함수 호출됨. 요청 코드 : " + resultCode,
                Toast.LENGTH_SHORT).show();
        if(resultCode == RESULT_OK) {
            String name = data.getStringExtra("name");
            Toast.makeText(getApplicationContext(),
                    "응답으로 전달된 name : " + name,
                    Toast.LENGTH_SHORT).show();
        }
    }
}

NewActivity가 종료되었을 때 결과 값을 받는 코드입니다. 만약 내가 REQUEST_CODE_MENU를 전송했다면 첫 번째 Toast가 실행되고 그 응답으로 RESULT_OK라는 응답코드를 받았다면 두 번째 Toast가 실행됩니다.

이 코드는 onCreate() 함수 바깥에 작성해주세요.

 

마지막으로 app/manifests를 더블 클릭해주시면

AndroidManifest.xml 파일이 나타납니다.

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.week3">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".NewActivity" />
    </application>

</manifest>

Activity를 추가할 때마다 위 처럼 <activity android:name=".NewActivity" />를 추가해주어야합니다.

 

실행시켜 보겠습니다.

화질이 안좋아 잘보이진 않지만 "onActivityResult 함수 호출됨. 요청 코드 : -1"과 "응답으로 전달된 name : rina"라는 토스트 메시지가 뜨는 것을 알 수 있습니다.

 


 

액티비티의 수명주기

안드로이드의 액티비티에는 수명주기가 있습니다. 

onCreate() : 액티비티 처음 만들어졌을 때 호출됨

    화면에 보이는 뷰들의 일반적인 상태를 설정하는 부분

    이 함수 다음에는 항상 onStart() 함수가 호출됨

onStart() : 액티비티 화면에 보이기 바로 전에 호출됨

    액티비티가 화면 상에 보이면 이 함수 다음에 onResume() 함수가 호출됨

    액티비티가 화면에서 가려지게 되면 이 함수 다음에 onStop() 함수가 호출됨

onResume() : 사용자와 상호작용하기 바로 전에 호출됨

onRestart() : 액티비티 중지 이후 다시 시작되기 바로 전

    이 함수 다음에는 항상 onStart()함수가 호출됨

onPause() : 또 다른 액티비티 시작하려고 할 때 호출됨

    저장되지 않은 데이터를 저장소에 저장하거나 애니메이션 중인 작업을 중지하는 등의 기능을 수행하는 함수임

    이 함수가 리턴하기 전에는 다음 액티비티가 시작될 수 없으므로 이 작업은 매우 빨리 수행된 후 리턴되어야 함

onStop() : 액티비티가 사용자에게 더 이상 보이지 않을 때 호출됨

    액티비티가 소멸되거나 또 다른 액티비티가 화면을 가릴 때 호출됨

onDestroy() : 액티비티가 소멸되어 없어지기 전에 호출됨

    이 함수는 액티비티가 받는 마지막 호출이 됨

 

실습 : 디버깅 로그로 수명주기 확인하기

MainActivity.java에서 onCreate() 함수 밖을 클릭하고 Ctrl + O를 눌러줍니다.

그러면 Override 또는 Implement 함수를 찾을 수 있습니다.

여기서 onStart(), onPause(), onResume(), onRestart(), onDestroy() 함수를 찾아서 MainActivity에 추가해줍니다.

@Override
protected void onStart() {
	super.onStart();
	Log.d("tag", "onStart");
}

@Override
protected void onPause() {
	super.onPause();
	Log.d("tag", "onPause");
}

@Override
protected void onResume() {
	super.onResume();
	Log.d("tag", "onResume");
}

@Override
protected void onRestart() {
	super.onRestart();
	Log.d("tag", "onRestart");
}

@Override
protected void onDestroy() {
	super.onDestroy();
	Log.d("tag", "onDestroy");
}

 

이렇게 작성하고 앱을 실행시키면

log 창에 onCreate()함수가 실행된 후 바로 onStart와 onResume이 실행된 것을 확인할 수 있고

다른 액티비티가 실행됐을 땐 onPause

다시 메인액티비티로 돌아왔을 땐 onRestart, onStart, onResume

그리고 앱을 종료했을 땐 onPause, onDestroy 함수가 실행된 것을 알 수 있습니다.

 


 

Fragment

프래그먼트(Fragment)는 하나의 화면을 여러 부분으로 나눠서 보여주거나 각각의 부분 화면 단위로 바꿔서 보여주고 싶을 때 사용합니다.

 

그리고 BottomNavigationView는 하단탭을 만들어줄 수 있는 기능입니다. 카카오톡에도 하단탭이 사용된 것을 알 수 있습니다.

 

실습 : BottomNavigationView 사용하기

 

우선 activity_main.xml에 버튼 하나를 추가해줍니다.

 

activity_main.xml

<Button
    android:id="@+id/btn_fragment"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="fragment" />

 

그리고 app/java/com.example.week3에 FragmentActivity.java 파일을 새로 만들겠습니다.

 

FragmentActivity.java

package com.example.week3;

import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class FragmentActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
    }
}

 

이 액티비티와 연결할 레이아웃 xml파일도 만들어주겠습니다.

우선 design 화면에서

Palette의 Containers안에 BottomNavigationView를 다운로드 받아줍니다.

 

OK 눌러주시면 됩니다.

그러고나서 다음과 같이 xml 파일을 작성하겠습니다.

 

activity_fragment.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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/mainFrame"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#ffffff"/>

</LinearLayout>

 

그리고 FragmentActivity.java의 super.onCreate(savedInstanceState); 코드 밑에 다음과 같이 작성해줍니다.

 

FragmentActivity.java

setContentView(R.layout.activity_fragment);

FragmentActivity와 activity_fragment.xml 파일을 연결한 것입니다.

 

이번엔 하단탭에 들어갈 메뉴를 만들어주겠습니다.

이번엔 res 폴더를 우클릭해서

Android Resource Directory를 생성해줍니다.

 

여기서 Resource type을 꼭 menu로 설정해주세요!

 

그리고 menu 폴더를 우클릭해서 Menu Resource File을 만들어줍니다.

이름은 bottom_menu로 하겠습니다.

 

 

이번에는 drawable 폴더를 우클릭해서 Vector Asset을 눌러줍니다.

 

여기서 Clip Art를 더블 클릭해주면

 

다음과 같이 여러가지의 아이콘들을 볼 수 있습니다. 여기서 아무거나 세 개를 drawable 폴더에 추가해주세요.

총 3개를 추가해주시면 됩니다.

 

그리고 아이템을 하단탭 메뉴에 추가시켜주겠습니다.

 

bottom_menu.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"
        android:icon="@drawable/ic_android_black_24dp"
        android:title="tab1" />
    <item
        android:id="@+id/tab2"
        android:icon="@drawable/ic_access_time_black_24dp"
        android:title="tab2" />
    <item
        android:id="@+id/tab3"
        android:icon="@drawable/ic_add_circle_outline_black_24dp"
        android:title="tab3" />

</menu>

android:icon 부분은 본인이 추가하신 아이콘의 경로를 입력해주세요.

 

그리고 BottomNavigationView 태그에 다음 속성을 추가해주세요.

 

activity_fragment.xml

app:menu="@menu/bottom_menu"

 

 

이번엔 부분화면인 Fragment를 만들어보도록 하겠습니다.

Fragment(Blank)를 생성해줍니다.

 

이름은 Frag1으로 만들고나면 fragment_frag1.xml 파일이 layout 폴더에 자동으로 생성됩니다.

그러면 생성된 fragment_frag1.xml 내부의 TextView 태그의 text 속성 값에 fragment1이라고 입력해줍니다.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Frag1">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="fragment1" />

</FrameLayout>

 

같은 과정을 2번 더 반복하여 Frag2와 Frag3도 만들어 주세요.

 

 

그럼 이제 하단탭을 사용할 준비가 되었습니다. FragmentActivity.java로 돌아가 코드를 작성하겠습니다.

 

FragmentActivity.java

package com.example.week3;

import android.os.Bundle;
import android.view.MenuItem;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;


import com.google.android.material.bottomnavigation.BottomNavigationView;

import java.util.ArrayList;

public class FragmentActivity extends AppCompatActivity {

    BottomNavigationView bottomNavigationView;
    Frag1 frag1;
    Frag2 frag2;
    Frag3 frag3;

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

        bottomNavigationView = findViewById(R.id.bottomNavigation);
        frag1 = new Frag1();
        frag2 = new Frag2();
        frag3 = new Frag3();

        getSupportFragmentManager().beginTransaction().replace(R.id.mainFrame, frag1).commit();

        bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.tab1: {
                        getSupportFragmentManager().beginTransaction().replace(R.id.mainFrame, frag1).commit();
                        return true;
                    }
                    case R.id.tab2: {
                        getSupportFragmentManager().beginTransaction().replace(R.id.mainFrame, frag2).commit();
                        return true;
                    }
                    case R.id.tab3: {
                        getSupportFragmentManager().beginTransaction().replace(R.id.mainFrame, frag3).commit();
                        return true;
                    }
                }
                return false;
            }
        });
    }
}

 

그리고 앱을 실행시키기 위해서 AndroidManifest.xml 파일에

<activity android:name=".FragmentActivity" />

태그를 추가하는 것을 잊지 마세요.

 

MainActivity.java에 FragmentActivity로 전환되는 버튼 기능을 추가하고 앱을 실행시켜보도록 하겠습니다.

 

MainActivity.java

Button btn_fragment = findViewById(R.id.btn_fragment);
btn_fragment.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    	Intent intent = new Intent(getApplicationContext(), FragmentActivity.class);
    	startActivity(intent);
    }
});

 

실행시키면

BottomNavigationView가 잘 실행되는 것을 볼 수 있습니다.

 

 

그리고 ViewPager라고 화면을 드래그해도 fragment를 전환할 수 있습니다.

여기에 바로 실습해보도록 하겠습니다.

실습 : ViewPager 사용하기

 

우선 activity_fragment.xml의 FrameLayout을 viewpager로 바꿔줍니다.

 

activity_fragment.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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/mainFrame"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:menu="@menu/bottom_menu"
        android:background="#ffffff"/>

</LinearLayout>

 

FragmentActivity.java

package com.example.week3;

import android.os.Bundle;
import android.view.MenuItem;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.ViewPager;

import com.google.android.material.bottomnavigation.BottomNavigationView;

import java.util.ArrayList;

public class FragmentActivity extends AppCompatActivity {

    BottomNavigationView bottomNavigationView;
    Frag1 frag1;
    Frag2 frag2;
    Frag3 frag3;
    ViewPager pager;

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

        bottomNavigationView = findViewById(R.id.bottomNavigation);
        frag1 = new Frag1();
        frag2 = new Frag2();
        frag3 = new Frag3();

        //getSupportFragmentManager().beginTransaction().replace(R.id.mainFrame, frag1).commit();
        pager = findViewById(R.id.mainFrame);
        pager.setOffscreenPageLimit(3);
        final MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        adapter.addItem(frag1);
        adapter.addItem(frag2);
        adapter.addItem(frag3);
        pager.setAdapter(adapter);

        bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.tab1: {
                        //getSupportFragmentManager().beginTransaction().replace(R.id.mainFrame, frag1).commit();
                        pager.setCurrentItem(0);
                        return true;
                    }
                    case R.id.tab2: {
                        //getSupportFragmentManager().beginTransaction().replace(R.id.mainFrame, frag2).commit();
                        pager.setCurrentItem(1);
                        return true;
                    }
                    case R.id.tab3: {
                        //getSupportFragmentManager().beginTransaction().replace(R.id.mainFrame, frag3).commit();
                        pager.setCurrentItem(2);
                        return true;
                    }
                }
                return false;
            }
        });

        pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                bottomNavigationView.getMenu().getItem(position).setChecked(true);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    }
    class MyPagerAdapter extends FragmentStatePagerAdapter {
        ArrayList<Fragment> items = new ArrayList<>();
        public MyPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        public void addItem(Fragment item) {
            items.add(item);
        }

        @Override
        public Fragment getItem(int position) {
            return items.get(position);
        }

        @Override
        public int getCount() {
            return items.size();
        }
    }
}

FragmentActivity.java 코드는 위와 같이 수정하면 ViewPager를 사용할 수 있습니다.