본문 바로가기

Blog/Android

List, 화면 리스트 만들기

화면에 전화번호부처럼 리스트를 만들고 아이템 추가해보기

 

 

activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout 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">

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

            <Button
                android:id="@+id/button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="추가" />

            <EditText
                android:id="@+id/editText1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:inputType="textPersonName"
                android:text="레드벨벳" />

            <EditText
                android:id="@+id/editText2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:inputType="textPersonName"
                android:text="010-8000-8000" />
            <!-- ems가 들어가있어서 글자 크기가 커진거 같다고 했음
            그리고 weight로 크기 조절-->
        </LinearLayout>

        <ListView
            android:id="@+id/listView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true"
            android:layout_alignParentEnd="true"
            android:layout_alignParentBottom="true"
            android:layout_marginStart="0dp"
            android:layout_marginTop="49dp"
            android:layout_marginEnd="0dp"
            android:layout_marginBottom="0dp" />
    </RelativeLayout>

 

디자인 팔레트에 리스트뷰가 있음, 그거 이용해서 리스트 만들면 됨<br>

하지만, 저 아이템들 각각의 요소를 세부적으로 정해주기 위해서는 item 레이아웃을 따로 만들어야 함

 

 

singerItem.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="wrap_content"
        android:orientation="horizontal">
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:src="@mipmap/ic_launcher"/>
        <!-- mipmap/ic_launcher은 기본적인 안드로이드 보이 이미지-->
        <!-- 이미지를 각각의 아이템마다 다르게 하기 위한 아이디 -->

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_marginLeft="10dp">
            <TextView
                android:id="@+id/textView1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="이름"
                android:textSize="30dp"
                android:textColor="@color/colorPrimaryDark"/>
            <TextView
                android:id="@+id/textView2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="전화번호"
                android:textSize="24dp"
                android:textColor="@color/colorAccent"
                android:layout_marginTop="8dp"/>
        </LinearLayout>

    </LinearLayout>

보면 각각 아이템들 왼쪽에는 사진, 오른쪽에는 이름과 전화번호 등의 세부 내용을 넣을 수 있도록 디자인 팔레트에서 요소들 가져와서 배치

 

 

먼저 리스트를 만들기 전에 리스트 목록에 들어갈 아이템의 클래스를 만들어 각각의 객체를 생성할 수 있도록 해야함. 

 

 

