국비교육(22-23)

68일차(2)/Android App(10) : ListView, OnClickListener, AlertDialog 활용 예제

서리/Seori 2023. 1. 13. 01:56

68일차(2)/Android App(10) : ListView, OnClickListener, AlertDialog 활용 예제

 

 

- xml 레이아웃을 사용하는 setContentView 로 액티비티를 보여준다.

 

- 이 메소드를 활용해서 xml로 만들어진 UI의 참조값을 얻어온다.

- 어떤 UI에 id를 부여하고, 이 id를 활용해서 액티비티에서 참조값을 얻어와서 사용할 수 있다.

 

- 하나의 어플리케이션은 여러개의 activity로 구성될 수 있다.

 

- 하나의 액티비티가 첫 화면으로써 사용자를 대면한다.

- intent filter 가 들어있는 액티비티가 앱이 처음 런칭될 때 실행된다.

 

 

- 액티비티를 이동하고자 한다면?

- 액티비티를 활성화시키는 객체는 intent 객체이다.

- 이 객체를 생성해서 그 안에 어떤 액티비티를 활성화시킬 것인지에 대한 정보를 담고

  startActivity() 메소드에 객체를 전달하면 된다.

 

- 나의 참조값 this. 

- this의 타입은 object, MainActivity이기도 하고 OnClickListener 이기도 하고 AppCompatActivity이기도 하다.

 

MainActivity a=this;
Object b=this;
View.OnClickListener c=this;
Context d=this;

- 클래스 안에서 이런 식으로 작성해보면서 this가 어떤 타입인지 확인해 볼 수 있다.

 

- intent 객체를 전달하는 데에는 인자 context가 필요하다.

 → activity가 context 타입이기 때문에 this를 전달한 것이다.

- Activity의 부모가 Context 라는 것 기억하기!★

 


- 새 모듈 생성

step05listview

 

MainActivity

package com.example.step05listview;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

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

public class MainActivity extends AppCompatActivity
        implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener {

    List<String> names;
    ArrayAdapter<String> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //listView의 참조값
        ListView listview=findViewById(R.id.listView);
        
        //listView에 출력할 sample data
        names=new ArrayList<>();
        names.add("바나나");
        names.add("딸기");
        names.add("복숭아");
        for(int i=0;i<100;i++){
            names.add("아무개"+i);
        }
        //listView 에 연결할 아답타 객체
        //new ArrayAdapter<>( Context , layout resource, 모델 ) 
        adapter=new ArrayAdapter<>(this,
                android.R.layout.simple_list_item_1,
                names);
        //listView에 아답타 연결하기
        listview.setAdapter(adapter);
        //Activity를 아이템 클릭 리스너로 등록하기
        listview.setOnItemClickListener(this);
        listview.setOnItemLongClickListener(this);
    }

    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
        // int i는 클릭한 아이템의 인덱스가 들어있다.
        String name=names.get(i);
        Toast.makeText(this, name, Toast.LENGTH_SHORT).show();
    }

    @Override
    public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
        //오랫동안 클릭한 셀에 출력된 이름 읽어오기
        String name=names.get(i);
        //아래의 익명 클래스에서 참조 가능하도록
        int SelectedIndex=i;

        /*AlertDialog.Builder builder=new AlertDialog.Builder(this);
        builder.setTitle("알림");
        builder.setMessage(name+" 을 삭제 하시 겠습니까?");
        builder.setNegativeButton("아니요", null);
        builder.setPositiveButton("네", null);
        AlertDialog dialog=builder.create();
        dialog.show();*/

        new AlertDialog.Builder(this)
                .setTitle("알림")
                .setMessage(name+"을 삭제하시겠습니까?")
                .setNegativeButton("아니요", null)
                .setPositiveButton("네", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        //모델에서 해당 인덱스의 데이터를 삭제한다.
                        names.remove(SelectedIndex);
                        //아답타에 모델에 변화가 생겼다고 알리기
                        adapter.notifyDataSetChanged();
                    }
                })
                .create()
                .show();
        //이벤트 전파를 여기서 막기
        return true;
    }
}

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/listView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

 

