레이블이 Android인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Android인 게시물을 표시합니다. 모든 게시물 표시

2020년 7월 15일 수요일

안드로이드 프래그먼트로 화면 만들기



* 프래그먼트(Fragment) 로 화면 만들기

프래그먼트를 이용해서 각 버튼을 클릭시 이미지가 변경되도록 해보자.
즉 한 화면에 두 개의 프래그먼트가 들어가게 된다.
하나는 버튼으로만 구성되어 있는 프래그먼트, 다른 하나는 이미지를 볼수 있는 프래그먼트이다.
위쪽 프래그먼트에서 선택한 이미지가 어떤 것인지를 액티비티에 알려준 후 액티비티에서 아래쪽 프래그먼트에 해당 이미지가 보이게 만들어야 한다.


* fragment_list.xml - 버튼들만 가지고 있는 위쪽 레이아웃

버튼 세개를 넣었으며, 각 버튼마다 이미지가 다르게 나타나도록 할 것이다.


* ListFragment.java

화면에서 선택된 버튼에 따라 다른 프래그먼트의 이미지를 바꿔주려면 액티비티 쪽으로 데이터를 전달해야만 한다.


    package org.techtown.fragment;

import android.content.Context;
import android.media.Image;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

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

public class ListFragment extends Fragment {

    /**
     * ImageSelectionCallback 인터페이스를 정의함
     * 메인액티비티가 이 인터페이스를 구현하게 되면 프래그먼트의 각 버튼이 눌릴 때
     * 메인액티비티의 onImageSelected를 호출하게 된다.
     */
    public static interface ImageSelectionCallback{
        public void onImageSelected(int position);
    }
    public ImageSelectionCallback callback;

    /**
     * 프래그먼트가 액티비티 위에 올라오는 시점에 호출되는 메서드
     * @param context : 이 프래그먼트가 올라가는 액티비티를 말한다.
     */
    @Override
    public void onAttach(Context context){
        super.onAttach(context);
        // 메인엑티비티가 ImageSelectionCallback 인터페이스를 구현했으므로
        // context(메인엑티비티) 는 ImageSelectionCallback로 형변환이 가능하다.
        if(context instanceof ImageSelectionCallback){
            callback = (ImageSelectionCallback) context;
        }
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState){

        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_list, container, false);

        //각각의 버튼을 클릭했을 떄 callback객체의 onImageSelected()메서드를 호출한다.
        Button button = rootView.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(callback != null){
                    callback.onImageSelected(0);
                }
            }
        });
        Button button2 = rootView.findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(callback != null){
                    callback.onImageSelected(1);
                }
            }
        });

        Button button3 = rootView.findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(callback != null){
                    callback.onImageSelected(2);
                }
            }
        });

        return rootView;
    }
}

    



* fragment_viewer.xml - 이미지를 보여주는 아래쪽 레이아웃



ImageView 안드로이드 개발툴 이미지
LinearLayout에 ImageView를 세팅하고 첫번째 이미지를 설정해둔다.


* ViewerFragment.java

ViewerFragment는 이미지를 보이게 하는 프래그먼트이다. 
액티비티에서 이 프래그먼트의 setImage를 하면 해당 이미지로 바뀌게 된다.

package org.techtown.fragment;

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

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

public class ViewerFragment extends Fragment {
    ImageView imageView;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        
        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_viewer, container, false);
        
        imageView = rootView.findViewById(R.id.imageView);
        return rootView;
    }
    
    public void setImage(int resId){
        imageView.setImageResource(resId);
    }
}


* activity_main.xml

메인엑티비티에 아래와 같이 ListFragment와 ViewerFragment를 동일한 화면비중을 차지하도록 설정하도록 만든다.




* MainActivity.java

package org.techtown.fragment;

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

import android.os.Bundle;

//MainActivity는 ListFragment.ImageSelectionCallback를 구현한다.
public class MainActivity extends AppCompatActivity implements ListFragment.ImageSelectionCallback{

    ListFragment listFragment;
    ViewerFragment viewerFragment;

    int[] images = {R.drawable.dream01,R.drawable.dream02,R.drawable.dream03};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //프래그먼트매니저를 이용해 두 개의 프래그먼트 객체들을 변수에 할당한다.
        FragmentManager manager = getSupportFragmentManager();
        listFragment = (ListFragment) manager.findFragmentById(R.id.listFragment);
        viewerFragment = (ViewerFragment) manager.findFragmentById(R.id.viewerFragment);
    }

    /**
     * ListFragment에서 버튼이 클릭될때 메인엑티비티에 구현된 onImageSelected을 호출하게 되고
     * viewerFragment의 setImage를 호출함으로써 프래그먼트의 이미지를 변경하도록 한다.
     * @param position
     */
    @Override
    public void onImageSelected(int position) {
        viewerFragment.setImage(images[position]);
    }
}





* 결과 

실행해보면 버튼을 클릭할 때마다 이미지가 바뀌게 된다.



안드로이드 프래그먼트 이해하기






* 프래그먼트(Fragment) 이해


 동일한 레이아웃을 한 번만 정의하고, 여러군데에서 재사용할 수 있도록 하는 것이 '프래그먼트' 이다.

프래그먼트는 태블릿처럼 큰 화면의 단말을 지원하려고 시작했는데, 지금은 단말의 크기와 상관없이 화면 UI를 만들 때 많이 사용된다.

프래그먼트는 하나의 화면 안에 들어가는 부분 화면과 비슷한데, 그런 의미에서는 레이아웃처럼 보이나, 실제론 액티비티처럼 독립적으로 동작하는 부분 화면이다.

프래그먼트의 사용 목적은 분할된 화면들을 독립적으로 구성하기 위해 사용하며,
분할된 화면들의 상태를 관리하기 위해 사용한다.

액티비티의 레이아웃은 시스템이 관리하지만, 프래그먼트에서 사용되는 레이아웃은 단순한 부분화면으로서 액티비티의 구성요소일 뿐이다.

프래그먼트는 항상 액티비티 위에 올라가 있어야 제대로 동작하게 된다. 
액티비티로 만든 화면을 분할한 뒤 각각의 부분 화면을 프래그먼트로 만들고 그 프래그먼트를 독립적으로 관리하는 것이 목표이기 때문에 프래그먼트는 액티비티 위에 올라가 있어야 프래그먼트로서의 역할을 할 수 있다.

즉 프래그먼트가 메모리에 올라간 시점이 아닌 액티비티에 올라가는 시점이 프래그먼트가 제대로 동작하는 시점이라고 할 수 있다.

