728x90

RecyclerView는 롤리팝 버전에서 새로이 등장한 위젯으로 안드로이드 ListView의 장/단점을 보완했다.

RecyclerView

ListView

 수직 스크롤, 수평 스크롤 지원

 수직 스크롤 지원

 ViewHolder 패턴 강제 적용

 (재활용 View 를 관리해주는 Class)

 ViewHolder 패턴 선택 적용

 Animation 적용이 쉽다
 (ItemAnimation : Item 항목의 Animation 관리 Class)

 Animation 적용이 어렵다


Target API에 따라서 숫자가 변경되니 Google SupportLibrary 사이트에서 확인해야 한다.
지원 라이브러리에 대한 내용은 https://developer.android.com/topic/libraries/support-library/features.html#v7 를 참조하면 도움된다.


Audio Player 에서 음악파일을 ListView 에 보여주는 걸 RecyclerView 로 변경하는 걸 해보고 신경써서 살펴볼 것만  적어둔다.

- 체크박스 선택 기능 및 전체 선택/해제 기능

- 버튼을 길게 누르면 체크박스를 선택할 수 있는 기능 (다중 선택)


아래 기술된 사항은 체크박스가 들어간 RecylcerView 라고 보면 된다.

보통 ListView 예제를 찾아보면 체크박스 선택하는 기능이 추가된 것은 찾기가 어렵다. 체크박스 선택 기능이 들어가면 코드 구현시 주의해야 할 사항이 있다.

아래 코드에는 ListView, RecyclerView 모두 체크박스 처리 부분이 들어가 있는데, RecyclerView 를 완벽하게 활용하지 못하므로 코드가 매끄럽지 못할 수 있다.


가장 먼저 앱 build.gradle 을 다음과 같이 추가해줬다. 이건 어디까지나 참고사항이므로 꼭 이대로 할 필요는 없다.

compileSdkVersion 23 으로 변경해서 테스트를 했다. 버전이 너무 높다고 좋은게 아닌거 같아서다.


apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "25.0.0"

    defaultConfig {
        applicationId "com.tistory.link2me.recyclerview"
        minSdkVersion 19
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.squareup.okhttp3:okhttp:3.9.0' // 서버와의 통신 라이브러리
    compile 'com.squareup.picasso:picasso:2.5.2' // 이미지 라이브러리
    compile 'com.android.support:recyclerview-v7:24.2.0' // ListView 개선 버전
    compile "com.android.support:cardview-v7:24.2.0"
    compile "com.android.support:design:24.2.0" // snack bar
}


dependencies 부분은 필요한 부분만 추가하면 된다.

RecyclerView 부분은 compile 'com.android.support:recyclerview-v7:24.2.0' 만 추가하면 된다.



activity_main.xml 파일에서 변경된 부분

ListView 부분이 RecyclerView 위젯으로 변경하면 된다. (색깔로 표시한 부분)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <RelativeLayout
        android:id="@+id/list_view_relative2"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/whitegray" >

        <CheckBox
            android:id="@+id/lv_checkbox_all"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:text="전체선택"
            android:textColor="#000000"
            android:textStyle="bold" />

        <Button
            android:id="@+id/btn_cancle"
            android:layout_width="51dp"
            android:layout_height="33dp"
            android:layout_marginLeft="10dp"
            android:layout_centerVertical="true"
            android:background="@drawable/btn_send_cancel" />

        <Button
            android:id="@+id/btn_send"
            android:layout_width="51dp"
            android:layout_height="33dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="30dp"
            android:layout_toRightOf="@+id/btn_cancle"
            android:background="@drawable/btn_send_act" />

    </RelativeLayout>

    <ListView
        android:id="@+id/my_listView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

 <?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="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/list_view_relative2"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/whitegray" >

        <CheckBox
            android:id="@+id/lv_checkbox_all"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:text="전체선택"
            android:textColor="#000000"
            android:textStyle="bold" />

        <Button
            android:id="@+id/btn_cancle"
            android:layout_width="51dp"
            android:layout_height="33dp"
            android:layout_marginLeft="10dp"
            android:layout_centerVertical="true"
            android:background="@drawable/btn_send_cancel" />

        <Button
            android:id="@+id/btn_send"
            android:layout_width="51dp"
            android:layout_height="33dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="30dp"
            android:layout_toRightOf="@+id/btn_cancle"
            android:background="@drawable/btn_send_act" />

    </RelativeLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"/>

</LinearLayout>


MainActivity.java 파일