- Legacy-ListView 요소 추가해주기. 목록을 보여주는 UI 이다.

 

- 끌어다놓고 이 리스트로 화면 꽉 채우기

- 폭과 높이 모두 0dp, 상하좌우 마진을 0으로 하면 화면 전체를 차지하는 ListView로 만들 수 있다.

 

- 코드로 보면 이렇다. 상하좌우 마진부분을 parent 상태로!

 

- listView라는 아이디를 부여해준다.

- @+id/아이디 라고 적는 것이 규칙이다.

- @ : 리소스, +id : 아이디를 추가하겠다 라는 뜻이다!

 

 

- MainActivity에 리스트 추가하기

 

- ListView의 구조! 데이터(모델)를 listview에 바로 연결할 수는 없다. 

- 중간에 adapter 가 끼어있어야 한다. 이렇게 연결되어 동작한다.

 

 

- 모델을 아답타에 연결해두면 아답타가 listview에 각각의 셀을 나타낼 수 있는 cellView 를 공급해준다.

- 이 각각의 cellview에는 TextView가 들어있다. 문자열 하나를 출력한 것이라고 보면 된다.

- 아답타는 list에 들어있는 문자 데이터를 이용해서 textView에 있는 이름을 하나씩 출력해서 ListView에 공급하는 것이다.

 

- adapter 에서는 데이터를 가공해서 공급한다.

- ListView는 사용자가 볼 수 있는내용을 출력하는 곳인데 출력할 view는 아답타로부터 요청해서 받는다.

 

- 이런 식으로 출력하려면 셀 하나하나의 레이아웃도 필요하다.

- 안드로이드에 간단한 레이아웃은 미리 만들어져있다. 가져다가 사용하면 된다.

 (코드의 simple_list_item_1 부분)

 

 

- 리스트뷰의 참조값을 adapter에 전달한다.

- 2번째 인자: 셀 하나하나의 레이아웃 전달(만들어져있는 레이아웃을 참조)

- 샘플 데이터 names는 adapter의 3번째 인자로 전달되었다.

 

- android.R 에는 개발자를 위해 미리 만들어 놓은 것들이 들어있다.

 

- 해당 레이아웃을 ctrl+클릭하면 이 레이아웃 설정으로 들어갈 수 있는데,

여기에서 레이아웃의 양식을 볼 수 있다.

 

- Model → Adapter → ListView 로 받아서 사용

 

- 이 xml 문서를 하나 전개해서 이 view를 만든 것이라고 보면 된다.

 

- new ArrayAdapter<> 타입으로 객체 생성

 

 

- 화면에는 이렇게나온다.

 

- 만약 클릭한 셀을 토스트메시지에 출력하고 싶다면,

 리스트뷰의 셀을 클릭했는지, 했다면 어디를 했는지 감시할 리스너가 필요하다.

 

- listview에서 보면 클릭, 드래그, hover, 아이템클릭, select 등등... 다양한 리스너가 등록되어 있다.

- itemClickListener 를 사용한다. 인터페이스 타입으로, 인터페이스를 구현해야 한다.

 

 

- 구현하고 메소드 override까지 해주기!

- 이제 아이템을 클릭하면 이 onItemClick() 메소드로 실행 순서가 들어온다.

  이곳에 클릭한 아이템의 정보가 전달된다.

 

- onItemClick() 의 인자인 int i 에는 클릭한 아이템의 인덱스가 들어있다.

- i 번호값을 가지고 names의 값을 얻어오기!

 

- 하지만 메소드 밖에서참조하려면 필드가 필요하다.

→ names라는 ArrayList를 필드로 만들어주기

 

 

- 실행해보면 클릭한 인덱스에 해당하는 이름이 toast 메시지로 나온다.

 

 

- 액티비티에 리스너를 사용하는 것은 안드로이드에서 매우 중요하다!

- 먼저 액티비티를 리스너로 만들고,

 사용자가 어디를 클릭했는지 알아내서 여기서 작업을 한다.

 


 

- 길게 클릭했을 때 다른 동작을 하고 싶다면?

setOnItemLongClickListener