프래그먼트로 구성된 대표적인 위젯으로 뷰페이저와 바로가기 메뉴가 있다.


* 프래그먼트가 동작하는 방식

 프래그먼트는 액티비티의 동작방식을 본 떠 만들었다. 
액티비티 동작방식은 액티비티 매니저가 액티비티의 동작 순서나 처리 방식을 결정한다. 
또한 액티비티가 시스템에서 관리되기 때문에 시스템이 이해하는 형식으로 명령이나 데이터를 전달하는 역할을 인텐트가 한다. 
즉, 액티비티를 관리하는 시스템 객체는 액티비티 매니저이며, 이 액티비티 매니저에 의해 액티비티가 독립적으로 동작할 수 있게 된다.

프래그먼트는 이러한 액티비티 동작방식과 유사하게 만들어졌다.
액티비티 동작방식에서 시스템이 하는 역할을 액티비티가 하게 되고, 프래그먼트를 관리하는 프래그먼트 매니저가 있다.

고로 액티비티가 시스템 역할을 하므로 프래그먼트는 액티비티 위에 올라가 있지 않으면 정상동작할 수 없다.

인텐트가 하던 역할은 프래그먼트에서는 사용할 수 없다. 왜냐하면 인텐트는 시스템에서 이해하는 객체인데 그것을 프래그먼트와 액티비티 사이에서 사용할 수 없기 때문이다.
액티비티와 프래그먼트 간에 데이터를 전달할 때는 단순히 메서드를 만들고 메서드를 호출하는 방식을 사용한다.

하나의 액티비티에서 프래그먼트만 전환하게 하면 가벼운 화면 전환 효과를 얻을 수 있다. 

또한 프래그먼트도 액티비티를 만들 때의 과정과 비슷하게, 하나의 XML 레이아웃과 하나의 자바소스 파일로 동작하게 된다.


* 액티비티에 프래그먼트 넣고 프래그먼트->프래그먼트로 교체해보기

 아래 이미지의 경로로 진입하여 MainFragment와 MenuFragment 2개를 생성한다.


[File] -> [Fragment] -> [Fragment (blank)] 클릭




메인 액티비티 레이아웃에 위와 같이 태그로 새로 만든 fragment를 추가한다.
id는 mainFragment로 지정한다.
name은 MainFragment.java의 패키지 경로를 포함하여 이름까지만 입력해야 한다.



메인프래그먼트 레이아웃은 텍스트뷰와 버튼을 하나 만들었고,
버튼을 클릭했을 때 메뉴화면(메뉴프래그먼트)로 프래그먼트 전환이 일어나도록 할 것이다.

* MainFragment.java


public class MainFragment extends Fragment {
    @Override
    // 프래그먼트와 관련되는 뷰 계층을 만들어서 리턴한다.
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // rootView : 인플레이션을 통해 참조한 최상위 레이아웃
        // 최상위 레이아웃은 메인 프래그먼트 안에 들어있고,
        // 메인 프래그먼트는 이 레이아웃을 화면에 보여주기 위한 틀이다.
        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_main,
                container, false);
        // 따라서 rootView를 통해 findViewBytId()을 이용하여 버튼 객체를 참조한다.
        Button button = rootView.findViewById(R.id.button);
        // 버튼을 클릭했을 때 메뉴프래그먼트 화면으로 전환이 되어야 한다.
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 메인 액티비티 객체를 참조한다.
                MainActivity activity = (MainActivity) getActivity();
                // onFragmentChanged 메소드는 메인액티비티에 존재하는 메서드이다.
                // 프래그먼트 매니저를 이용해 프래그먼트를 전환하는 메서드이다.
                // 하나의 프래그먼트에서 다른 프래그먼트를 띄우려면 액티비티를 통해 띄어야 하므로
                // 메인 액티비티 코드 쪽에 아래의 메서드가 위치해야한다.
                activity.onFragmentChanged(0);
            }
        });

        return rootView;
    }
}

* MainActivity.java


public class MainActivity extends AppCompatActivity {

    MainFragment mainFragment;
    MenuFragment menuFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //프래그먼트는 뷰가 아니라서 바로 찾을 수 없고 프래그먼트매니저를 통해 객체를 참조할 수 있다.
        mainFragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.mainFragment);
        menuFragment = new MenuFragment();
    }

    //실제로 액티비티에 프래그먼트를 추가하고 프래그먼트로 동작하게 되는 메소드
    //프래그먼트는 액티비티가 관리하여야 하므로 메서드 기능상 MainActivity.java 쪽에 있는 것이 낫다.
    public void onFragmentChanged(int index){
        //index가 0이면 메뉴프래그먼트로 교체함
        if(index == 0){
            //트랜젝션을 시작하고 프래그먼트를 교체한 후에 commit()을 호출하여 실제로 트랜젝션이 실행된다.
            //프래그먼트 화면 교체 중에 오류가 발생하면 트랜젝션 이전의 상태로 되돌리게 하기 위함이다.
            getSupportFragmentManager().beginTransaction().replace(R.id.container, menuFragment).commit();
        //index가 1이면 메인프래그먼트로 교체함
        }else if(index == 1){
            getSupportFragmentManager().beginTransaction().replace(R.id.container, mainFragment).commit();
        }
    }
}

* MenuFragment.java


public class MenuFragment extends Fragment {


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // fragment_menu.xml 레이아웃 파일의 내용이 인플레이션 되어야 하므로 아래와 같이 입력
        return inflater.inflate(R.layout.fragment_menu, container, false);
    }

}



* 프래그먼트의 수명주기

 프래그먼트들도 액티비티와 같이 수명주기가 있다. 
프래그먼트들의 상태를 관리하는 것이 필요하기 때문이다.

- 액티비티에 프래그먼트 추가시
onAttach -> onCreate -> onCreateView -> onActivityCreated -> onStart -> onResume

  • onAttach(Activity) : 프래그먼트가 액티비티와 연결할 때 호출됨
  • onCreate(Bundle) : 프래그먼트가 초기화 할 때 호출됨. new 연산자를 이용해 새로운 프래그먼트 객체를 만드는 시점이 아니라는 것에 주의
  • onCreateView(LayoutInflator, ViewGroup,Bundle) : 프래그먼트와 관련되는 뷰 계층을 만들어서 리턴함
  • onActivityCreated(Bundle) : 프래그먼트와 연결된 액티비티가 onCreate() 메서드의 작업을 완료했을 때 호출함
  • onStart() : 프래그먼트와 연결된 액티비티가 onStart()되어 사용자에게 프래그먼트가 보일 때 호출 됨
  • onResume() : 프래그먼트와 연결된 액티비티가 onResume()되어 사용자와 상호작용할 수 있을 때 호출됨

