본문 바로가기

카테고리 없음

84 Android SupportLibrary(Fragment, ViewPager, RecyclerView, Material Design

** Fragment

=> API Level 11 부터 추가된 뷰

=> View 이지만 Activity 처럼 수명주기를 가지고 있다.

 

1. 생성

=> Fragment 클래스를 상속받아서 생성

=> onCreateView 메소드를 재정의 해서 화면에 출력할 View를 리턴해 주어야 한다.

=> layout.xml 파일을 만들어서화면을 만들고 LayoutInflater를 이용해서 전개하는 형태를 많이 사용

 

2. Layout 파일에서는 fragment 태그로 생성하고 class 속성에 1번에서 만든 클래스를 등록

 

3. 코드로 생성

=> FragmentTransaction 객체를 Fragment 클래스의 beginTransaction메소드를 호출해서 생성하고 add 메소드를 이용해서 View들을 직접 설정하고 commit을 호출해서 생성한다.

=> commit 메소드가 화면에 적용하는 메소드이다.

 

4. 수명 주기 관련 메소드

=> onAttach : Fragmemt가 Activity에 부착될 때 호출되는 메소드

=> onCreate : 생성될 때 - 만들어질 때 1번만 호출되는 메소드

=> onResume : 화면에 보여질 때 - 활성화 될 때 호출되는 메소드

=> onPause : 화면에서 제거될 때 호출되는 메소드

 

5. 제공되는 Fragment

=> ListFragment : ListView를 포함하는 Fragment

=> WebViewFragment : WebView를 포함하는 Fragment

=> DialogFrament : Dialog를 포함하는 Fragment

=> 위 3가지는 별도의 디자인 필요없이 바로 사용이 가능

 

6. Fragment 는 태블릿 때문에 등장한 View

=> 왼쪽에 목록을 보여주고 오른쪽에 목록에서 선택한 항목을 출력하는 것을 구현하기 위해서 등장

=> 초창기에는 왼쪽 목록에 URL을 출력하고 그 URL을 선택하면 오른쪽에 보여주는 형태의 Application에 많아서 처음부터 제공

 

7. Fragment는 앱을 구성하는 필수 뷰는 아니다.

=> 디바이스의 크기가 점점 커지고 해상도가 높아지기 때문에 많이 사용

 

8. 실습

=> 웹의 SPA 처럼 상단에 메뉴를 배치하고 하단에 출력 영역을 배치한 후 메뉴를 누르면 하단에 서로 다른 컨텐츠를 출력하기

이런 모양은 FrameLayout을 이용해서도 구현이 가능

FrameLayout을 이용할 때는 교체하는 개념이 아니고 보이게 하고 안 보이게 하는 속성을 이용해서 구현

=> 상단에 버튼을 3개 배치해서 버튼을 누를 때 마다 ListFragment, DialogFragment, 일반 Fragment를 출력

 

1) 안드로이드 Application 생성

 

2) 화면 디자인

 

<?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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:id="@+id/list"
            android:text="List 출력"/>
        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:id="@+id/dialog"
            android:text="Dialog 출력"/>
        <Button
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:id="@+id/fragment"
            android:text="Fragment 출력"/>

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

</LinearLayout>

 

 

3) 3번째 Fragment 화면으로 사용할 Layout을 생성 - three fragment

 

<?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="match_parent"
        android:layout_height="match_parent"
        android:text="직접 만든 Fragment"
        android:textSize="32sp"
        android:gravity="center"/>

</LinearLayout>

 

 

4) ListFragment로 부터 상속받는 클래스를 생성 - OneFragment

=> ListFragment 는 ListView로부터 상속받은 클래스라고 간주할 수 있고 모든 이벤트 핸들러 메소드가 전부 비어있는 상태로 구현되어 있다.

메소드만 오버라이딩하면 이벤트 처리가 전부 구현이 된다.

 

package com.example.android0804;

import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import androidx.fragment.app.ListFragment;

public class OneFragment extends ListFragment {
    @Override
    public void onViewCreated(View view, Bundle saveInstanceState){
        //상위 클래스의 메소드를 호출
        super.onViewCreated(view, saveInstanceState);
        //출력할 데이터 생성
        String [] oop = {"Encapsulation", "Inheritance", "Polymorphism"};
        //Adapter 생성
        ArrayAdapter<String> adapter =
                new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, oop);
        //어댑터 연결
        this.setListAdapter(adapter);
    }

    @Override
    //listView는 이벤트가 발생한 ListView
    //v는 이벤트가 발생한 항목 뷰
    //posision은 누른 항목의 인덱스
    //id는 v의 id
    public void onListItemClick(ListView listView, View v, int position, long id){
        //선택한 데이터 찾아오기
        String item = (String)listView.getAdapter().getItem(position);
        //Toast 출력
        Toast.makeText(getActivity(), item, Toast.LENGTH_LONG).show();
    }
}

 

 

 

