2017년 3월 4일 토요일

Custom List which has check box in Android (checkbox를 가지는 사용자 정의 리스트 만들기)


Custom 사용자정의 List 는 많은곳에서 사용합니다.

List를 검색도 할 수 있고, check box도 가지고 있는 형태를 만들어 보도록 하겠습니다.
아래 ScreenShot을 참고 하시면 됩니다. 최종 상태의 화면입니다.





리스트를 만들때 시간이 오래걸리는 경우도 있기 때문에 여기 예제에서는 sleep함수로 대기하도록 구현하였으며, 그 때 로딩 다이얼로그가 나오도록 작업하였습니다. 아래 그림 참고




먼저 List하나의 item의 배치를 만들도록 합니다.
내용은 아래와 같이 만듧니다. check box가 필요하지 않다면 삭제해도 됩니다.

list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?android:attr/listPreferredItemHeight"
    android:orientation="vertical"
    android:gravity="fill" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="horizontal"
        android:paddingRight="6dip"
        android:paddingLeft="6dip"
        android:gravity="center_vertical" >

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/checkBox_saved"
            android:layout_marginLeft="5dip"
            android:layout_marginRight="11dip"
            android:layout_gravity="center_vertical"
            android:scaleType="fitCenter"/>

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
            <TextView android:id="@+id/name_saved"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:singleLine="false"
                android:ellipsize="marquee"
                android:layout_marginBottom="2dip"
                android:maxLines="3"
                android:textSize="12sp"
                android:gravity="center_vertical" />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

여기에서 주의할점은 layout_width, layout_height 입니다. text가 비어있더라도 전체 영역이 되도록 설정해줘야만 텍스트가 없는 빈곳을 tap시에도 list가 선택이 됩니다.

이제는 list를 담을 main xml이 필요합니다.
위쪽에는 검색을 만들어 넣고 아래쪽에는 정보를 출력하기 위한 textView_debug 를 만들었습니다. 그리고 그 아래는 궁극의 리스트를 만들어 넣었습니다.

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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.app.darts.customlistexample.MainActivity">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

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

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:srcCompat="@android:drawable/ic_menu_search"
                android:id="@+id/imageView" />

            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPersonName"
                android:text=""
                android:ems="10"
                android:id="@+id/savedfilter" />

        </LinearLayout>

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">

            <TextView
                android:text="TextView"
                android:layout_width="match_parent"
                android:layout_height="102dp"
                android:id="@+id/textView_debug"
                android:maxLines="5" />
            <ListView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/list1"
                />
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>

setupList 메소드를 만들고 호출해줍니다.
List 설정
    ListView listView;

    setupList();

    private void setupList() {
        listView=(ListView)findViewById(R.id.list1);
    }


AdapterAsyncTask는 AsyncTask상속 받아서 만듭니다. AsyncTask는 비동기적인 처리를 위한 process로 구동되는 안드로이드에 있는 class입니다. 안그러면 thread를 사용해야하는데요. 그것보다는 편합니다. 아래 예제 보면, onPreExecute doInBackground onPostExecute 3개가 중요한 메소드인데요. 순차적으로 실행이 됩니다, doInBackground 안에 시간이 걸리는 작업을 하면됩니다. 물론 해당 메소드 안에서는 ui 관련 작업을 하면 안됩니다. 그래서 onPreExecute 에서 다이얼로그를 그리고 onPostExecute 여기에서는 해당 다이얼로그를 없애도록 합니다. 아래 소스 참고
background 처리중 ProgressDialog 처리
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            mDlg = new ProgressDialog(mContext);
            mDlg.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            mDlg.setMessage( "loading" );
            mDlg.show();
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            mDlg.dismiss();
            ...
        }

list의 아이템들은 어댑터로 연결됩니다. setupAdapter 메소드로 설정이 되는데요. 이건 AsyncTask 작업의 마지막 단계인 onPostExecute 가 호출되는 시점에 listView.setAdapter(listadapter); 에 의해서 설정됩니다.