onAttach() 메서드가 호출될 때 파라미터로 전달되는 액티비티 객체 위에 프래그먼트가 올라가 있게 된다. 그러므로 액티비티를 위해 설정해야 하는 정보들은 이 onAttach() 메서드에서 처리해야 한다.


- 액티비티에 프래그먼트 제거시
onPause -> onStop -> onDestroyView -> onDestroy -> onDetach

  • onPause : 프래그먼트와 연결된 액티비티가 onPause()되어 사용자와 상호작용을 중지할 때 호출됨
  • onStop : 프래그먼트와 연결된 액티비티가 onStop()되어 화면에서 더 이상 보이지 않을 때나 프래그먼트의 기능이 중지되었을 때 호출됨
  • onDestroyView : 프래그먼트와 관련된 뷰 리소스를 해제할 수 있도록 호출됨
  • onDestroy : 프래그먼트의 상태를 마지막으로 정리할 수 있도록 호출됨
  • onDetach : 프래그먼트가 액티비티와 연결을 끊기 바로 전에 호출됨

MenuFragment fragment = new MenuFragment();
// 프래그먼트 객체는 만들어졌지만 프래그먼트로 동작하지는 않음
getSupportFragmentManager().beginTransaction().add(fragment).commit();
// 액티비티에 추가된 후 프래그먼트로 동작함

2019년 10월 23일 수요일

안드로이드 화면 전환하기






1. Layout Inflation (레이아웃 인플레이션)

앱이 실행될 때 XML 레이아웃의 내용이 메모리에 객체화되고 객체화된 XML 레이아웃을 소스 파일에서 사용한다.
XML 레이아웃의 내용이 메모리에 객체화 되는 과정을 Inflation(인플레이션)이라고 한다.



* setContentView() : 레이아웃을 메모리에 객체화 하는 메소드

- 인자로 R.layout.activity_main 과 같이 받아서, 해당 레이아웃 XML 파일을 메모리에 객체화한다.
- 화면 전체(메인 화면)에 나타낼 뷰를 지정할 수 있다.
- 화면의 일부분을 차지하는 부분 레이아웃을 객체화할 수는 없다.
- 부분 레이아웃을 메모리에 객체화 하려면 인플레이터를 사용해야 한다.





2. LayoutInflater (부분 화면) 클래스 사용하기

안드로이드는 부분 화면을 객체화 하여 화면에 띄우기 위해 시스템 서비스로 LayoutInflater 라는 클래스를 제공한다.
getSystemService() 메서드를 사용하여 LayoutInflater 객체를 참조할 수 있다.
cf) 시스템 서비스는 단말이 시작되면서 항상 실행되는 서비스이다.
혹은 LayoutInflater.from() 를 이용하여 LayoutInflater 객체를 참조할 수도 있다.



cf) 프로젝트에서 액티비티 추가하기
[app 우클릭] -> [New] -> [Activity] -> [Empty Activity] 



Activity Name을 MenuActivity 라고 입력하고 Finish 클릭


MenuActivity로 실습을 할것이므로 Manifest 파일에 첫 실행 액티비티를 위처럼 수정한다.






public class MenuActivity extends AppCompatActivity {
    
    LinearLayout container;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_menu);
        
        container = findViewById(R.id.container);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            /**
             * 버튼을 클릭했을 때 부분화면을 전체 레이아웃의 리니어 레이아웃에 추가한다.
             * @param v
             */
            @Override
            public void onClick(View v){
                LayoutInflater inflater = (LayoutInflater) getSystemService(
                        Context.LAYOUT_INFLATER_SERVICE);
                // 부분 레이아웃(sub1.xml)을 메모리에 객체화
                // container에 부분 레이아웃을 붙인다.
                inflater.inflate(R.layout.sub1, container, true);
                // 그 후 container에서 부분 레이아웃의 요소를 참조할 수 있게 된다.
                CheckBox checkBox = container.findViewById(R.id.checkBox);
                checkBox.setText("로딩되었어요.");
            }
        });
    }
}





sub1.xml




activity_menu.xml




추가하기 버튼을 클릭하면 부분화면이 아래에 추가된다.





3. 여러 화면 만들고 화면간 전환하기


프로젝트에 MenuActivity를 새로 추가하면 Manifest 파일에 자동으로 추가된다.
label 속성은 화면의 제목, theme 는 대화상자 형태로 액티비티를 설정한다.



activity_menu.xml
단순하게 버튼 한개만 추가한 형태이다.
이 화면은 대화상자 형태로 표시 될 것이다.


activity_main.xml
메인 액티비티 역시 버튼 하나로 구성되었다.

메인 액티비티에서 버튼을 클릭하면, 메뉴 액티비티를 띄울 것이고
메뉴 액티비티에는 돌아가기 버튼이 있어 클릭 시 메인 액티비티로 돌아가게 된다.




MainActivity를 작성하기 앞서 부모 클래스들에게서
상속받은 메서드들 중 하나인 onActivityResult()를 Override 하여 구현한다.
새로 띄운 액티비티로부터 응답을 처리 하기 위한 메소드 이다. 


* MainActivity.java


public class MainActivity extends AppCompatActivity {
    // 새 액티비티를 띄울 때 보낼 코드
    public static final int REQUEST_CODE_MENU = 101;

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

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            /**
             * 버튼을 클릭했을 때 새로운 액티비티(MenuActivity)를 띄운다.
             * @param v
             */
            @Override
            public void onClick(View v) {
                // 액티비티를 띄우기 위해 인텐트 객체를 사용한다.
                // 또한 액티비티간에 데이터를 전달할 수 있다.
                // 이벤트 처리 메서드 안에서 this 변수로 MainActivity 객체를 참조할 수 없으므로
                // getApplicationContext() 를 사용하였다.
                // Intent 생성자 인자로 MenuActivity.class를 넣음으로써 이와 연결된다.
                Intent intent = new Intent(getApplicationContext(), MenuActivity.class);
                // startActivityForResult 는 새 액티비티로부터 응답을 받을 수 있다.
                startActivityForResult(intent, REQUEST_CODE_MENU);
            }
        });
    }

    /**
     * 새로 띄었던 액티비티가 응답을 보내오면 그 응답을 처리한다.
     * @param requestCode : 액티비티를 띄울 때 전달했던 요청 코드
     * @param resultCode : 새 액티비티로부터 전달된 응답코드 (새 액티비티에서 처리한 결과가 정상인지 여부 판단)
     * @param data : 새 액티비티로부터 받은 인텐트
     */
    @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 호출됨. 요청 코드 : " + requestCode +
                            ", 결과 코드 : " + resultCode, Toast.LENGTH_LONG ).show();
            if(resultCode == RESULT_OK){
                //인텐트로부터 데이터 꺼내오기 
                String name = data.getStringExtra("name");
                Toast.makeText(getApplicationContext(), "응답으로 전달된 name : " + name, Toast.LENGTH_LONG).show();
            }
        }
    }
}