5) MainActivity.java 파일에 인스턴스 변수를 선언

=> 버튼 3개

=> OneFragment 변수, FragmentManager

 

public class MainActivity extends AppCompatActivity {
    Button btn1, btn2, btn3;

    FragmentManager fm;
    OneFragment oneFragment;

 

 

6) MainActivity.java 파일의 onCreate 메소드에서 출력 영역에 oneFragment 출력하기

 

package com.example.android0804;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import android.os.Bundle;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    Button btn1, btn2, btn3;

    FragmentManager fm;
    OneFragment oneFragment;

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

        btn1 = (Button)findViewById(R.id.list);
        btn2 = (Button)findViewById(R.id.dialog);
        btn3 = (Button)findViewById(R.id.fragment);

        //FragmentManager 생성
        fm = getSupportFragmentManager();
        //Fragment 객체 생성
        oneFragment = new OneFragment();
        //화면을 갱신할 준비
        FragmentTransaction tf = fm.beginTransaction();
        tf.addToBackStack(null);
        //main_container 영역에 oneFragment를 출력
        tf.add(R.id.main_container, oneFragment);
        //화면을 갱신
        tf.commit();
    }
}

 

 

7) 두번째 프래그먼트로 사용할 Fragment 클래스를 생성

=> DialogFragment로부터 상속을 받는 TwoFragment 클래스

 

package com.example.android0804;

import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;

public class TwoFragment extends DialogFragment {
    @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle saveInstanceState){
        //Android에서 대화상자는 메소드 체이닝을 이용해서 생성
        AlertDialog dialog = new AlertDialog.Builder(getActivity())
                .setIcon(android.R.drawable.ic_dialog_alert)
                .setTitle("대화상자 프래그먼트")
                .setMessage("대화상자를 출력합니다.")
                .setPositiveButton("확인", null)
                .create();
        return dialog;
    }
}

 

 

8) 세번째로 보여질 Fragment 클래스를 생성

=> Fragment를 상속받는 클래스 - ThreeFragment

 

package com.example.android0804;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.fragment.app.Fragment;

public class ThreeFragment extends Fragment {
    //화면을 만들기 위한 메소드
    //LayoutInflater : layout.xml의 내용을 View로 만들어주는 클래스
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saveInstanceState){
        //뷰를 전개할 때 첫번째는 뷰의 아이디
        //두번째 이 뷰가 놓이게 되는 레이아웃 - 부모 뷰
        //세번째 자신이 최상위 뷰인지 설정 - 부모뷰가 될수 있는 지 여부
        return inflater.inflate(R.layout.threeframent, container, false);
    }
}

 

 

9) 2개의 인스턴스 변수 추가

 FragmentManager fm;
    OneFragment oneFragment;
    TwoFragment twoFragment;
    ThreeFragment threeFragment;

 

 

10) 

 

package com.example.android0804;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements Button.OnClickListener{
    public void onClick(View view){
        switch (view.getId()){

        }
    }

    Button btn1, btn2, btn3;

    FragmentManager fm;
    OneFragment oneFragment;
    TwoFragment twoFragment;
    ThreeFragment threeFragment;

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

        btn1 = (Button)findViewById(R.id.list);
        btn2 = (Button)findViewById(R.id.dialog);
        btn3 = (Button)findViewById(R.id.fragment);

        //FragmentManager 생성
        fm = getSupportFragmentManager();
        //Fragment 객체 생성
        oneFragment = new OneFragment();
        //화면을 갱신할 준비
        FragmentTransaction tf = fm.beginTransaction();
        tf.addToBackStack(null);
        //main_container 영역에 oneFragment를 출력
        tf.add(R.id.main_container, oneFragment);
        //화면을 갱신
        tf.commit();

        /*
        각각의 이벤트 핸들러 만들기 첫번째
        btn1.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View view) {

            }
        });
        btn2.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View view) {

            }
        });
        btn3.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View view) {

            }
        });
         */

        twoFragment = new TwoFragment();
        threeFragment = new ThreeFragment();

        //두번째 방법 - 버튼들의 이벤트 처리 객체
        Button.OnClickListener handler = new Button.OnClickListener(){
            //이벤트 처리 메소드의 매개변수 정보를 이용하면 이벤트가 발생한 객체와
            // 그 이벤트 러리를 위해서 필요한 정보를 가져올 수 있다.
            public void onClick(View view){
                switch (view.getId()){
                    case R.id.list:
                        FragmentTransaction tf = fm.beginTransaction();
                        tf.addToBackStack(null);
                        tf.replace(R.id.main_container, oneFragment);
                        tf.commit();
                    case R.id.dialog:
                        //대화상자는 다른 프래그먼트와 교체되는 것이 아니고 프래그먼트 위에 출력
                        if(twoFragment.isVisible() == false){
                            twoFragment.show(fm, null);
                        }
                        break;
                    case R.id.fragment:
                        FragmentTransaction tf2 = fm.beginTransaction();
                        tf2.addToBackStack(null);
                        tf2.replace(R.id.main_container, threeFragment);
                        tf2.commit();
                }
            }
        };
        btn1.setOnClickListener(handler);
        btn2.setOnClickListener(handler);
        btn3.setOnClickListener(handler);

        /* 세번째 방법
        //자기에게 implement 한 경우는
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
        btn3.setOnClickListener(this);
         */

    }
}

