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 의 장점(?)을 파악해보련다.