* 새로 띄울 액티비티(MenuActivity.java)


public class MenuActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_menu);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                // 인텐트 객체 생성
                Intent intent = new Intent();
                // name 값을 부가 데이터로 넣기
                intent.putExtra("name", "mike~");
                // 새로 띄운 액티비티에서 이전 액티비티로 인텐트를 전달하기
                setResult(RESULT_OK, intent);
                // 현재 액티비티 화면에서 없애기
                finish();
            }
        });
    }
}


버튼을 클릭하면 다이얼로그 방식의 MenuActivity가 보여진다.


메인 액티비티로 돌아가게 되면 응답을 처리하는 메서드가 호출되며
요청, 결과 코드를 확인할 수 있다.
Toast가 두개여서 나중것이 가려졌는데 Toast 한개를 주석처리하면 확인 할 수 있다.





4. Intent (인텐트) 사용하기

인텐트는 앱 구성 요소 간에 작업 수행을 위한 정보를 전달하는 역할을 한다.


* 인텐트의 기본 구성 요소 : 액션(Action) , 데이터(Data)

액션은 수행할 기능이며 데이터는 수행될 대상의 데이터를 의미한다.

액션과 데이터를 이용해 인텐트 객체를 생성하고 필요한 액티비티를 띄어줄 수 있다.

* 인텐트 종류 : 명시적 인텐트, 암시적 인텐트
명시적 인텐트는 클래스 객체나 컴포넌트 이름을 지정하여 호출할 대상을 확실히 아는 경우를 말한다.
암시적 인텐트는 액션과 데이터를 지정하였으나 호출할 대상이 달라질 수 있는 경우를 말한다.

- 암시적 인텐트의 여러가지 속성


  • 범주(Category) : 액션이 실행되는데 필요한 추가적 정보
  • 타입(Type) : 인텐트에 들어가는 데이터의 MIME 타입을 명시적으로 지정
  • 컴포넌트(Component) : 인텐트에 사용될 컴포넌트 클래스 명을 명시
  • 부가 데이터(Extra Data) : 추가적인 정보가 들어있는 번들 데이터를 전달


아래 코드는 인텐트에 액션과 데이터를 넣어 다른 앱의 액티비티를 띄우는 코드이다.


단순히 입력상자와 버튼을 만들었다.
입력상자에는 전화번호 포맷 형식의 데이터를 입력한다.



public class MainActivity extends AppCompatActivity {

    EditText editText;

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

        editText = findViewById(R.id.editText);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //입력 상자에 입력된 전화번호 가져오기
                String data = editText.getText().toString();
                //전화 걸기 화면을 보여줄 인텐트 객체 생성
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(data));
                //액티비티 띄우기 (액티비티 메모리에 올리기)
                startActivity(intent);
            }
        });
    }
}


전화걸기 버튼을 클릭했을 때,
자동으로 전화번호 앱이 실행되는 것을 확인할 수 있다.


아래 두번째 코드는 컴포넌트 이름을 이용해 새로운 액티비티를 띄우는 경우이다.
위 화면 레이아웃에서 id가 button2인 버튼을 하나더 추가하고, 새로운 액티비티(MenuActivity)를 하나 생성한다.


Button button2 = findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                //컴포넌트 이름을 지정할 수 있는 객체 생성
                //ComponentName 생성자의 인자로 순서대로 패키지명과, 클래스 명이 된다.
                ComponentName name = new ComponentName("org.techtown.samplecallintent",
                        "org.techtown.samplecallintent.MenuActivity");
                // 인텐트 객체에 컴포넌트를 지정한다.
                intent.setComponent(name);
                startActivityForResult(intent,101);
            }
        });


컴포넌트이름을 지정할 때 클래스 명은 앞에 패키지 경로까지 전부다 적어주어야 한다.
인텐트를 이용해 다른 화면을 띄울 수가 있는데 직접 만든 화면이나, 다른 사람이 만든 앱의 화면을 띄울 수도 있다.





5. 플래그와 부가 데이터 사용하기

startActivity 또는 startActivityForResult 메서드를 여러 번 호출 하게 될 경우 메모리에 여러 개가 중복되어 만들어질 것이다.
따라서 중복된 액티비티는 띄우지 않게 하기 위해 플래그를 사용한다.

액티비티가 만들어질 때마다 액티비티 스택이라는 곳에 차곡차곡 쌓이게 되는데, startActivity 또는 startActivityForResult를 호출하게 되면 이전의 액티비티는 액티비티 스택에 저장되고 새로 만들어진 액티비티가 화면에 보이게 된다.
 즉 가장 상위에 쌓인 것부터 화면에 보여지면서 back 시스템 버튼을 눌렀을 때 그 아래의 액티비티가 보여지게 된다.

동일한 액티비티들을 여러 번 실행한다면 동일한 액티비티가 스택에 여러 개 들어가게 되는 문제를 해결해주는 것이 플래그이다.

* 대표적인 플래그들
  • FLAG_ACTIVITY_SINGLE_TOP : 이미 생성된 액티비티가 있다면 그 액티비티를 그대로 사용하라는 플래그. 기존에 사용하는 액티비티에서 인텐트 객체만 전달받으려면 onNewIntent()를 오버라이드하면 된다.
  • FLAG_ACTIVITY_NO_HISTORY : 처음 이후에 실행된 액티비티는 액티비티 스택에 쌓이지 않는다.
  • FLAG_ACTIVITY_CLEAR_TOP : 이것이 설정되어 있는 액티비티 위에 있는 다른 액티비티를 모두 종료하게 된다. 홈 화면과 같이 항상 우선하는 액티비티를 만들때 유용함.



* 인텐트에 객체를 직렬화하여 데이터 보내기

객체를 직렬화 하기 위해서는 데이터를 담고 있는 클래스는 Parcelable 인터페이스를 구현해야한다.
자바의 Serializable 직렬화와 동일한 개념이다.

* Parcelable 직렬화 필수 구현
  1. CREATOR 상수 정의
  2. describeContents() 구현
  3. writeToParcel(Parcel dest, int flags) 구현