=> 대화 상자는 데이터 다운로드 받을 때 받기 전에 메시지 출력하는 용도로 사용하면 유용하다.

서버에서 데이터를 다운로드 받을 때 다른 작업을 해도 된다고 알리고자 하는 경우나 다운로드 받는 과정을 표시할 때 주로 대화상자를 이용한다.

 

=> 안드로이드에서 여러 개의 페이지를 화면에 구현할 때 Activity를 이용하지 않고 Fragment를 구현하는 경우가 종종 있는데 이때는 버튼을 사용하기도 하지만 ViewPager와 많이 사용한다.

 

** 안드로이드에서 이벤트 처리

1. 상위 클래스로부터 상속받은 메소드를 오버라이딩 - touch 이벤트

 

2. Delegate 패턴 - 이벤트 처리 객체를 지정

=> 이벤트 처리 객체는 이벤트가 발생하는 뷰의 하위 인터페이스로 생성되어 있다.

1) Activity 클래스에 implements해서 사용

 

2) 이벤트 연결 메소드 안에 Anonymous 객체를 각각 만들어서 처리

 

3) Anonymous 객체를 별도로 만들어서 설정

 

4) click, longClick에 한해서는 레이아웃 파일에서 속성으로 메소드를 지정할 수 있다.

 

다른 개발자가 만든 코드를 볼 때는 버튼의 경우는 레이아웃 파일을 확인하고 Activity클래스에 다른 인터페이스가 implements 되어 있는지 확인

자바 1.7 이상을 사용하는 경우 Anonymous 코드는 람다로 변환될 수 있기 때문에 람다의 기본 형식도 확인

 

 

** ViewPager

=> 사용자의 손가락을 따라가면서 순서대로 좌우 화면이 슬라이드되어 나타나는 구성

=> AdapterView라서 Adapter를 구현해야 하는데 Adapter 클래스는 PagerAdapter와 FragmentPageAdapter 2개를 제공

=> PageAdapter는 여러개의 페이지를 스와이프하기 위한 Adapter

=> FragmentPageAdapter는 여러개의 Fragment를 스와이프 하기 위한 Adapter

GragmenPageAdapter를 사용하기 위해서는 FragmentPageAdapter를 상속받는 클래스를 생성해서 getCount 메소드에서 보여질 Fragment의 개수를 리턴하고 getItem메소드에서 인덱스를 매개변수로 받아서 인덱스에 해당하는 Fragment를 리턴하면 된다.

 

1. 실행가능한 Activity를추가 - PagerActivity

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.viewpager.widget.ViewPager 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=".PagerActivity"
    android:id="@+id/pager">

</androidx.viewpager.widget.ViewPager>

 

2. 출력할 Fragment를 공급해줄 Adapter 클래스를 생성

=> FragmentPagerAdapter를 상속받는 클래스

 

package com.example.android0804;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;

import java.util.ArrayList;

public class MyPagerAdapter extends FragmentPagerAdapter {
    //Fragment 의 List를 생성
    ArrayList<Fragment> fragments;

    //생성자 - 상위 클래스에 기본 생성자가 없어서 생성
    public MyPagerAdapter(@NonNull FragmentManager fm) {
        super(fm);
        //스와이프로 보여질 프래그먼트 리스트를 생성
        fragments = new ArrayList<>();
        fragments.add(new OneFragment());
        fragments.add(new ThreeFragment());

    }

    @NonNull
    @Override
    //실제 출력될 Fragment를 설정하는 메소드
    public Fragment getItem(int position) {
        return fragments.get(position);
    }