SingerItem.java

    public class SingerItem {//데이터를 담아두기 위한 클래스
        String name;
        String mobile;
        int resId;

        public SingerItem(String name, String mobile, int resId) {//생성자
            this.name = name;
            this.mobile = mobile;
            this.resId = resId;
        }

        public String getName() {//getter, setter 이용하는 이유는 최대한 직접 접근보다(name.name) 간접적으로 메소드를 통해
                                //접근하라는 것
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getMobile() {
            return mobile;
        }

        public void setMobile(String mobile) {
            this.mobile = mobile;
        }
        //데이터를 담아두기 위한 클래스를 위해 편리하게 데이터를 담아둘 수 있는 메서드
        //또는 이 안에서 사용할 메소드를 생성


        public int getResId() {
            return resId;
        }

        public void setResId(int resId) {
            this.resId = resId;
        }

        @Override
        public String toString() {//toString() 메소드를 통해 객체 안의 내용을 문자열로 바로 출력해줌
            return "SingerItem{" +
                    "name='" + name + '\'' +
                    ", mobile='" + mobile + '\'' +
                    '}';
        }
    }
    //걸그룹의 데이터를 정의

 

 

이제 정의해놓은 내용을 하나하나 뷰에 붙여야해

아까 디자인 팔레트에 만든 각각 아이템의 내용을 layoutInflater를 이용해 분할해서 붙임. (MainActivity -> view이용 -> List아이템들)

 

 

SingerItemView.java

    import android.content.Context;
    import android.media.Image;
    import android.util.AttributeSet;
    import android.view.LayoutInflater;
    import android.widget.ImageView;
    import android.widget.LinearLayout;
    import android.widget.TextView;

    public class SingerItemView extends LinearLayout {
        TextView textView1;
        TextView textView2;
        ImageView imageView;

        public SingerItemView(Context context) {//생성자
            super(context);
            init(context); //각각의 생성자에서 초기화 메서드 호출
        }

        public SingerItemView(Context context, AttributeSet attrs) {//생성자
            super(context, attrs);
            init(context);
        }

        private void init(Context context){//초기화하는 메서드
            //여기는 우리의 singer_item.xml파일을 인플레이션해서 여기다가 붙여주는 역할을 하는 거
            LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);//인플레이션을 위해 레이아웃 인플레이션 서비스를 이용해 시스템 서비스를 참조할 수 있음
            //화면이 없는 백그라운드 서비스가 돌고 있음, 단말이 시작됐을 때 단말에서 기본적으로 실행시켜 놓는 서비스를 시스템 서비스라고 함.(그 중 레이아웃 인플레이터를 이용해 서비스 받았으니까 이거 쓰자)
            inflater.inflate(R.layout.singer_item, this, true);//내가 만든 singer_item.xml을 대상으로 하고, this는 우리가 LinearLayout을 상속받았는데 , 그러면 singer_item.xml에 있는 최상위 레이아웃이 LinearLayout이니까
            //인플레이션 해서 여기다가 붙일 수 있음(그래서 this라고 넣어주는거)
            //(즉, 내 생각 = 전에 토스트 보더 만들 때도 레이아웃 인플레이션을 했었는데 그 때는 레이아웃 루트에 가져다 붙였음(메인 액티비티에서) 그것은 따로 가져다 붙일 수 있는 뷰를 안 만들어서 루트에 붙인거고
            //여기 자바는 일부러 가져다 붙일 수 있는 뷰를 만든 거기 때문에 이 액티비티에 붙이겠다라고 하는 것으로 this를 쓴 것

            textView1 =  (TextView)findViewById(R.id.textView1);//이 텍스트뷰를 클래스 안에서 어디서든 사용 가능하게 하려면 위에 바깥에 선언해야 함
            textView2 =  (TextView)findViewById(R.id.textView2);
            //이렇게 인플레이션 해주면 xml 안에 들어있는 텍스트 뷰나 이미지 뷰를 참조 가능
            imageView = (ImageView)findViewById(R.id.imageView);
        }

        //이렇게 한 SingerItemView를 어댑터 안에서 쓸건데(데이터니까), 여기다가 데이터를 설정해줄 일이 있을 것임,
        // 그러면 SingerItemView를 이용해서 만든 객체에다가 우리가 메서드를 호출하면 바로 데이터를 설정할 수 잇게 메서드를 여기다가 정의를 할거임
        public void setName(String name){//텍스트뷰1에는 이름 설정
            textView1.setText(name);
        }

        public void setMobile(String mobile){//텍스트뷰2에는 전화번호 설정
            textView2.setText(mobile);
        }

        public void setImage(int resId){
            imageView.setImageResource(resId);
        }//이미지 뷰에 이미지 설정하는 거는 아까 내가 추가한 R.drawable 밑에 있는 거니까 메소드 설정하는 메소드 만들고
        //Integer 값으로 관리가 되므로 resId라고 해서, resId를 파라미터로 전달하게 함
        //데이터에 이미지 값 들어감
    }
    //SingerItem.xml에서 최상위 레이아웃이 LinearLayout 이었는데 이 자바 클래스도 LinearLayout을 상속해서 만들면 레이아웃 인플레이션 할 수 있음(즉, 메모리 객체화한 다음에 여기다 그대로 붙여줄 수 있다.)
    //xml을 메모리 객체화해서 여기 레이아웃에 붙여가지고 자바 소스파일로 아이디 가져와서 사용가능(객체)

    //이렇게 해서 하나의 아이템을 보여주기 위해 뷰를 하나 정의한 것, 부분 화면처럼 정의가 됨,
    //그럼 이제 이후에는 이걸 가지고 이제 MainAcitivity에서 정의하는 어댑터 안에서 getView를 이용해서 뷰 객체를 만들고 리턴을 하도록 만들거임

    //전체 정리 내생각
    //지금까지 하나의 아이템을 만들기 위해 부분화면인 xml을 만들고 그걸 객체화해서 뷰에 가져다 붙이기 위해 자바 클래스에서 객체화하고 뷰에 가져다 붙임
    //객체화했기 때문에 xml 안에 있는 텍스트뷰를 아이디로 가져올 수 있게 되었고, 이 뷰 안의(아이템 안에) 이름과 전화번호 설정도 할 수 있게 됨
    //이 정의된 내용을 담고 있는 뷰 전체를 객체화하기 위해 MainActivity에서 getView()를 이용해 뷰 객체를 만들고 이 객체를 리턴 받아서 사용할거라는 뜻

 

 