adapter 설정
    protected void onCreate(Bundle savedInstanceState) {
    ...
        setupAdapter();
    ...
    }
    private void setupAdapter() {
        AdapterAsyncTask adaterAsyncTask = new AdapterAsyncTask(MainActivity.this);
        adaterAsyncTask.execute();
    }

    public class AdapterAsyncTask extends AsyncTask<String,Void,String> {
        private ProgressDialog mDlg;
        Context mContext;
        public AdapterAsyncTask(Context context) {
            mContext = context;
        }
        @Override
        protected void onPreExecute() {
            ...
        }
        @Override
        protected String doInBackground(String... strings) {
            ...
        }
        @Override
        protected void onPostExecute(String s) {
            ...
        }
    }

background 처리중 ProgressDialog 처리
        @Override
        protected void onPostExecute(String s) {
            ...
            listadapter=new MyListAdapterMy(mContext);
            listView.setAdapter(listadapter);
            ...
        }

listView.setAdapter(listadapter); 로 설정되는 listadapter 의 type은 BaseAdapter로 부터 상속 받은 class여야 합니다.
여기에서는 MyListAdapterMy -> MyArrayAdapter -> BaseAdapter 순으로 상속이 되어있습니다.
상속 관계
public class MyListAdapterMy extends MyArrayAdapter<MyListItem> {
...
}
public abstract class MyArrayAdapter<E> extends BaseAdapter {
...
}

tap을 해서 클릭 처리를 위해서 check box 쪽과 text누를때 각각을 처리해야 하므로 getView메소드에서 해당 내용을 처리합니다.
super.getView 메소드에서는 LAYOUT_INFLATER_SERVICE 에 의해서 하나의 list item의 컴포넌트가 inflate가 되어있습니다.
getView
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View retView = super.getView(position,convertView,parent);
            final int pos = position;
            final View parView = retView;
            CheckBox cb = (CheckBox)retView.findViewById(R.id.checkBox_saved);
            cb.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    MyListItem item = listDispItems.get(pos);
                    item.checked = !item.checked;
                    if( item.checked ) totalSelected++;
                    item.selectedNumber=totalSelected;
                    Toast.makeText(MainActivity.this,"1: Click "+pos+ "th " + item.checked + " "+totalSelected,Toast.LENGTH_SHORT).show();
                    printDebug();
                }
            });
            TextView name = (TextView)retView.findViewById(R.id.name_saved);
            name.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    MyListItem item = listDispItems.get(pos);
                    item.checked = !item.checked;
                    if( item.checked ) totalSelected++;
                    item.selectedNumber=totalSelected;
                    Toast.makeText(MainActivity.this,"2: Click "+pos+ "th " + item.checked + " "+totalSelected,Toast.LENGTH_SHORT).show();
                    printDebug();
                    bindView(parView, item);
                }
            });

            return retView;
        }

filter의 처리는 setupFilter메소드에 의해서 이루어집니다. textChange 메소드에 callback내용을 등록을 해서 listadapter.fillter 함수를 호출하면 화면에 뿌려지는 리스트인 listDispItems 여기에 list 항목이 갱신됩니다. 갱신 후에는 실제로 리스트 내용이 변경되었다는 노티를 줘야하는데 안주면 ui적으로 보이는 list는 갱신이 이루어 지지 않습니다. 이때 호출해야하는 메소드가 notifyDataSetChanged() 입니다.

setupFilter
    private void setupFilter() {
        editView=(EditText)findViewById(R.id.savedfilter);
        editView.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }
            @Override
            public void afterTextChanged(Editable s) {
                String searchText = editView.getText().toString();
                if( listadapter!=null ) listadapter.fillter(searchText);
            }
        });
    }