    //출력할 Fragment 개수를 설정하는 메소드
    @Override
    //이 메소드가 리턴한 만큼 다른 메소드를 수
    public int getCount() {
        return fragments.size();
    }
}

 

 

3. PagerAdapter.java 파일의 onCreate 메소드에서 ViewPager 설정

 

package com.example.android0804;

import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager.widget.ViewPager;

import android.os.Bundle;

public class PagerActivity extends AppCompatActivity {

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

        //어댑터 생성
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        ViewPager pager = (ViewPager)findViewById(R.id.pager);
        pager.setAdapter(adapter);
    }
}

 

 

** RecyclerView

=> ListView의 확장된 모델

Android 5.0 에서 추가

 

1. 구성 요소

1) Adapter : 데이터 와 각 항목 구성

2) ViewHolder : 각 항목 구성 뷰의 재활용을 목적을 만든 클래스

3) LayoutManeger : 항목을 배치

4) ItemDecoration : 항목을 꾸미는 클래스

5) ItemAnomation : 아이템이 추가되거나 삭제 및 정렬될 때의 애니메이션 처리를 위한 클래스

 

2. 작성 방법

1) RecyclerView를 준비

2) ViewHolder 클래스를 상속받는 클래스를 만들어서 항목을 디자인

3) Adapter 캘래스를 상속받는 클래스를 만들어서 ViewHolder를 지정해서 항목 뷰를 만들도록 하고 데이터 개수를 설정

4) LayoutManager 객체를 만들어서 RecycleView에 설정

=> LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager(높이가 다른 그리드) 등을 이용

5) ItemDecoration을 상속받아서 메소드를 재정의

=> onDraw : 항목을 배치하기 전에 호출

=> onDrawOver : 항목을 배치한 후에 호출

=> getItemOffset : 각 항목을 배치할 때 호출

 

** 안드로이드에서 외부 라이브러리 사용

=> build.gradle 파일의 dependencies 항목에 추가하면 된다.

직접 추가하기도 하지만 검색해서 추가하기도 한다.

직접 추가하는 경우에는 버전의 안정성을 담보할수 없다.

직접 추가하는 경우는 모든 책임을 프로그래머가 져야 한다.

 

=> android.support로 시작하거나 androidx로 시작하는 클래스를 사용할 대는 라이브러러의 의존성을 설정할 때 검색해서 설정하자.

안드로이드는 새로운 버전이 나올 때마다 새로운 뷰나 API를 추가하기 때문에 새로운 버전에서 앱을 개발할 대 이전 버전의 API를 사용 못하는 경우가 발생한다.

 

** 스마트폰 SDK에서 모듈의 개념

독립적으로 실행될 수 있는 것을 모듈이라고 한다.

 

Project(Workspace - 작업의 단위) > Module(Application  - 배포 단위) > 실행단위(Activity)

 

 

3. 사용

1) RecyclerView 사용을 위한 의존성 추가

=> 모듈을 선택하고 마우스 오른쪽 버튼을 눌러서 [Open Module Settings]를 선택하고 왼쪽 창에서 dependencies를 선택한 후 라이브러리 위의 + 버튼을 누르고 Library Dependency를 선택하고 recycle를 입력해서 검색

=> build.gradle 파일의 dependencies에 직접 추가해도 된다.

implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha

 

=> 직접 입력하게 되면 다운로드가 되지 않는 경우도 발생할 수 있다.

 

 

2) 실행가능한 Activity 추가 - RecyclerActivity

 

3) 추가된 레이아웃 수정

=>RecyclerView 1개만 배치

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView 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=".RecyclerActivity"
    android:id="@+id/recycler">

</androidx.recyclerview.widget.RecyclerView>

 

 

4. 하나의 항목을 만들 ViewHolder 클래스를 생성

=> RecyclerView.ViewHolder로 부터 상속

=> 항목 뷰를 만들어주는데 이 뷰에서 만든 뷰들은 스크롤 할 때 자동으로 재사용을 한다. 이 재사용 메카니즘 때문에 이 부를 RecyclerView라고 한다.

 

package com.example.android0804;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

//재사용 가능한 항목 뷰를 위한 클래스
class MyViewHolder extends RecyclerView.ViewHolder{
    public TextView title;

    public MyViewHolder(@NonNull View itemView) {
        super(itemView);
        //시스템이 제공하는 텍스트 뷰를 사용
        title = itemView.findViewById(android.R.id.text1);
    }
}

public class RecyclerActivity extends AppCompatActivity {

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

 

 

5) 여러 개의 데이터를 출력하는 뷰에 데이터를 공급하고 뷰를 만들어주는 Adapter 클래스를 생성 - RecyclerView.Adapter<ViewHolder>를 상속

 

