문제 해결

[Android] EditText로 ListView 구성하기

토아드 2018. 8. 27. 17:46
반응형

 단어장 앱을 만들던 중 EditText를 가지는 Custom ListView를 구현하고 있었다. 그런데 예제를 따라 만들던 중 EditText를 스크롤 할때마다 돌아올 때 값이 사라지거나 하는 현상이 발생했다.

 ListView에 대한 이해 없이 구현하다 보니 이런 문제가 발생했다. 그래서 이번 포스트는 1) ListView가 동작하는 방식과 2) EditText를 원소로 이용할 때 어떻게 값을 저장하고 불러올지 예제를 소개하고자 한다.



ListView

ListView는 카카오톡 친구목록, 대화방 목록과 같이 같은 형태의 여러 View들을 리스트 형태로 보여주는 View이다. ListView를 이용하기 위해서는 Adapter라는 인터페이스를 구현해야 되는데, 이 Adapter라는 녀석이 안드로이드 화면 위에서 View가 위 아래로 스크롤 될 때 View를 처리해 주는 역할을 해 준다.


ListView에서 가장 중요한 부분이 바로 Adapter에서 구현하는 getView 메소드다. 


1
public View getView(int i, View convertView, ViewGroup parent)
cs


위 메소드가 getView의 리턴 타입과 파라미터들이다.


1) i는 실제 리스트 뷰에서 몇번째 view인지 표현하는 index 변수


2) convertView는 재활용 View라고 해서 ListView에서 가장 중요한 부분이라 해도 과언이 아니다. 아래에서 자세히 설명하겠다.


3) parent는 리스트뷰를 담고 있는 부모 뷰이다.



convertView?

 ListView는 스크롤이 이동 할 때마다 뷰를 새로 생성하는 과정을 거쳐서 사용자에게 스크롤 되는 화면을 제공하는데, 모든 뷰를 새로 inflate 해서 보여주는 것은 공간적으로도 시간적으로도 비효율적이다. 그래서 convertView라는 재활용 View를 사용하게 되는데 왜 재활용 View라고 부르냐 하면, 이미 inflate 했으며 스크롤 해서 사라진 View를 convertView 파라미터로 보내줘서 사용자 측에서 이용 할 수 있게 하기 때문에 재활용 View라고 말한다. 

getView 메소드는 convertView가 null인 경우 재활용 할 수 있는 View가 없으므로 inflate 과정을 거친 다음 해당 View의 id를 찾아서 값을 세팅하는 방식으로 구현하는 것이 정석이다. (뷰를 새로 받거나 재활용뷰를 받아서 값을 세팅하는 방법은 adapter 내의 Item arrayList 원소에서 파라미터로 받은 i를 이용해 

view.setText(ItemList.get(i).getValue()); 와 같은 방법으로 값을 넣어준다. 자세한 것은 ListView 사용방법 참조)



EditText in ListView

 내가 이 게시글을 쓴 이유 중 하나이기도 하다. 처음 리스트뷰에 대한 이해도가 낮았을 때 어떻게 EditText로부터 값을 받아야 되는지 몰랐었다. 그래서 처음에는 EditText 뷰를 생성할 때 그 값들을 ArrayList로 저장한 후, 나중에 일괄적으로 getText() 메소드를 이용해서 받아오면 되겠지 싶었다. 하지만 실제로 View 자체는 재활용 되므로 내가 만든 동적 ListView의 갯수와 EditText의 갯수는 달라서 아이템 갯수가 스크롤을 넘기는 사이즈가 됬을 때 에러가 났었다.

 

 그냥 TextView 같은경우는 원래 값이 주어진 Item List가 있으므로 문제가 되지 않지만 이 경우 Item들이 동적으로 바뀌므로 스크롤을 해서 해당 뷰가 사라질 때 마다 Item 리스트의 값을 변경해 줘야 하는 문제점이 있었다. 그래서 평소처럼 ListView 구현을 했을 때 입력을 하고 스크롤을 내렸다 올라가면 값이 사라져있거나, 처음 입력했던 값이 스크롤을 내린 후의 뷰에 입력되어있는 중복현상이 있었다.


 여러 해결법을 찾아봤지만 가장 심플하고 가독성 좋은 방법을 아래 사이트에서 찾을 수 있었다.


 https://vikaskanani.wordpress.com/2011/07/27/android-focusable-edittext-inside-listview/


 setOnFocusChangeListener 라는 EditText가 포커스를 상실했을 때 호출되는 이벤트 리스너 추가 메소드를 이용해서 포커스를 잃으면 값을 ItemList에 저장하는 방법이다.

 

 위 사이트의 게시글을 보고 적용한 결과는 다음과 같다 (뷰홀더 패턴 & 포커스 리스너)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Override
    public View getView(int i, View convertView, ViewGroup parent) {
        final int pos = i;
        final Context context = parent.getContext();
        View view = convertView;
        ViewHolder viewHolder;
        if (view == null)
        {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate(R.layout.word_add_listview_item,parent,false);
            viewHolder = new ViewHolder();
            viewHolder.word = (EditText) view.findViewById(R.id.add_word_editText);
            viewHolder.meaning = (EditText) view.findViewById(R.id.add_meaning_editText);
            viewHolder.word.setPrivateImeOptions("defaultInputmode=english;");
            viewHolder.meaning.setPrivateImeOptions("defaultInputmode=korean;");
            view.setTag(viewHolder);
        }
        else
        {
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.word.setText(myItemList.get(i).word);
        viewHolder.meaning.setText(myItemList.get(i).wordsMeaning);
        viewHolder.word.setId(pos);
        viewHolder.meaning.setId(pos);
        viewHolder.word.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if(!hasFocus) {
                    final int position = v.getId();
                    final EditText wordEdit = (EditText) v;
                    myItemList.get(position).word = wordEdit.getText().toString();
                }
            }
        });
        viewHolder.meaning.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if(!hasFocus) {
                    final int position = v.getId();
                    final EditText meaningEdit = (EditText) v;
                    myItemList.get(position).wordsMeaning = meaningEdit.getText().toString();
                }
            }
        });
        return view;
    }
cs


반응형