MainActivity.java

    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.AdapterView;
    import android.widget.BaseAdapter;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.ListView;
    import android.widget.Toast;

    import java.util.ArrayList;

    public class MainActivity extends AppCompatActivity {
        SingerAdapter adapter;

        EditText editText1;
        EditText editText2;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);//단지 화면을 객체화 시켜주는

            editText1 = (EditText)findViewById(R.id.editText1);//에디트 텍스트에 친 정보를 리스트뷰에 추가하기 위함
            editText2 = (EditText)findViewById(R.id.editText2);


            ListView listView =  (ListView)findViewById(R.id.listView);//지금 얘는 데이터 담을 수 있는 것만 정의해 놓은 거고 시제 데이터는 아예 없음
            //어댑터를 만들어야 하는데 안드로이드 api에서 제공하는 다양한 어댑터들이 있음
            //어레이 어댑터라든가 이런 거 쓰면 코드를 간단히 만들 수도 있음
            //근데 리스트 뷰를 만들고 나서 리스트 뷰 안에 들어가는 게 이미지, 텍스트 이런 게 있다고 하면
            //그런 것들을 커스터마이징해서 또 쓰게 되니까 그래서 처음부터 이 리스트 뷰 안에 들어가는 아이템의 모양을
            // 걍 내가 원하는대로 바꿀 수 있게 구조를 만드는 게 좋음(그래서 각각의 아이템을 위한 별도의 뷰를 부분 화면처럼 만들 거임
            // 그리고 별도의 뷰를 어댑터에 넣어줄 거임)
            //어댑터 경우에도 나중에! 커스터마이징하기 쉽게 직접 정의 할 거임

            //리스트 뷰에다가 어댑터 객체를 만든 다음에 설정을 해주는 과정이 필요
            adapter = new SingerAdapter();//어댑터 객체 만들고

            //데이터가 없으니 데이터 추가
            adapter.addItem(new SingerItem("소녀시가", "010-9999-9999", R.drawable.face));
            adapter.addItem(new SingerItem("소녀시에", "010-9999-9998", R.drawable.face2));
            adapter.addItem(new SingerItem("소녀시고", "010-9999-9990", R.drawable.face_two));
            adapter.addItem(new SingerItem("소녀시기", "010-9999-9997", R.drawable.iconfinder1));
            adapter.addItem(new SingerItem("소녀시먹", "010-9999-9996", R.drawable.iconfinder2));
            adapter.addItem(new SingerItem("소녀시고", "010-9999-9995", R.drawable.iconfinder3));
            adapter.addItem(new SingerItem("소녀시싶", "010-9999-9994", R.drawable.iconfinder4));
            adapter.addItem(new SingerItem("소녀시다", "010-9999-9993", R.drawable.face));
            //여기 이 형태만 보면 리스트뷰 껍데기니까 xml 레이아웃에 넣고, 어댑터 객체가 사용되니까 이걸 만들고 데이터를 넣은 다음 리스트 뷰를 설정한 것이다.
            //근데 어댑터를 구성하려고 하니까 각각의 아이템을 위한 뷰 객체를 정의해야 했고, 그 뷰 객체안에 들어가야 할 데이터가 여러 개니까 그걸 위한 자바 클래스를 하나 정의한 것

            listView.setAdapter(adapter);//리스트뷰 보면 셋 어댑터 메소드가 있는데 여기에 어댑터 객체 전달
            //이렇게 하면 리스트뷰가 어댑터를 알게 되어서 어댑터한테 얘기한 것처럼 getCount 호출하면서 몇 개있냐 물어보고 getView를 그 개수만큼 호출하면서 뷰 객체를 받아서 화면에 보여주게 됨

            listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                //리스트 뷰에 어떤 한 아이템을 클릭했을 때 어떤 아이템이 선택되었는지 이런 정보를 한번 보여주도록 할 것
                //어떤 아이템이 선택됐을 때 이벤트를 처리할 수 있어야 그 아이템의 상세정보를 보여주는 화면으로 넘어가던지..이런 정보 보여줘야 함
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    //몇 번째 아이템인지에 대한 인덱스 값도 파라미터 position, 이 포지션에 해당하는 데이터를 확인할 수 있어야 어떤 게 선택되는지를 알 수 있음
                    SingerItem item = (SingerItem) adapter.getItem(position);//어댑터에서 데이터를 관리하므로 어댑터에서 데이터 꺼냄, adapter의 선언을 OnCreate 안에 선언하면 이 new AdapterView.OnItemClickListener() 정의하는 곳 안에서는 처리할 수 없으므로
                                                                            //(변수 참조 불가능) OnCreate 바깥에 선언해야 함
                    //getItem(position)하면 지정한 position의 데이터가 넘어옴, 그 데이터는 우리가 위에서 넘겨준 SingerItem 임(데이터를 어댑터한테 넘겨주고 어댑터에서 가져옴)
                    Toast.makeText(getApplicationContext(), "선택 : " + item.getName(), Toast.LENGTH_LONG).show();//선택한 아이템 토스트로 띄워주기


                }
            });//아이템을 클릭했을 때 발생하는 이벤트 처리를 위함함

            Button button = (Button)findViewById(R.id.button);//버튼 누르면 내용 처리할라고
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    String name = editText1.getText().toString();//각각의 텍스트뷰에 적힌 내용을 가져와서(아이디를 통해) 변수에 넣어줌
                    String mobile = editText2.getText().toString();

                    adapter.addItem(new SingerItem(name, mobile, R.drawable.iconfinder4));//넘어온 내용을 어댑터에 추가
                    //이제 리스트뷰 갱신해줘야 함. 근데 리스트 뷰 자체를 가지고 메서드를 호출하는 건 아니고(뭔소리냐 : 리스트 뷰만을 이용해서 메서드를 호출해서 갱신하는게 아니고)
                    adapter.notifyDataSetChanged();//어댑터쪽에서 리스트뷰쪽으로 갱신하라고 알려줌, 그럼 끝이래

                }
            });


        }

    class SingerAdapter extends BaseAdapter{//여기서 SingerAdapter는 걍 내가 지은 이름이고 기본 어댑터인(일반적, 다양) BaseAdpater 상속 받는 거
            //ArrayList<String> items = new ArrayList<String>();//문자열 어레이리스트, 사실 이름/전화번호 등 여러 자료형이 들어가면 어레이 리스트로는 안 되므로 별도의 클래스 정의해야 함

            ArrayList<SingerItem> items = new ArrayList<SingerItem>();//SingerItem 클래스의 어레이리스트 객체 생성
            //어댑터가 데이터를 관리하는데, 데이터를 추가할 수도 있고 삭제할 수도 있으니까
            //이 안에서 데이터를 관리할 수 있는 거를 ArrayList로 만들어봄


            //위에 리스트뷰는 데이터 담을 수 있는 거 정의고 데이터는 없으므로 데이터 객체를 만들고 리스트 뷰 설정한 다음에 또는 그전에 추가를 해야 할텐데
            //밖에서 추가를, 이 클래스 또는 객체 밖에서 추가를 하려 그러면 메소드가 필요함 그래서 하나 더 정의
            public void addItem(SingerItem item){//SingerItem이라고 하는 객체를 하나 파라미터로 전달한다면
                items.add(item);//그거를 items.add라고 해서 추가하도록 메서드를 하나 정의함

            }

            @Override
            public int getCount() {
                //리스트 뷰가 화면에 보여지는 시점에 리스트뷰가 어댑터한테 몇 개의 아이템이 있니(getCount() 호출하며) 물어봄, 그럼 어댑터가 몇 개야 답하면 리스트뷰가 그 만큼의 아이템을 전달 받아서 보여줌
                return items.size();//아이템의 몇 개인지 알려줌(리스트 사이즈)
            }

            @Override
            public Object getItem(int position) {
                //필요할 때 그 데이터 아이템 좀 줄래? 라고 리스트뷰가 그러면서 몇 번째 데이터인지 이걸로 넘겨줌(인덱스)
                return items.get(position);//position이 인덱스에 해당
            }

            @Override
            public long getItemId(int position) {
                return position;
            }//그 순서의 데이터가 아이디가 있다면 아이디를 넘겨달라는 뜻

            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                //뷰의 재사용(마지막 추가 설명 내용)
                /*
                SingerItemView view= null;
                if(convertView == null){//convertView가 널인 경우에만
                    view = new SingerItemView(getApplicationContext());//뷰에다가 새로 만들어서 할당
                }
                else{//그렇지 않으면
                    view = (SingerItemView)convertView;//convertView를 SingerItemView로 캐스팅만 해서 그대로 재사용
                }
                */

                SingerItemView view = new SingerItemView(getApplicationContext());//어떤 뷰든 안드로이드에서는 전부 다 컨텍스트 객체를 받게 되어있음
                SingerItem item = items.get(position);
                //이 뷰가 position 값이 넘어옴, 몇 번째 뷰를 달라는건지 이게 넘어오는 거 , 그러면 그 몇 번째에 해당하는 데이터를 이용해 이 뷰에다가 설정을 해줘야 그 데이터가 뷰에 보임
                view.setName(item.getName());//이름 설정, item.getName()이라는 메서드로 이름 데이터를 가져와서 설정
                view.setMobile(item.getMobile());
                view.setImage(item.getResId());//데이터에서 관리되는 걸 가지고 와서 뷰에 설정해주게 됨
                //SingerItemView 안에서 뷰를 설정하고 그 자바 소스 클래스의 객체 변수를 만들어서(인스턴스), 그것으로 메소드 호출하여 이미지 아이디로 가져오도록
                //얘는 뷰에 붙여주는 역할(화면에 보이도록 하는)이고, 실제 데이터를 넣는 곳은 위에 생성자 부분임

                return view;//이 뷰를 리턴해주면 그대로 리스트뷰 하나의 아이템으로 보인다. , 리턴되는 뷰에는 실제 데이터의 SingerItem이라고 하는 데이터 안에 들어가있는 Name과 Mobile 값이 설정되어 보이게 됨
                //어댑터가 데이터 관리하기 때문에 데이터 관리하는 어댑터가 화면에 보여질 각각의 아이템에 대한 뷰도 만들어 달라는 뜻(이미지, 글자 필요하다면 여기서 리턴해주는 뷰는 레이아웃으로 구성이 되야 함
                //=> 그럼 레이아웃에 해당하는 거를 부분화면으로 하나 정의하고 여기서 그걸 이용해서 객체를 만든 다음에 데이터를 설정하고 리턴해주는 게 가장 좋은 방법)

                //SingerAdapter에서 getView를 해줄 때, 실제로 실무에서 쓰기에는 위 내용은 좀 부족
                //왜냐면 우리가 이제 리스트뷰 보일 때 많은 아이템을 로딩하기가 메모리가 너무 많이 쓰임 즉, 뷰 만들 때 뷰 객체가 너무 많이 생기면 메모리 엄청난 소비임
                //그래서 재사용할 수 있게 해놓음(뷰가 화면에 안 보이는 아이템은 보이는 아이템에 뷰와 붙여줘서 안 쓰는 뷰를 재사용하는거지, 데이터만 바꿔주고)
                //코드를 재사용할 수 있도록 위 파라미터에 있는 converView라고 하는 걸 넘겨줌

            }
        }
        //어댑터 객체를 만들기 위해 어댑터 클래스를 먼저 정의(기존 제공의 어댑터를 상속해서 그거를 필요한 만큼
        //코드를 넣을 수 있게 할 거임임
    }

    //리스트 뷰에서 자동으로 클릭 효과 있고, 많으면 스크롤 뷰도 자동으로 생김

DB 없으니까 대충

 

 

결과