import android.os.Parcel;
import android.os.Parcelable;

/*
    안드로이드에서의 직렬화는 Parcelable 인터페이스를 구현함으로써 사용할 수 있다.
 */
public class SimpleData implements Parcelable {

    int number;
    String message;

    public SimpleData(int num, String msg){
        number = num;
        message = msg;
    }

    /**
     * SimpleData의 생성자의 인자로 Parcel 객체를 받아들이는데
     * Parcel 객체에서 데이터를 읽어들이는 역할을 한다.
     * @param src
     */
    public SimpleData(Parcel src){
        number = src.readInt();
        message = src.readString();
    }

    /**
     * CREATOR 상수를 정의해야한다.
     * Parcel 객체를 읽어들이고, 객체를 생성하는 역할
     * 반드시 static final 로 선언해야 한다.
     */
    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {

        public SimpleData createFromParcel(Parcel in){
            return new SimpleData(in);
        }

        public SimpleData[] newArray(int size){
            return new SimpleData[size];
        }

    };

    /**
     * 필수 구현
     * @return
     */
    public int describeContents(){
        return 0;
    }

    /**
     * 필수 구현
     * SimpleData 객체에 있는 데이터를 Parcel 객체로 만드는 역할을 한다.
     * @param dest
     * @param flags
     */
    public void writeToParcel(Parcel dest, int flags){
        dest.writeInt(number);
        dest.writeString(message);
    }

}



public class MainActivity extends AppCompatActivity {

    public static final int REQUEST_CODE_MENU = 101;
    public static final String KEY_SIMPLE_DATA = "data";

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

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getApplicationContext(), MenuActivity.class);
                // 객체를 생성
                SimpleData data = new SimpleData(100, "Hello Android!!");
                // 인텐트에 객체를 보낸다.
                intent.putExtra(KEY_SIMPLE_DATA, data);
                startActivityForResult(intent, REQUEST_CODE_MENU);
            }
        });
    }
}



public class MenuActivity extends AppCompatActivity {

    TextView textView;

    public static final String KEY_SIMPLE_DATA = "data";

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

        textView = findViewById(R.id.textView);
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.putExtra("name", "mike");
                setResult(RESULT_OK, intent);

                finish();

            }
        });
        /**
         * 메인 액티비티가 전달한 인텐트를 참조하기 위해 onCreate()안에서 getIntent()를 호출
         */
        Intent intent = getIntent();
        processIntent(intent);

    }

    public void processIntent(Intent intent){
        if(intent != null){
            //Bundle 객체를 반환한다.
            Bundle bundle = intent.getExtras();
            //Bundle 객체에서 키를 이용하여
            //getParcelable메서드를 호출하면 SimpleData 객체을 얻어낼 수 있다.
            SimpleData data = bundle.getParcelable(KEY_SIMPLE_DATA);

            textView.setText("전달 받은 데이터\nNumber: "+data.number +
                    "\nMessage:"+data.message);

        }
    }
}





6. 태스크(Task) 관리 이해하기

태스크는 앱이 어떻게 동작할지 결정하는데 사용된다.
태스크를 이용하면 프로세스처럼 독립적인 실행 단위와 상관없이 어떤 화면들이 같이 동작해야 하는지 흐름을 관리할 수 있다.

프로세스끼리는 정보를 공유할 수가 없다.
따라서 하나의 프로세스에서 다른 프로세스의 화면을 띄우려면 시스템의 도움이 필요하다. 
시스템에서 이런 액티비티의 각종 정보를 저장해두기 위해 태스크라는 것을 만든다.

시스템에서는 액티비티들의 정보를 저장해두기 위해 태스트라는 것을 만들고 알아서 관리하지만 직접 제어할 수도 있다.

AndroidManifest.xml 에 액티비티를 등록할 때 태스크도 함께 설정할 수 있다.


public class MainActivity extends AppCompatActivity {

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

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                //나 자신을 띄운다.
                Intent intent = new Intent(getApplicationContext(), MainActivity.class);
                startActivity(intent);

            }
        });
    }
}

위 코드는 버튼을 클릭할 때마다 자기자신의 액티비티를 스택에 넣는다. 이것은 AndroidManifest.xml 파일에서 activity 태그의 속성에서 android:launchMode="standard" 을 넣어주는 것과 같다.

만약 android:launchMode="singleTop" 으로 설정한다면 태스크의 가장 위쪽에 있는 액티비티는 더 이상 새로이 만들지 않게된다.
이는 5번의 플래그의 FLAG_ACTIVITY_SINGLE_TOP 와 같은 효과이다.
따라서 중복되어 액티비티가 생성되지 않으므로 (onCreate메서드가 실행되지 않음)
인텐트는 onNewIntent()메서드로 전달받으면 된다.

singleTask : 액티비티가 실행되는 시점에 새로운 태스크를 만들게 된다.
singleInstance: 액티비티가 실행되는 시점에 새로운 태스크를 만들면서, 그 이후에 실행되는 액티비티들은 이 태스크를 공유하지 않는다.





7. 액티비티의 수명주기와 SharedPreferences 이해하기


onCreate -> onStart -> onResume -> onPause ->onStop -> onDestroy


앱이 갑자기 중지되어 앱 데이터의 저장과 복원이 필요할 때가 있다.
간단한 데이터를 저장하거나 복원시에는 SharedPreferences 를 사용할 수 있다.


아래 코드는 앱을 실행했을 때 입력상자에 사람 이름을 입력한 상태에서, 앱을 종료한 후 다시 실행했을 때 사람 이름이 그대로 보이도록 만드는 내용이다.

앱이 갑자기 중지 될때 항상 호출되는 메서드는 onPause이다.
앱이 시작될 때 항상 호출되는 메서드는 onResume이다.
즉 중지 시에 데이터를 저장하고 시작할 때 데이터를 복원하면 된다. 




public class MainActivity extends AppCompatActivity {