package com.example.android0804;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

//재사용 가능한 항목 뷰를 위한 클래스
class MyViewHolder extends RecyclerView.ViewHolder{
    public TextView title;

    public MyViewHolder(@NonNull View itemView) {
        super(itemView);
        //시스템이 제공하는 텍스트 뷰를 사용
        title = itemView.findViewById(android.R.id.text1);
    }
}

//RecyclerView에 항목을 만들어주는 Adapter 클래스
class MyAdapter extends RecyclerView.Adapter<MyViewHolder>{
    //출력할 데이터 리스트 변수
    //Adapter는 넘겨받은 데이터를 가지고 뷰의 개수를 설정하고 출력할 뷰 모양을 만들어주고 그 뷰에 데이터를 출력해주는 클래스
    //Adapter는 Model이 아니다.
    //스마트폰에서 모델의 역할은 Activity 또는 별도의 클래스를 생성
    //데이터에 변화가 생겼을 때 데이터를 재출력할려면 주입받는 데이터가 있어야 한다.
    List<String> list;
    public  MyAdapter(List<String> list){
        this.list = list;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        //항목 뷰를 생성해서 ViewHolder에게 념겨줌
        //안드로이드가 제공하는 뷰를 사용
        View view = LayoutInflater.from(parent.getContext())
                .inflate(android.R.layout.simple_list_item_1, parent, false);
        return new MyViewHolder(view);
    }

    //출력할 데이터를 매핑하는 클래스
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        //현재 위치에 해당하는 데이터를 가져오기
        String item = list.get(position);
        holder.title.setText(item);
    }

    //출력할 행의 개수를 설정하는 메소드
    @Override
    public int getItemCount() {
        return list.size();
    }
}

public class RecyclerActivity extends AppCompatActivity {

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

 

 

 

6)항목의 세부적인 옵션을 수정하는 ItemDecoration을 생성

=>여기를 잘 설계하면 그룹 별 분류를 할 수 있습니다.

=>RecyclerView.ItemDecoration 클래스를 상속

 

//각 항목의 세부적인 옵션을 설정할 클래스
class MyItemDecoration extends RecyclerView.ItemDecoration{
    //항목 뷰가 배치될 때 호출되는 메소드
    //outRect 가 외곽 여백 - margin
    //View는 각 항목 뷰
    //state 현재 상태
    @Override
    public void getItemOffsets(
            Rect outRect, View view,
            RecyclerView parent, RecyclerView.State state){
        //현재 항목 뷰의 인덱스 찾아오기
        int index = parent.getChildAdapterPosition(view) + 1;
        /*
        //Item - category, itemname : category로 정렬
        Item current = list.get(index);
        Item prev = list.get(index - 1);
        if(current.category.equals(prev.category)){
            outRect.set(20,20,20,20);
        }else{
            outRect.set(20,20,20,60);
        }
        */


        //3개를 출력하고 여백을 조금 더 설정
        if(index % 3 == 0){
            outRect.set(20,20,20,60);
        }else{
            outRect.set(20,20,20,20);
        }
        //여러 개의 데이터를 출력할 때 그룹 별로 여백을 다르게 지정하고자 하면
        //index로 출력할 데이터를 찾아아고 이전 데이터와의 그룹이 다르면
        //여백이 많이 설정
        //여러 개의 데이터를 가져올 때 자주 활용하는 컬럼으로 정렬해서 가져오기

        //뷰의 배경색을 설정
        view.setBackgroundColor(0xFFECECE9);
        ViewCompat.setElevation(view, 20.0f);

    }
}

 

 

 

 

7) onCreate 메소드에서 RecyclerView 를 출력

 

package com.example.android0804;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.graphics.Rect;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

//재사용 가능한 항목 뷰를 위한 클래스
class MyViewHolder extends RecyclerView.ViewHolder{
    public TextView title;

    public MyViewHolder(@NonNull View itemView) {
        super(itemView);
        //시스템이 제공하는 텍스트 뷰를 사용
        title = itemView.findViewById(android.R.id.text1);
    }
}

//RecyclerView에 항목을 만들어주는 Adapter 클래스
class MyAdapter extends RecyclerView.Adapter<MyViewHolder>{
    //출력할 데이터 리스트 변수
    //Adapter는 넘겨받은 데이터를 가지고 뷰의 개수를 설정하고
    //출력할 뷰 모양을 만들어주고 그 뷰에 데이터를 출력해주는 클래스
    //Adapter는 Model 이 아님
    //스마트 폰에서 모델의 역할은 Activity 또는 별도의 클래스를 생성
    //데이터에 변화가 생겼을 때 데이터를 재출력할려면
    //주입받는 데이터가 있어야 함
    List<String> list;
    public MyAdapter(List<String> list){
        this.list = list;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        //항목 뷰를 생성해서 ViewHolder에게 넘겨줌
        //안드로이드가 제공하는 뷰를 사용
        View view = LayoutInflater.from(parent.getContext())
                        .inflate(android.R.layout.simple_list_item_1, parent, false);

        return new MyViewHolder(view);
    }