변수 선언 부분

   private ListView audiolistView; // 리스트뷰
    private ArrayList<Song_Item> songsList = null; // 음악 전체 데이터 리스트
    private ArrayList<Song_Item> playList = new ArrayList<>(); // 선택한 음악 데이터 리스트
    private ListViewAdapter listViewAdapter = null; // 리스트뷰에 사용되는 ListViewAdapter

    RecyclerView audiolistView; // 리스트뷰 (ListView 대신 RecyclerView)
    private ArrayList<Song_Item> songsList = new ArrayList<>(); // 음악 전체 데이터 리스트
    private ArrayList<Song_Item> playList = new ArrayList<>(); // 선택한 음악 데이터 리스트
    RecyclerView.Adapter listViewAdapter; // ListViewAdapter 대신 RecyclerView.Adapter
    RecyclerView.LayoutManager layoutManager;


protected void onCreate(Bundle savedInstanceState) 부분

       // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        songsList = new ArrayList<Song_Item>(); // ArrayList 생성
        audiolistView = (ListView) findViewById(R.id.my_listView);
        listViewAdapter = new ListViewAdapter(this); // Adapter 생성
        audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅
        audiolistView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

         // Adapter에 추가 데이터를 저장하기 위한 ArrayList
        audiolistView = (RecyclerView) findViewById(R.id.recyclerview);
        audiolistView.setHasFixedSize(true);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        audiolistView.setLayoutManager(layoutManager);

        listViewAdapter = new ListViewAdapter(songsList,this); // Adapter 생성
        audiolistView.setAdapter(listViewAdapter); // 어댑터를 리스트뷰에 세팅


체크박스 전체 선택/취소 버튼 부분

ListView 에서는 ListViewAdapter 부분에 전체 선택/해제 할 수 있는 기능을 넣어도 문제가 없었는데,

에러가 발생해서 public void setAllChecked(final boolean ischeked) 메소드 위치를 변경해서 해결했다.

        // all checkbox
        checkAll = (CheckBox) findViewById(R.id.lv_checkbox_all);
        checkAll.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (checkAll.isChecked() == true) {
                    isAll = true;
                    listViewAdapter.setAllChecked(checkAll.isChecked());
                    listViewAdapter.notifyDataSetChanged();
                } else {
                    isAll = false;
                    listViewAdapter.setAllChecked(checkAll.isChecked());
                    listViewAdapter.notifyDataSetChanged();
                }
            }
        });

        final Button calcel = (Button) findViewById(R.id.btn_cancle);
        calcel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                isMSG = false;
                relative2.setVisibility(View.GONE);
                listViewAdapter.setAllChecked(false);
                checkAll.setChecked(false); // 전체 선택 체크박스 해제
                listViewAdapter.notifyDataSetChanged();
            }
        });

         // all checkbox
        checkAll = (CheckBox) findViewById(R.id.lv_checkbox_all);
        checkAll.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (checkAll.isChecked() == true) {
                    isAll = true;
                    setAllChecked(checkAll.isChecked());
                    listViewAdapter.notifyDataSetChanged();
                } else {
                    isAll = false;
                    setAllChecked(checkAll.isChecked());
                    listViewAdapter.notifyDataSetChanged();
                }
            }
        });

        final Button calcel = (Button) findViewById(R.id.btn_cancle);
        calcel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                isMSG = false;
                relative2.setVisibility(View.GONE);
                setAllChecked(false);
                checkAll.setChecked(false); // 전체 선택 체크박스 해제
                listViewAdapter.notifyDataSetChanged();
            }
        });


음악데이터 추가 메소드도 위치를 변경해서 해결했다.