    EditText nameInput;

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

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getApplicationContext(), MenuActivity.class);
                startActivity(intent);
            }
        });
        Toast.makeText(this, "onCreate 호출됨.",Toast.LENGTH_LONG).show();
        nameInput = findViewById(R.id.nameInput);
        
    }

    @Override
    protected void onStart() {
        super.onStart();
        println("onStart 호출됨.");
    }

    @Override
    protected void onStop() {
        super.onStop();
        println("onStop 호출됨.");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        println("onDestroy 호출됨.");
    }

    public void println(String data){
        Toast.makeText(this, data, Toast.LENGTH_LONG).show();
        //Logcat에 로그 출력하기
        Log.d("Main", data);
    }

    @Override
    protected void onPause() {
        super.onPause();
        Toast.makeText(this, "onPause 호출됨", Toast.LENGTH_LONG).show();
        //현재 입력상자에 입력된 데이터를 저장한다.
        saveState();
    }

    @Override
    protected void onResume() {
        super.onResume();
        Toast.makeText(this, "onPause 호출됨", Toast.LENGTH_LONG).show();
        //설정 정보에 저장된 데이터를 복원한다.
        restoreState();
    }

    protected  void restoreState(){
        SharedPreferences pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
        if( (pref != null) && (pref.contains("name"))){
            String name = pref.getString("name","");
            nameInput.setText(name);
        }
    }

    protected void saveState(){
        SharedPreferences pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
        SharedPreferences.Editor editor = pref.edit();
        editor.putString("name", nameInput.getText().toString());
        editor.commit(); //커밋을 해야 데이터가 저장이 된다.
    }

    protected void clearState(){
        SharedPreferences pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
        SharedPreferences.Editor editor = pref.edit();
        editor.clear();
        editor.commit();
    }
}













2019년 10월 22일 화요일

안드로이드 이벤트 처리





1. 터치 이벤트, 제스처 이벤트

터치 이벤트는 화면을 손가락으로 누를 때 발생하는 이벤트들을 처리할 수 있다.
제스처 이벤트는 터치 이벤트 중에서 일정한 패턴, 스크롤과 같은 이벤트를 처리할 수가 있다.
제스처 이벤트는 터치 이벤트를 받은 후에 추가적인 확인을 거쳐 만들어진다.




package org.techtown.sampleevent;

import androidx.appcompat.app.AppCompatActivity;

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

public class MainActivity extends AppCompatActivity {

    TextView textView;
    GestureDetector detector;

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

        textView = findViewById(R.id.textView);

        View view = findViewById(R.id.view);
        /**
         * 뷰에 OnTouchListener 를 등록한다.
         */
        view.setOnTouchListener(new View.OnTouchListener(){
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent){
                // 액션의 상태 값을 가져오는 메소드
                int action = motionEvent.getAction();
                // 현재 X, Y 좌표의 위치를 가져옴
                float curX = motionEvent.getX();
                float curY = motionEvent.getY();

                if(action == MotionEvent.ACTION_DOWN) {
                    println("손가락 눌림 : "+ curX +" , " + curY);
                }else if( action == MotionEvent.ACTION_MOVE) {
                    println("손가락 움직임 : " + curX + " , " +  curY);
                }else if(action == MotionEvent.ACTION_UP) {
                    println("손가락 뗌 : " + curX + " , " + curY);
                }
                return true;
            }
        });

        //제스쳐 이벤트를 생성
        detector = new GestureDetector(this, new GestureDetector.OnGestureListener() {
            @Override
            public boolean onDown(MotionEvent e) {
                println("onDown() 호출됨.");
                return true;
            }

            @Override
            public void onShowPress(MotionEvent e) {
                println("onShowPress() 호출됨.");
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                println("onSingleTapUp() 호출됨.");
                return true;
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                println("onScroll() 호출됨 : "+ distanceX + " , " + distanceY);
                return true;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                println("onLongPress() 호출됨.");
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                println("onFling() 호출됨 : "+ velocityX + ", " + velocityY);
                return true;
            }
        });
        /**
         * 두번째 뷰에다 제스쳐이벤트를 설정
         */
        View view2 = findViewById(R.id.view2);
        view2.setOnTouchListener(new View.OnTouchListener(){
                @Override
                public boolean onTouch(View view, MotionEvent motionEvent ){
                    // 뷰를 터치했을때 발생하는 터치이벤트를 제스터 디렉터로 전달한다.
                    detector.onTouchEvent(motionEvent);
                    return true;
                }
        });
    }

    public void println(String data) {
        textView.append(data + "\n");
    }

}



스크롤뷰 -> 리니어레이아웃 -> 텍스트뷰에서 이벤트 정보를 찍어보기로 한다.


파란 부분이 첫번째 뷰, 주황 부분이 두번째 뷰이다.
이벤트가 발생할때마다 아래의 스크롤뷰에 수직으로 데이터들이 쌓인다.





2. 키 이벤트 처리하기

키 입력은 시스템 버튼이 눌렸거나, 문자열 키가 입력된 경우에 관하여 이벤트 처리를 할 수 있다.


- 시스템 [back] 버튼 눌렸을 때 토스트 메시지 생성하기
시스템 버튼인 back은 onKeyDown 메서드를 재정의 하여 간단히 이벤트 처리할 수 있다.


MainActivity.java 에서 우클릭 한 후 [Generate] 선택
[Override Methods] 선택


[onKeyDown] 메소드를 선택하고 [OK] 하면 메소드가 자동으로 생성된다.





@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        
        if(keyCode == KeyEvent.KEYCODE_BACK){
            Toast.makeText(this, "시스템 [back] 버튼이 눌렸습니다.",
                    Toast.LENGTH_LONG).show();
            return true;
        }
        return false;
    }





3. 단말 방향 전환 시 이벤트 처리

단말의 방향 전환 시에는 앱화면에 대하여 가로, 세로 각각 XML 레이아웃을 만들 필요가 있다.
단말의 방향이 바뀌었을 때는 액티비티는 메모리에서 없어졌다가 다시 만들어지게 된다.


/app/res/layout/ : 단말 세로 방향
/app/res/layout-land/ : 단말 가로 방향


[res] 폴더에서 우클릭하여 [New] -> [Android Resource Directory] 클릭


Directory name 을 layout-land 로 입력하고 OK.


Project Files 구조로 볼때만 layout-land 폴더가 보이게 된다.
생성된 것을 확인.


layout 폴더와 layout-land 는 같은 역할을 하지만 단말이 가로방향으로 보일 때는 layout-land 폴더 안에 있는 XML 레이아웃 파일이 사용된다.
layout-land 폴더가 없다면 어떤 방향이든 layout 폴더의 XML 파일을 디폴트로 사용하게 된다.


단말의 방향이 바뀌었을 때는 액티비티는 메모리에서 없어졌다가 다시 만들어지게 된다면, 이전에 선언해 두었던 변수 값이 사라지므로 이를 유지하도록 해야한다.

이를 위해 onSaveInstanceState 콜백 메서드가 제공된다.
이 메서드는 액티비티가 종료되기 전의 상태를 저장한다.
이때 저장한 상태는 onCreate() 메서드가 호출될 때 전달되는 번들 객체로 복원할 수 있다.