    //출력할 데이터를 매핑하는 클래스
    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        //현재 위치에 해당하는 데이터를 가져오기
        String item = list.get(position);
        holder.title.setText(item);
    }

    //출력할 행의 개수를 설정하는 메소드
    @Override
    public int getItemCount() {
        return list.size();
    }
}

//각 항목의 세부적인 옵션을 설정할 클래스
class MyItemDecoration extends RecyclerView.ItemDecoration{
    //항목 뷰가 배치될 때 호출되는 메소드
    //outRect 가 외곽 여백 - margin
    //View는 각 항목 뷰
    //state 현재 상태
    @Override
    public void getItemOffsets(
            Rect outRect, View view, RecyclerView parent, RecyclerView.State state){
        //현재 항목 뷰의 인덱스 찾아오기
        int index = parent.getChildAdapterPosition(view) + 1;
        /*
        //Item - category, itemname : category로 정렬
        Item current = list.get(index);
        Item prev = list.get(index - 1);
        if(current.category.equals(prev.category)){
            outRect.set(20,20,20,20);
        }else{
            outRect.set(20,20,20,60);
        }
        */

        //3개를 출력하고 여백을 조금 더 설정
        if(index % 3 == 0){
            outRect.set(20,20,20,60);
        }else{
            outRect.set(20,20,20,20);
        }
        //outRect.set(20,20,20,20);
        //여러 개의 데이터를 출력할 때 그룹 별로 여백을 다르게 지정하고자 하면
        //index로 출력할 데이터를 찾아아고 이전 데이터와의 그룹이 다르면
        //여백이 많이 설정
        //여러 개의 데이터를 가져올 때 자주 활용하는 컬럼으로 정렬해서 가져오기

        //뷰의 배경색을 설정
        view.setBackgroundColor(0xFFECECE9);
        ViewCompat.setElevation(view, 20.0f);


    }
}

public class RecyclerActivity extends AppCompatActivity {

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

        //RecyclerView 찾아오기
        RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler);
        //출력할 데이터 생성
        ArrayList<String> list = new ArrayList<>();
        list.add("변수");
        list.add("연산자");
        list.add("제어문");
        list.add("배열");
        list.add("클래스");
        list.add("상속");
        list.add("인터페이스");
        list.add("내부클래스");
        list.add("예외처리");
        //레이아웃 설정
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        //격자 모양으로 옆으로 3개씩 배치하는 레이아웃
        //위쪽의 여백 설정하는 부분을 수정해야 합니다.
        /recyclerView.setLayoutManager(new GridLayoutManager(this, 3));

        //어댑터 설정
        MyAdapter adapter = new MyAdapter(list);
        recyclerView.setAdapter(adapter);
        //아이템 테코레이션 설정
        recyclerView.addItemDecoration(new MyItemDecoration());

    }
}

 

 

 

** Material Design

=> 구글의 디자인 철학

=> 2014년 이전까지는 구글은 제품 별로 다른 UI를 채택했는데 2014년에 구글이 그동안 제공해왔던 모든 디자인들을 하나로 통합해서 재편

=> 처음에는 supprot library라는 이름으로 배포를 했는데 2019년부터 이를 다시 통합해서 androidx 패키지로 제공

=> android에서는 2018년까지 되던 프로제그가 tarsdk를 올리면 에러가 나는 경우가 발생한다.

=> 자주 사용되는 것들이 NavigationDrawer(DrawLayout을 이용해서 작성), 왼쪽이나 오른쪽에서 리스트 뷰를 숨겨놓고 있다가 이떤 이벤트에 의해서 화면에 출력되는 구조

반응형 웹 디자인에서 메뉴를 숨기는 용도로 사용되던 디자인

=> Snackbar ; Toast가 메시지를 출력하는 용도로 사용되었는데 Toast는 사용자의 상호작용에는 사용 못함

메시지를 출력하고 사용자가 선택했을 때 어던 동작으로 수행할 수 있도록 만든 UI

=> Activity 화면에 떠 있는 것 처럼 보이는 버튼 : FloatingActionButton

=> TabLayout : TabHost를 이용해서 Tab을 구현했는데 스와이프까지 적용할 수 있는 레이아웃