ListView 기반 ViewHolder 와 ListViewAdapter 부분

    class ViewHolder {
        public ImageView mImgAlbumArt;
        public TextView mTitle;
        public TextView mSubTitle;
        public TextView mDuration;
        public CheckBox checkbox;
    }

    private class ListViewAdapter extends BaseAdapter {
        Context context;
        public final Uri artworkUri = Uri.parse("content://media/external/audio/albumart");

        public ListViewAdapter(Context context) {
            this.context = context;
        }

        // 음악 데이터 추가를 위한 메소드
        public void addItem(long mId, long AlbumId, String Title, String Artist, String Album, Integer Duration, String DataPath, boolean checkItem_flag) {
            Song_Item item = new Song_Item();
            item.setmId(mId);
            item.setAlbumId(AlbumId);
            item.setTitle(Title);
            item.setArtist(Artist);
            item.setAlbum(Album);
            item.setDuration(Duration);
            item.setDataPath(DataPath);
            item.setCheckBoxState(checkItem_flag);
            songsList.add(item);
        }

        // CheckBox를 모두 선택하는 메서드
        public void setAllChecked(final boolean ischeked) {
            final int tempSize = songsList.size();
            if (isAll == true) {
                for (int i = 0; i < tempSize; i++) {
                    songsList.get(i).setCheckBoxState(true);
                }
            } else {
                for (int i = 0; i < tempSize; i++) {
                    songsList.get(i).setCheckBoxState(false);
                }
            }
        }

        @Override
        public int getCount() {
            return songsList.size(); // 데이터 개수 리턴
        }

        @Override
        public Object getItem(int position) {
            return songsList.get(position);
        }

        // 지정한 위치(position)에 있는 데이터 리턴
        @Override
        public long getItemId(int position) {
            return position;
        }

        // position에 위치한 데이터를 화면에 출력하는데 사용될 View를 리턴
        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {
            final ViewHolder viewHolder;
            final Context context = parent.getContext();
            final Integer index = Integer.valueOf(position);

            // 화면에 표시될 View
            if (convertView == null) {
                viewHolder = new ViewHolder();

                LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = inflater.inflate(R.layout.song_item, parent, false);

                convertView.setBackgroundColor(0x00FFFFFF);
                convertView.invalidate();

                // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
                viewHolder.mImgAlbumArt = (ImageView) convertView.findViewById(R.id.album_Image);
                viewHolder.mTitle = (TextView) convertView.findViewById(R.id.txt_title);
                viewHolder.mSubTitle = (TextView) convertView.findViewById(R.id.txt_subtitle);
                viewHolder.mDuration = (TextView) convertView.findViewById(R.id.txt_duration);
                viewHolder.checkbox = (CheckBox) convertView.findViewById(R.id.list_cell_checkbox);

                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            // Song_Item 에서 position 에 위치한 데이터 참조 획득
            final Song_Item songItem = songsList.get(position);

            // 아이템 내 각 위젯에 데이터 반영
            Uri albumArtUri = ContentUris.withAppendedId(artworkUri, songItem.getAlbumId());
            Picasso.with(convertView.getContext()).load(albumArtUri).error(R.drawable.ic_launcher).into(viewHolder.mImgAlbumArt);
            viewHolder.mTitle.setText(songItem.getTitle());
            viewHolder.mSubTitle.setText(songItem.getArtist());

            int dur = (int) songItem.getDuration();
            int hrs = (dur / 3600000);
            int mns = (dur / 60000) % 60000;
            int scs = dur % 60000 / 1000;
            String songTime = String.format("%02d:%02d:%02d", hrs, mns, scs);
            if (hrs == 0) {
                songTime = String.format("%02d:%02d", mns, scs);
            }
            viewHolder.mDuration.setText(songTime);

            if (isMSG == false) {
                viewHolder.checkbox.setVisibility(View.GONE);
                convertView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        playList.clear();
                        PlayList(songsList.get(position).getmId(), songsList.get(position).getAlbumId(), songsList.get(position).getTitle(), songsList.get(position).getArtist(),
                                songsList.get(position).getAlbum(), songsList.get(position).getDuration(), songsList.get(position).getDataPath());

                        System.out.println("position=" + position + " 노래제목 : " + songsList.get(position).getTitle());

                        Intent intent = new Intent(MainActivity.this, Player.class);
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | intent.FLAG_ACTIVITY_CLEAR_TOP);
                        intent.putExtra("playList", playList); // 배열 데이터
                        startActivity(intent);
                    }
                });

                convertView.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View view) {
                        isMSG = true;
                        relative2.setVisibility(View.VISIBLE);
                        listViewAdapter.notifyDataSetChanged();
                        return false;
                    }
                });

            } else {
                if (isMSG == true) {
                    viewHolder.checkbox.setVisibility(View.VISIBLE);
                    viewHolder.checkbox.setTag(position); // This line is important.

                    viewHolder.checkbox.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (songsList.get(position).isCheckBoxState() == true) {
                                songsList.get(position).setCheckBoxState(false);
                            } else {
                                songsList.get(position).setCheckBoxState(true);
                            }
                        }
                    });

                    convertView.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (viewHolder.checkbox.isChecked() == false) {
                                viewHolder.checkbox.setChecked(true);
                                songsList.get(position).setCheckBoxState(true);
                            } else {
                                viewHolder.checkbox.setChecked(false);
                                songsList.get(position).setCheckBoxState(false);
                            }
                        }
                    });

                }
            }

            // 재사용 문제 해결
            if (songsList.get(position).isCheckBoxState() == true) {
                viewHolder.checkbox.setChecked(true);
            } else {
                viewHolder.checkbox.setChecked(false);
            }

            return convertView;
        }

    }