public class MainActivity extends AppCompatActivity {
    String name;

    EditText editText;

    /**
     * onCreate의 파라미터를 보면 Bundle 객체인 savedInstanceState를 인자로 받게 되어 있다.
     * 이 객체에서 데이터를 가져와서 name 변수에 다시 할당하게 되어 복구되는 것이다.
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        showToast("onCreate 호출됨.");

        editText = findViewById(R.id.editText);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view){
                // 버튼을 클릭했을 때 사용자가 입력한 값을 name 변수에 저장
                name = editText.getText().toString();
                showToast("입력된 값을 변수에 저장하였습니다 : "+ name);
            }
        });
        // 이 화면이 초기화 될 때 name 변수값 복원
        // ex) 단말 방향 전환시 액티비티가 메모리에서 제거되는 상황
        if(savedInstanceState != null){
            name = savedInstanceState.getString("name");
            showToast("값을 복원하였습니다 : "+name);
        }

    }
    /**
     * onSaveInstanceState 메소드는
     * 액티비티가 종료되기 전의 상태를 저장한다.
     * @param outState
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("name", name);
    }

    @Override
    protected void onStart() {
        super.onStart();
        showToast("onStart 호출됨.");
    }

    @Override
    protected void onStop() {
        super.onStop();
        showToast("onStop 호출됨.");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        showToast("onDestroy 호출됨.");
    }

    public void showToast(String data){
        Toast.makeText(this, data, Toast.LENGTH_LONG).show();
    }

}





4. 단말 방향이 바뀔 때 액티비티 유지하는 방법

- /app/manifests/AndroidManifest.xml 수정

아래와 같이 빨간네모상자의 속성을 설정하면, 시스템은 단말의 방향이 바뀌는 시점에 액티비티에게 상태 변화를 알려줄 수 있게 된다. 
keyboardHidden 값은 키패드가 자동으로 나타나지 않도록 하는 설정이다.


activity 태그가 액티비티를 등록할 때 사용하는 태그이다.
configChanges 속성을 위와 같이 추가한다.





public class MainActivity extends AppCompatActivity {

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

    /**
     * 액티비티에서 방향 전환을 인식할 수 있게 되어,
     * 단말의 방향이 바뀌는 시점에 자동으로 이 메서드가 호출된다.
     * @param newConfig
     */
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            showToast("가로 방향: ORIENTATION_LANDSCAPE");
        }else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
            showToast("세로 방향: ORIENTATION_PORTRAIT");
        }
    }
    public void showToast(String data){
        Toast.makeText(this, data, Toast.LENGTH_LONG).show();
    }

}




5. 단말 방향을 고정시키기

- /app/manifests/AndroidManifest.xml 수정


screenOrientation 속성에서 landscape로 설정하면 화면이 가로로 고정된다.




2019년 10월 21일 월요일

안드로이드 위젯 기초





1. String 문자열 파일 xml로 관리하기

다국어 지원을 하거나, 문자열의 관리를 쉽게 한다.



/app/res/values/string.xml 에 문자열을 정의함.




@string/... 과 같은 형식으로 참조한다.


cf) 다국어 문자열 파일 관리 방법

Android 폴더 보기 형식에서는 아래와 같은 구조를 확인할 수 없으므로 상단에서 Project Files로 변경하면 아래와 같은 구성이 보인다.


values-en, values-ko 처럼 values 뒤에 로케일을 붙여서 폴더를 생성하고
string.xml파일을 각 나라의 언어로 정의하면 된다. 기본 폴더인 values는 삭제하지 않는다.
단말에 설정된 언어에 해당하는 파일이 없는 경우 기본 폴더를 참조하기 때문이다.



2. 텍스트 뷰의 속성

-textColor : 텍스트 색상 설정


#AARRGGBB 의 형태
Alpha: 투명도, FF(투명하지 않음), 88(중간), 00(투명함)
Red: 빨강
Green: 초록
Blue: 파랑

-textStyle : 텍스트 스타일 설정
normal|bold|italic 와 같이 | 파이프라인을 이용하여 여러 속성을 함께 지정할 수 있다.

-typeFace : 문자열의 폰트 설정

-maxLines : 텍스트 뷰에서 표시하는 만주열의 최대 줄 수를 설정


maxLines을 1로 지정하고 한줄을 초과하는 text를 작성해본다.


maxLines이 1이므로 한 줄로 표시된다.

-selectAllOnFocus : true/false 로 포커스를 받을 때 문자열 전체가 선택 여부
-cursorVisible : true/false 로 커서 보임 여부
-autoLink : true/false 로 웹페이지 주소나, 이메일 주소를 링크 색상으로 표시
-lineSpacingMultiplier : 줄 간격을 기본 줄 간격의 배수로 설정
-lineSpacingExtra : 여유 값으로 설정할 때 사용
-capitalize : 글자, 단어, 문장 단위로 대소문자 조절
-ellipsize : 줄임 표시 관련 속성
-editable : 편집 가능 관련 속성



3. 버튼 속성

버튼은 텍스트뷰를 상속하여 정의되어 있으므로 텍스트뷰의 속성을 그대로 사용할 수 있다.









4. 에디트 텍스트 속성

-hint : 기본 안내문의 hint 표시
-inputType : 입력되는 글자의 유형을 정의할 수 있다. 글자를 입력할 때 보이는 키패드의 유형을 정의할 수 있다.



에디트텍스트 속성 추가하기
커서가 깜박이며 아래 글자키패드가 보여진다.





5. 이미지를 해상도에 따라 다르게 보여주기
/app/res 하위에 drawable 디렉토리를 아래와 같이 추가로 생성해주고 알맞은 이미지를 넣는다.


/app/res/drawable : 일반적으로 사용하는 기본 이미지
/app/res/drawable-xhdpi : 초고해상도
/app/res/drawable-hdpi : 고해상도
/app/res/drawable-mdpi : 중간해상도
/app/res/drawable-ldpi : 저해상도





6. 드로어블 만들기


드로어블은 뷰에 설정할 수 있는 객체이며, 그 위에 그래픽을 그릴 수 있다.
드로어블은 XML 파일이나 소스 파일로 작성할 수 있다.
drawable 폴더 안에 이미지가 아닌 XML 파일이 들어가서 이미지처럼 설정된다.



* 상태 드로어블 만들기
res/drawable 폴더를 우클릭 한 후 [Drawable resource file] 을 클릭
file name을 지정하고 OK.