- 이것도 인터페이스이므로 구현해준다. override도 하기!

- 아이템을 길게 클릭했을 때(누르고 있을 때) 이 메소드에 실행순서가 돌아온다.

 

- AlertDialog.Builder 객체 생성

 

- setTitle() 메소드를 보면 builder 타입이 리턴된다. 이런 구조에서는 메소드를 연속적으로 이어서 사용할 수 있다.

- 재귀적으로 자기자신의 타입이 계속 리턴되는 메소드들이 있다.

 

- 설정을 여러개 할 때 .xxx().yyy().zzz() 라고 연이어서 작성할 수 있다.

- 왜 이런 메소드들이 있는지? 어떤 설정을 편하게 할 수 있도록!

 

- 오랫동안 클릭하면 이렇게 알림이 뜬다.

 

 

- return true; 하면 다른 동작을 막아준다.

(원래는 길게 클릭할 때에도 일반 클릭도 동작이 들어온다. 그런데 저 값을 true로 바꿔주면 토스트 메시지가 나오지 않는다.)

 

- 부정버튼, 긍정버튼을 추가해준 모습!

 

- i 를 클릭하면 오랫동안 클릭한 아이템(이름)을 읽어올 수 있다.

 

- 이런 형태로 만들어볼 수 있다.

 

- 이 긍정/부정 버튼을 눌렀는지 안눌렀는지 알고싶으면 이 값을 읽어오는 리스너를 넣으면 된다.

 

- DialogInterface.OnClickListener 를 넣어주기!

- 그럼 익명클래스가 열린다.

 

- 위에 있는 i를 참조하고 싶은데, 그냥 i를 작성하면 이름이 가까워서 이 i가 참조될 것이다.

 

int SelectedIndex=i;

→ 이 i 값을 필드로 만들어 담아준다.

 

- 이런 구조로 연결되어 있는데,

 모델이 바뀌었다고 Listview가 자동으로 업데이트 되는 것은 아니다.

- 바뀌었다고 adapter에 알려야 한다! 그럼 adapter가 리스트뷰를 업데이트 시켜준다.

 

- 위에 있는 메소드에도 변했다는 내용을 전달해야 하기 때문에

→ ArrayAdapter<String> adapter; 필드로 선언!

 

- 이제 삭제 기능을 사용할 수 있다.

 


 

- AlertDialog.Builder 작성법 관련

- 위에는 리턴타입이 다  Builder 타입인데

  .create() 에서는 AlertDialog 타입이 리턴된다.

 

- 이 메소드는 자신의 타입 객체가 계속 리턴된다. 즉 설정을 편리하게 할 수 있다!

 

- 원래는 이렇게 이 안에 들어있는 Builder 이너클래스로 객체를 생성한 것이다.

 

- 위와 아래 블럭은 같은 내용이다!

- 일반적으로 작성하면 원래는 위와 같은 모양이 되어야 한다. 그러나 아래와 같이 코딩할수 있으면 엄청 효율적이다!

- 코딩의 유행이 이런 형태로 많이 가고 있다.

- 무조건 이렇게 할 수 있는 것은 아니다. 그 객체가 리턴되게끔 설계되어 있어야만 이렇게 코딩할 수 있다.

 

- 액티비티를 DialogInterface.OnClickListener 로 만들 수도 있다.

 


 

 

- 기존 MainActivity를 복사해서 만든 MainActivity2

package com.example.step05listview;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

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