RecyclerView 기반 ListViewAdapter 부분

    class ListViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        Context mContext;
        private ArrayList<Song_Item> songsList;
        Song_Item songItem;
        int Position;

        public final Uri artworkUri = Uri.parse("content://media/external/audio/albumart");

        public ListViewAdapter(ArrayList<Song_Item> items,Context context) {
            songsList = items;
            mContext = context;
        }

        public class ViewHolder extends RecyclerView.ViewHolder  {
            public ImageView mImgAlbumArt;
            public TextView mTitle;
            public TextView mSubTitle;
            public TextView mDuration;
            public CheckBox checkbox;

            public ViewHolder(final View itemView) {
                super(itemView);
                // 화면에 표시될 View 로부터 위젯에 대한 참조 획득
                mImgAlbumArt = (ImageView) itemView.findViewById(R.id.album_Image);
                mTitle = (TextView) itemView.findViewById(R.id.txt_title);
                mSubTitle = (TextView) itemView.findViewById(R.id.txt_subtitle);
                mDuration = (TextView) itemView.findViewById(R.id.txt_duration);
                checkbox = (CheckBox) itemView.findViewById(R.id.list_cell_checkbox);

                itemView.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View view) {
                        isMSG = true;
                        relative2.setVisibility(View.VISIBLE);
                        listViewAdapter.notifyDataSetChanged();
                        return false;
                    }
                });

                itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Position = getAdapterPosition();
                        if (isMSG == false) {
                            playList.clear();
                            PlayList(songsList.get(Position).getmId(), songsList.get(Position).getAlbumId(), songsList.get(Position).getTitle(), songsList.get(Position).getArtist(),
                                    songsList.get(Position).getAlbum(), songsList.get(Position).getDuration(), songsList.get(Position).getDataPath());

                            System.out.println("position=" + Position + " 노래제목 : " + songsList.get(Position).getTitle());

                            Intent intent = new Intent(MainActivity.this, Player.class);
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | intent.FLAG_ACTIVITY_CLEAR_TOP);
                            intent.putExtra("playList", playList); // 배열 데이터
                            mContext.startActivity(intent);
                        } else {
                            //Toast.makeText(mContext, ""+Position, Toast.LENGTH_SHORT).show();
                            if (checkbox.isChecked() == false) {
                                checkbox.setChecked(true);
                                songsList.get(Position).setCheckBoxState(true);
                            } else {
                                checkbox.setChecked(false);
                                songsList.get(Position).setCheckBoxState(false);
                            }
                        }

                    }
                });

            }

            public void setAudioItem(Song_Item item, final int position) {
                songItem = item;
                // 아이템 내 각 위젯에 데이터 반영
                Uri albumArtUri = ContentUris.withAppendedId(artworkUri, songItem.getAlbumId());
                Picasso.with(mContext).load(albumArtUri).error(R.drawable.ic_launcher).into(mImgAlbumArt);
                mTitle.setText(songItem.getTitle());
                mSubTitle.setText(songItem.getArtist());

                int dur = (int) songItem.getDuration();
                int hrs = (dur / 3600000);
                int mns = (dur / 60000) % 60000;
                int scs = dur % 60000 / 1000;
                String songTime = String.format("%02d:%02d:%02d", hrs, mns, scs);
                if (hrs == 0) {
                    songTime = String.format("%02d:%02d", mns, scs);
                }
                mDuration.setText(songTime);

                if (isMSG == false) {
                    checkbox.setVisibility(View.GONE);
                } else {
                    if (isMSG == true) {
                        checkbox.setVisibility(View.VISIBLE);
                        checkbox.setTag(position); // This line is important.
                        checkbox.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                //Toast.makeText(mContext, ""+position, Toast.LENGTH_SHORT).show();
                                if (songsList.get(position).isCheckBoxState() == true) {
                                    songsList.get(position).setCheckBoxState(false);
                                } else {
                                    songsList.get(position).setCheckBoxState(true);
                                }
                            }
                        });
                    }
                }

                // 재사용 문제 해결
                if (songsList.get(position).isCheckBoxState() == true) {
                    checkbox.setChecked(true);
                } else {
                    checkbox.setChecked(false);
                }
            }

        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            // 새로운 뷰를 만든다.
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.song_item,parent,false);
            ViewHolder viewHolder = new ViewHolder(itemView);
            return viewHolder;
        }

        @Override
        public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
            // ListView의 getView 부분을 담당하는 메소드
            // Song_Item 에서 position 에 위치한 데이터 참조 획득
            songItem = songsList.get(position);
            ((ViewHolder) holder).setAudioItem(songItem,position);
        }

        @Override
        public int getItemCount() {
            return songsList.size(); // 데이터 개수 리턴
        }

    }


이런 부분을 수정해서 RecyclerView 기반으로 변경하는데 성공했다.

좀 더 다른 예제도 활용해서 테스트를 해보고 RecyclerView 의 장점(?)을 파악해보련다.

블로그 이미지

Link2Me

,