=> Toolbar : ActionBar가 제공되지만 사용자가 원하는 대로 만들고 보여지도록 할 수 있는 바

=> Bar의 크기를 늘려서 이미지 등을 출력할 수 있는 AppBarLayout

=> CoordiantorLayout : 뷰2개의 상호작용에 이용하는 레이아웃

FloatingActionButton 과 Snackbar 또는 AppBar 와 ListView 나 RecycleView 등의 작용에 사용

FloatingActionButton은 하단에 배치하는데 Snackbar가 출력되면 버튼이 안보일수 있어서 CoordinatorLayout 안에 배치하면 Snackbar가 출력될 때 버튼이 자동으로 위로 올라감

 

상단의 바와 스크롤이 가능한 뷰가 같이 존재할 때 상단의 바 때문에 스크롤 가능한 뷰의 높이가 좁아져서 많은 양의 데이터를 출력할 수 없는 경우가 발생한다.

스크롤이 가능한 뷰를위로 스크롤 하면 AppBar가 자동으로 숨겨지도록 만들 수 있다.

 

Pull to Refresh : 아래로 당겨서 새로고침, Layout으로 제공해서 이 안에 스크롤이 가능한 뷰만 배치하면 된다.

스크롤을 가장 아래에서 했을 때 업데이트(현재 페이지 이전의 데이터를 가져오는 것) 하는 것과 위에서 아래로 내렸을 때 업데이트하는 것은 다름

 

하단에서 상단으로 올라오는 대화상자 - BottomSheet

보조적인 입력을 받을 때 BottomSheet를 많이 사용

모바일에서는 콤보 박스를 사용하는 것보다는 하단에서 Sheet 형태로 올라와서 선택하면설정되도록 하는 경우가 많다.

 

**DrawLayout

=> ListView에 목록을 출력해 놓고 어떤 이벤트를만나면 왼쪽이나 오른쪽에서 밀려들어오듯이 출력되는 레이아웃

=> 스마트폰의 세로 모드에서 메뉴 역할을 하는 목록을 숨겨두었다가 필요할 때 보여주는 형태로 많이 사용한다.

 

1. material 에 대한 의존성을 설정

 

2.실행 가능한 Activity 추가(NavigationActivity)

 

3. 프로젝트 안에서 사용할 문자열 상수 2개를 res/values/strings.xml에 등록

 

<resources>
    <string name="app_name">Android0804</string>
    
    <string name="drawer_open">네비게이션 열기</string>
    <string name="drawer_close">네비게이션 닫기</string>
</resources>

=> 처음한번 호출해서 변경없이 사용할 문자열 중에서 정적으로 변경가능성이 있는것은 Java 코드에 문자열 상수로 설정하지 말고 별도의 파일에 만들어두고 파일에서 읽는 것이 좋다. - 유지보수 때문

 

4.ListView에 출력할 메뉴를 생성

=>res 디렉토리에 menu 라는 디렉토리를 만들고 그 안에 xml 파일을 추가해서 생성

=>res/menu/menu_drawer.xml 파일로 만들기

 

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/menu_drawer_home"
        android:icon="@android:drawable/ic_menu_directions"
        android:title="HOME"/>
    <item
        android:id="@+id/menu_drawer_message"
        android:icon="@android:drawable/ic_menu_agenda"
        android:title="MESSAGE"/>

</menu>

 

5. 제공되는 레이아웃 수정

=> 전체를 DrawLayout으로 감싸고 그 안에 보여일 VIew를 생성

=> 안에 NavigationView를 추가 : 메뉴 형태로 보여질 뷰이다.

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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=".NavigationActivity"
    android:id="@+id/main_drawer">
    <!--기본 화면에 보여질 레이아웃 -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="네비게이션 사용"/>
    </RelativeLayout>
    <!-- 보여졌다 안보여졌다 하는 네비게이션을 생성 -->
    <com.google.android.material.navigation.NavigationView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:id="@+id/main_draw_view"
        android:layout_gravity="start"
        app:menu="@menu/menu_drawer" />

</androidx.drawerlayout.widget.DrawerLayout>

 

 

 

6.Activity 파일에 필요한 인스턴스 변수를 선언

  

public class NavigationActivity extends AppCompatActivity {
    DrawerLayout drawer;
    //메뉴를 토글할 변수
    ActionBarDrawerToggle toggle;
    //현재 메뉴 표시 여부를 저장할 변수
    boolean isDrawOpend;

 

 

7.Activity 파일의 onCreate 메소드에서 출력

  

package com.example.android0804;

import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.drawerlayout.widget.DrawerLayout;

import android.os.Bundle;

public class NavigationActivity extends AppCompatActivity {
    DrawerLayout drawer;
    //메뉴를 토글할 변수
    ActionBarDrawerToggle toggle;
    //현재 메뉴 표시 여부를 저장할 변수
    boolean isDrawOpend;

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