public class MainActivity2 extends AppCompatActivity
        implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener {

    List<String> names;
    ArrayAdapter<String> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //listView의 참조값
        ListView listview=findViewById(R.id.listView);
        
        //listView에 출력할 sample data
        names=new ArrayList<>();
        names.add("바나나");
        names.add("딸기");
        names.add("복숭아");
        for(int i=0;i<100;i++){
            names.add("아무개"+i);
        }
        //listView 에 연결할 아답타 객체
        //new ArrayAdapter<>( Context , layout resource, 모델 ) 
        adapter=new ArrayAdapter<>(this,
                android.R.layout.simple_list_item_1,
                names);
        //listView에 아답타 연결하기
        listview.setAdapter(adapter);
        //Activity를 아이템 클릭 리스너로 등록하기
        listview.setOnItemClickListener(this);
        listview.setOnItemLongClickListener(this);
    }

    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
        // int i는 클릭한 아이템의 인덱스가 들어있다.
        String name=names.get(i);
        Toast.makeText(this, name, Toast.LENGTH_SHORT).show();
    }

    //DialogInterface.OnClickListener 타입 필드
    DialogInterface.OnClickListener listener=new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialogInterface, int i) {
            //눌러진 버튼이 Negative 버튼인지 Positive 버튼인지 구별할 숫자값이 매개변수 i에 전달된다.
            if(i == DialogInterface.BUTTON_POSITIVE){//네 버튼
                //필드에 저장된 값을 활용해서 데이터를 삭제
                names.remove(selectedIndex);
                adapter.notifyDataSetChanged();
            }else if(i == DialogInterface.BUTTON_NEGATIVE){//아니요 버튼

            }
        }
    };
    //응 클릭된 인덱스를 저장할 필드
    int selectedIndex;

    @Override
    public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
        //오랫동안 클릭한 셀에 출력된 이름 읽어오기
        String name=names.get(i);
        //위의 필드에 값을 넣어줄 때 사용한 익명 클래스에서 참조 가능하도록 필드에 담아둔다.
        selectedIndex=i;


        new AlertDialog.Builder(this)
                .setTitle("알림")
                .setMessage(name+"을 삭제하시겠습니까?")
                .setNegativeButton("아니요", this.listener)
                .setPositiveButton("네", listener)
                .create()
                .show();
        //이벤트 전파를 여기서 막기
        return true;
    }
}

 

- 네/아니오 버튼 중 무엇을 눌러도 동일한 리스너가 동작하게 하기

(어떤 것을 눌렀는지 구분, 분기가 필요하다)

 

- 필드에 무언가를 선언하고 값을 집어넣을 수 있다.

→ DialogInterface를 필드로 만들기

 

- 필드에 직접 new해서 객체를 생성해주기

- DialogInterface 타입의 OnClickListener 객체를 생성해서 listener에 담아준 것이다.

 

 

- DialogInterface.OnClickListener 타입의 listener 라는 필드를 생성

- 이 필드를 선언해서 값도 직접 넣어준 것이다.

 

- 이렇게 리스너를 각각의 Button에 등록해준다.

 

 

- 현재 동일한 리스너를 등록했으므로, 어떤 버튼을 눌러도 이 메소드로 순서가 들어온다.

- 구분할 수 있는 방법은? int i 활용

 

- 눌러진 버튼이 Negative 버튼인지 Positive 버튼인지 구별할 숫자(정수) 값이 매개변수 i 에 전달된다.

 

- 버튼에 따라 상수값으로 정의되어있다.

- Negative 버튼을 클릭하면 -2, Positive 버튼을 클릭하면 -1이 출력된다.

- Neutral 버튼은 보통 확인 버튼으로 사용된다.

 

 

- 정의되어 있는 상수를 사용해 이렇게 분기할수있다

 

- 그런데  빨강->파랑으로 실행 순서가 들어오는데

파란색 메소드 안에서 빨간 메소드 안에 있는 값(index)이 필요하다. 접근이 불가능하다.

→ 이 i 값을 필드에 넣어두어야 참조가 가능하다.

 

- selectedIndex를 필드로 선언. 이 값을 필드에 넣어두면 위의 onclick메소드에서도 참조 가능해진다.

- 값을 담아두었다가 필요할 때 사용할 수 있다!

 

- 필드값을 참조해서 삭제하고, adapter에서 listview로 값이 바뀌었다고 전달하기.

 

 

- mainActivity2를 화면에 띄워보려면? manifest에 등록하기

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

    <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/Theme.MyAndroid">

        <activity android:name=".MainActivity2" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        </activity>

        <activity
            android:name=".MainActivity"
            android:exported="true">


            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
    </application>

</manifest>

 

 

- 이렇게 작성하고 옮겨놓으면 mainActivity2를 테스트할 수 있다.