selector 안에 item 요소를 추가한다.
버튼이 눌린 이미지에는 state_pressed를 true로 설정하여
버튼이 눌렸을때 눌린 이미지가 나오도록 설정한다.
그 다음줄에 아무런 상태가 설정되어 있지 않은 item은
버튼에서 손을 땐 기본 상태이며 finger 이미지가 나오게 된다.


그리고 버튼의 background 속성에 @drawable/xml파일명 을 적어서 연결하면
클릭했을때 이미지가 변하는 버튼으로 설정된다.




* 셰이프 드로어블 만들기





shape 로 바꾸고 모양을 사각형으로 설정하고 디자인 요소를 설정한다.
이것을 버튼의 background 에 @drawable/xml파일명으로 설정해주면 된다.


* 배경 그라데이션 드로어블 만들기


그라데이션을 주려면 gradient 속성을 이용하면 된다.



* 드로어블을 이용하여 테두리만 있는 버튼 만들기




layer-list 안에 item 태그를 여러개를 넣을 수 있으며
 그래픽을 중첩시켜서 표현 할 수도 있다.





위에서 작성한 드로어블들이 반영된 모습





7. 토스트, 스낵바, 대화상자 만들기


- 토스트 메시지 위치 변경 시키기


public class MainActivity extends AppCompatActivity {

    EditText editText;
    EditText editText2;

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

        editText = findViewById(R.id.editText);
        editText2 = findViewById(R.id.editText2);

    }

    public void onButton1Clicked(View v){
        try{
            Toast toastView = Toast.makeText(this, "위치가 바뀐 토스트 메시지 입니다.",
                    Toast.LENGTH_LONG);

            //위치값을 에디트텍스트에서 값을 받아와서,
            int xOffset = Integer.parseInt(editText.getText().toString());
            int yOffset = Integer.parseInt(editText2.getText().toString());

            //setGravity() 를 이용하여 토스트 위치를 바꿔준다.
            toastView.setGravity(Gravity.TOP|Gravity.TOP, xOffset, yOffset);
            toastView.show();

        }catch (NumberFormatException e){
            e.printStackTrace();
        }
    }

}


토스트 메시지의 위치가 입력한 위치에 맞춰 나타난다.



-토스트 메시지 모양 바꿔 보여주기


public void onButton2Clicked(View v){
        //LayoutInflater 객체를 이용해 XML 로 정의된 레이아웃을 메모리에 객체화 함
        LayoutInflater inflater = getLayoutInflater();

        //layout 폴더의 toastborder.xml 파일의 toast_layout_root 아이디를 가져옴
        View layout = inflater.inflate(R.layout.toastborder,
                (ViewGroup) findViewById(R.id.toast_layout_root));

        //View 객체에서 아이디가 text 인 텍스트뷰 객체를 가져옴
        TextView text = layout.findViewById(R.id.text);

        Toast toast = new Toast(this);
        text.setText("모양 바꾼 토스트");
        toast.setGravity(Gravity.CENTER, 0, -100);
        toast.setDuration(Toast.LENGTH_SHORT);
        toast.setView(layout);
        toast.show();

}



/app/layout/toastborder.xml


/app/drawable/toast.xml









- Snackbar(스낵바) 보여주기
간단한 메시지를 보여줄 때 토스트 대신 스낵바를 사용하는 경우도 많다.
스낵바는 외부 라이브러리로 추가 되었기 때문에 스낵바가 들어있는 디자인 라이브러리를 프로젝트에 추가해야 사용할 수 있다.

* 외부 라이브러리 추가하기


[File] -> [Project Structure] 클릭


좌측 Dependency 메뉴를 클릭 한 후 app을 선택. (안드로이드 스튜디오 3.5.1버전 화면)
우측에 + 버튼을 클릭하여 Library Dependency 를 선택한다.


스낵바에서 사용하는 외부라이브러리를 검색 하고 버전을 선택 후 OK 클릭


그러면 라이브러리가 추가된 것이 보이고 OK 하면
프로젝트가 재빌드 되면서 해당 라이브러리를 사용할 수 있게 된다. 



public void onButton3Clicked(View v){
       Snackbar.make(v, "스낵바입니다..~", Snackbar.LENGTH_LONG).show();
}


버튼을 클릭했을 때 하단에 스낵바 생성됨


- 알림 대화 상자(Dialog) 보여주기



public class MainActivity extends AppCompatActivity {

    TextView textView;

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

        textView = findViewById(R.id.textView);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                showMessage();
            }
        });
    }
    private void showMessage(){
        //다이얼로그 빌더를 이용하여 다이얼로그 객체를 생성
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        //알림창 제목
        builder.setTitle("안내");
        //알림창 내용
        builder.setMessage("종료하시겠습니까?");
        //아이콘 설정
        builder.setIcon(android.R.drawable.ic_dialog_alert);

        builder.setPositiveButton("예", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                String message = "예 버튼이 눌렸습니다";
                textView.setText(message);
            }
        });

        builder.setNeutralButton("취소", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                String message = "취소 버튼이 눌렸습니다.";
                textView.setText(message);
            }
        });

        builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                String message = "아니오 버튼이 눌렸습니다";
                textView.setText(message);
            }
        });

        //대화 상자 객체를 만든다.
        AlertDialog dialog = builder.create();
        //대화 상자를 화면에 보여준다.
        dialog.show();

    }
}



버튼을 클릭했을때 알림창이 뜬다.





8. 프로그레스바 (상태 진행바) 만들기

어떤 일의 진행 상태를 보여주기 위해 프로그레스바를 만든다.



public class MainActivity extends AppCompatActivity {
    ProgressDialog dialog;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        //프로그레스바에 현재 상태값 설정하기
        ProgressBar progressBar = findViewById(R.id.progressBar);
        //setIndeterminate을 true 로 하게 되면 프로그레스바 진행 움직임 애니메이션이 무한정 나오게 된다. 
        // 이를 false로 하여 80이라는 값을 정지 상태로 보여주기로 한다
        progressBar.setIndeterminate(false);
        progressBar.setProgress(80);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                dialog = new ProgressDialog(MainActivity.this);
                dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
                dialog.setMessage("데이터를 확인하는 중입니다.");

                dialog.show();
            }
        });

        Button button2 = findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                //프로그레스 대화상자 없애기
                if(dialog != null){
                    dialog.dismiss();
                }
            }
        });
    }
}





스피너가 계속 돌아가면서 프로그레스 다이얼로그를 보여준다.
또한 상단에 빨간 선으로 80까지 상태 값이 표시되고 있다.
이 코드에서는 닫기 버튼으로 프로그레스 대화상자를 없앨수 없다.