        //네비게이션 출력 설정
        drawer = (DrawerLayout)findViewById(R.id.main_drawer);
        //토글 생성
        toggle = new ActionBarDrawerToggle(
                this, drawer,R.string.drawer_open, R.string.drawer_close);
        //액션 바에 출력
        getSupportActionBar().setDisplayShowTitleEnabled(false);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        toggle.syncState();

    }
}

 

=> 실행하면 상단에 삼선 버튼이 출력됩니다.

 

 

8.Activity 파일에 삼선 버튼을 눌렀을 때 호출되는 메소드를 재정의

 //삼선 버튼을 눌렀을 때 호출되는 메소드

    @Override

    public boolean onOptionsItemSelected(MenuItem item){

        if(toggle.onOptionsItemSelected(item)){

            return false;

        }

        return super.onOptionsItemSelected(item);

    }

=>실행을 다시하면 삼선 버튼을 누를 때 마다 목록이 토글 됩니다.

 

 

9. onCreate 메소드에 메뉴를 눌렀을 때 수행할 코드를 작성

=> NavigationView의 onNavigationItemSelectedListener 로 처리

 

package com.example.android0804;

import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.drawerlayout.widget.DrawerLayout;

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

import com.google.android.material.navigation.NavigationView;

public class NavigationActivity extends AppCompatActivity {
    DrawerLayout drawer;
    //메뉴를 토글할 변수
    ActionBarDrawerToggle toggle;
    //현재 메뉴 표시 여부를 저장할 변수
    boolean isDrawOpend;

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

        //네비게이션 출력 설정
        drawer = (DrawerLayout)findViewById(R.id.main_drawer);
        //토글 생성
        toggle = new ActionBarDrawerToggle(
                this, drawer,
                R.string.drawer_open, R.string.drawer_close);
        //액션 바에 출력
        getSupportActionBar().setDisplayShowTitleEnabled(false);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        toggle.syncState();

        //메뉴를 눌렀을 때 수행할 코드를 작성
        NavigationView navigationView = (NavigationView)findViewById(R.id.main_draw_view);
        navigationView.setNavigationItemSelectedListener(
                new NavigationView.OnNavigationItemSelectedListener() {
                    @Override
                    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                        //메뉴 아이디를 찾아옴
                        int id = item.getItemId();
                        if(id == R.id.menu_drawer_home){
                            Toast.makeText(NavigationActivity.this,
                                    "홈을 선택", Toast.LENGTH_LONG).show();
                        }else{
                            Toast.makeText(NavigationActivity.this,
                                    "메시지를 선택", Toast.LENGTH_LONG).show();
                        }
                        return false;
                    }
                });
    }

    //삼선 버튼을 눌렀을 때 호출되는 메소드
    @Override
    public boolean onOptionsItemSelected(MenuItem item){
        if(toggle.onOptionsItemSelected(item)){
            return false;
        }
        return super.onOptionsItemSelected(item);
    }
}

 

 

 

 

** Xcode

=> iSO, Mac OS 앱 개발을 위한 IDE가 Xcode

=> Mac에만 설치

=> 최신 버전은 운영체제에 버전이 맞아야만 설치가 됨

최슨 Xcode를 사용하고자 하는 경우에는 운영체제도 최신 버전으로 업데이트해야 한다.

=> 앱 스토어가 아니라 developers.apple.com에서 베타 버전은 다운로드 받을 수도 있는데 이 버전은 운영체제가 실제 배포될 때는 사용할 수 없다.

 

1. 최신 버전 다운로드

=> AppStore에서 Xcode 검색

=> 애플 계정과 비밀번호를 입력해서 다운로드와 설치를 진행

 

2. 설치 후 처음 실행

=> 컴포넌트를 다운로드 받아서 추가

 

3. 최신버전이 설치가 안되는 경우 : 운영체제 버전이 낮아서인 경우가 많다.

=> 운영체제를 업데이트 하고 설치

 

4. 예전 버전을 설치하고자 하는 경우

https://developer.apple.com/downloads/more/

 

=> Xcode 11 버전 아래로는 Swift UI를 할 수 없다.

Swift UI 는 새로 등장한 UI 작성 방법 - 코드로 UI를 작성

 

5. Mac이 없는데 iOS Native App 개발

=> 가상머신(VM Ware)에 Mac OS X을 설치해서 할 수 있다.

기기 테스트는 안된다.