fillter
        public void fillter(String searchText) {
            listDispItems.clear();
            totalSelected = 0;
            for(int i = 0;i<listAllItems.size();i++){
                MyListItem item = listAllItems.get(i);
                item.checked = false;
                item.selectedNumber = 0;
            }
            if(searchText.length() == 0)
            {
                listDispItems.addAll(listAllItems);
            }
            else
            {
                for( MyListItem item : listAllItems)
                {
                    if(item.name.contains(searchText))
                    {
                        listDispItems.add(item);
                    }
                }
            }
            notifyDataSetChanged();
        }









지금까지 설명된 전체 소스 입니다.

MainActivity.java
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    int totalSelected = 0;
    EditText editView;
    ListView listView;
    TextView textView;
    MyListAdapterMy listadapter;
    ArrayList<MyListItem> listAllItems=new ArrayList<MyListItem>();
    ArrayList<MyListItem> listDispItems=new ArrayList<MyListItem>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView=(TextView)findViewById(R.id.textView_debug);
        setupList();
        setupAdapter();
        setupFilter();
    }

    private void setupList() {
        listView=(ListView)findViewById(R.id.list1);
    }

    public class MyListItem {
        int selectedNumber;
        boolean checked;
        String name;
    }

    public class MyListAdapterMy extends MyArrayAdapter<MyListItem> {

        public MyListAdapterMy(Context context) {
            super(context,R.layout.list_item);
            totalSelected = 0;
            setSource(listDispItems);
        }
        @Override
        public void bindView(View view, MyListItem item) {
            TextView name = (TextView)view.findViewById(R.id.name_saved);
            name.setText(item.name);
            CheckBox cb = (CheckBox)view.findViewById(R.id.checkBox_saved);
            cb.setChecked(item.checked);
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View retView = super.getView(position,convertView,parent);
            final int pos = position;
            final View parView = retView;
            CheckBox cb = (CheckBox)retView.findViewById(R.id.checkBox_saved);
            cb.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    MyListItem item = listDispItems.get(pos);
                    item.checked = !item.checked;
                    if( item.checked ) totalSelected++;
                    item.selectedNumber=totalSelected;
                    Toast.makeText(MainActivity.this,"1: Click "+pos+ "th " + item.checked + " "+totalSelected,Toast.LENGTH_SHORT).show();
                    printDebug();
                }
            });
            TextView name = (TextView)retView.findViewById(R.id.name_saved);
            name.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    MyListItem item = listDispItems.get(pos);
                    item.checked = !item.checked;
                    if( item.checked ) totalSelected++;
                    item.selectedNumber=totalSelected;
                    Toast.makeText(MainActivity.this,"2: Click "+pos+ "th " + item.checked + " "+totalSelected,Toast.LENGTH_SHORT).show();
                    printDebug();
                    bindView(parView, item);
                }
            });

            return retView;
        }
        public void fillter(String searchText) {
            listDispItems.clear();
            totalSelected = 0;
            for(int i = 0;i<listAllItems.size();i++){
                MyListItem item = listAllItems.get(i);
                item.checked = false;
                item.selectedNumber = 0;
            }
            if(searchText.length() == 0)
            {
                listDispItems.addAll(listAllItems);
            }
            else
            {
                for( MyListItem item : listAllItems)
                {
                    if(item.name.contains(searchText))
                    {
                        listDispItems.add(item);
                    }
                }
            }
            notifyDataSetChanged();
        }
    }


    public class AdapterAsyncTask extends AsyncTask<String,Void,String> {
        private ProgressDialog mDlg;
        Context mContext;

        public AdapterAsyncTask(Context context) {
            mContext = context;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            mDlg = new ProgressDialog(mContext);
            mDlg.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            mDlg.setMessage( "loading" );
            mDlg.show();
        }
        @Override
        protected String doInBackground(String... strings) {
            // listAllItems MyListItem
            listAllItems.clear();
            listDispItems.clear();
            List<String> list = new ArrayList<String>();

            // Test item
            for(int i=0;i<100;i++){
                list.add("TEST test"+i);
            }
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // Test item

            if( list==null ) list = new ArrayList<String>();
            for(int i=0;i<list.size();i++){
                MyListItem item = new MyListItem();
                item.checked = false;
                item.name = list.get(i);
                listAllItems.add(item);
            }

            if (listAllItems != null) {
                Collections.sort(listAllItems, nameComparator);
            }
            listDispItems.addAll(listAllItems);
            return null;
        }
        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            mDlg.dismiss();
            listadapter=new MyListAdapterMy(mContext);
            listView.setAdapter(listadapter);

            String searchText = editView.getText().toString();
            if( listadapter!=null ) listadapter.fillter(searchText);
        }
        private final Comparator<MyListItem> nameComparator
                = new Comparator<MyListItem>() {
            public final int
            compare(MyListItem a, MyListItem b) {
                return collator.compare(a.name, b.name);
            }
            private final Collator collator = Collator.getInstance();
        };
    }
    private void setupAdapter() {
        AdapterAsyncTask adaterAsyncTask = new AdapterAsyncTask(MainActivity.this);
        adaterAsyncTask.execute();
    }
    private void setupFilter() {
        editView=(EditText)findViewById(R.id.savedfilter);
        editView.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }
            @Override
            public void afterTextChanged(Editable s) {
                String searchText = editView.getText().toString();
                if( listadapter!=null ) listadapter.fillter(searchText);
            }
        });
    }

    private int getSelectedItemCount() {
        int checkcnt = 0;
        for(int i=0;i<listDispItems.size();i++){
            MyListItem item = listDispItems.get(i);
            if( item.checked ) checkcnt++;
        }
        return checkcnt;
    }

    private List<String> getSelectedItems() {
        List<String> ret = new ArrayList<String>();
        int count = 0;
        for(int i=0;i<listDispItems.size();i++){
            MyListItem item = listDispItems.get(i);
            if( item.checked ) {
                if( count < item.selectedNumber ){
                    count = item.selectedNumber;
                }
            }
        }
        for(int j=1;j<=count;j++) {
            for (int i = 0; i<listDispItems.size() ;i++ ){
                MyListItem item = listDispItems.get(i);
                if( item.checked && item.selectedNumber == j){
                    ret.add(item.name);
                }
            }
        }
        return ret;
    }
    private String getSelectedItem() {
        List<String> ret = new ArrayList<String>();
        for(int i=0;i<listDispItems.size();i++){
            MyListItem item = listDispItems.get(i);
            if( item.checked ) {
                return item.name;
            }
        }
        return "";
    }
    private void printDebug() {
        StringBuilder sb = new StringBuilder();
        sb.append("Count:"+getSelectedItemCount()+"\n");
        sb.append("getSelectedItem:"+getSelectedItem()+"\n");
        sb.append("getSelectedItems:");
        List<String> data = getSelectedItems();
        for(int i=0;i<data.size();i++){
            String item = data.get(i);
            sb.append(item+",");
        }
        textView.setText(sb.toString());
    }

}



MyArrayAdapter.java
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import java.util.List;

public abstract class MyArrayAdapter<E> extends BaseAdapter
{
    public MyArrayAdapter(Context context, int layoutRes) {
        mContext = context;
        mInflater = (LayoutInflater)context.getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        mLayoutRes = layoutRes;
    }

    public void setSource(List<E> list) {
        mList = list;
    }

    public abstract void bindView(View view, E item);

    public E itemForPosition(int position) {
        if (mList == null) {
            return null;
        }

        return mList.get(position);
    }

    public int getCount() {
        return mList != null ? mList.size() : 0;
    }

    public Object getItem(int position) {
        return position;
    }

    public long getItemId(int position) {
        return position;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        View view;
        if (convertView == null) {
            view = mInflater.inflate(mLayoutRes, parent, false);
        } else {
            view = convertView;
        }
        bindView(view, mList.get(position));
        return view;
    }

    private final Context mContext;
    private final LayoutInflater mInflater;
    private final int mLayoutRes;
    private List<E> mList;
}












댓글 없음:

댓글 쓰기