RecyclerView and common quirks (item get display in random position, messed up custom animation)
Source: StackOverflow
Answer: RecyclerView
example that solve some common quirks (item get display in random position, messed up custom animation).
MediaPickerActivity.java
:
public class MediaPickerActivity extends AppCompatActivity {
private VideoView mVideoView;
private FrameLayout mVideoViewWrapper; //need to do this because portrait video can't receive touch event outside of the video
private RecyclerView mRecyclerView;
//for debugging
private static final String TAG = "MediaPickerActivity";
private List<VideoItem> mVideoItemList;
private VideoHelper mVideoHelper;
private VideoAdapter mVideoAdapter;
private String mCurrentPlayingVideo;
private Subscription mSubscription;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_media_picker);
mRecyclerView = (RecyclerView)findViewById(R.id.media_picker_recyclerView);
mRecyclerView.setLayoutManager(new GridLayoutManager(MediaPickerActivity.this, 3));
mVideoView = (VideoView)findViewById(R.id.media_picker_videoView);
mVideoViewWrapper = (FrameLayout) findViewById(R.id.media_picker_videoView_wrapper);
}
@Override
protected void onResume() {
super.onResume();
List<String> mPathList = DiskUtils.getAllVideoPath(this);
mVideoItemList = new ArrayList<>();
mVideoAdapter = new VideoAdapter(mVideoItemList);
//to prevent messed up animation when use notifyDatasetChanged(), see http://stackoverflow.com/a/29792793/1602807 for details, doesn't need step 1
mVideoAdapter.setHasStableIds(true);
mRecyclerView.setAdapter(mVideoAdapter);
mVideoHelper = new VideoHelper();
mVideoHelper.initMetaDataRetriever();
getVideoInfoInBackground(mPathList);
}
//this bit is just to retrieve video duration, can be replaced with temporary 00:00:00
private void getVideoInfoInBackground(List<String> pathList){
Observable<String> getVideoDurationObservable = Observable
.from(pathList)
.map(new Func1<String, String>() {
@Override
public String call(String path) {
VideoItem videoItem = new VideoItem();
String hms = "00:00:00";
try{
hms = mVideoHelper.getVideoDuration(path);
}catch (Exception e){
e.printStackTrace();
return "FAILURE";
}
videoItem.setDuration(hms);
videoItem.setPath(path);
mVideoItemList.add(videoItem);
return "SUCCESS";
}
});
mSubscription = getVideoDurationObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
mVideoHelper.releaseMediaMetadataRetriever();
mVideoHelper = null;
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(String s) {
if(s.equals("SUCCESS")){
mVideoAdapter.notifyDataSetChanged();
}
}
});
}
@Override
protected void onStop() {
super.onStop();
if (mSubscription != null && !mSubscription.isUnsubscribed()) {
mSubscription.unsubscribe();
}
if(mVideoView != null && mVideoView.isPlaying()){
mVideoView.stopPlayback();
mVideoViewWrapper.setVisibility(View.GONE);
}
}
private class VideoHolder extends RecyclerView.ViewHolder{
private ImageView mItemImageView;
private TextView mItemTextView;
private ImageView mSelectedImageView;
private VideoItem mItem;
public VideoHolder(final View itemView) {
super(itemView);
mItemImageView = (ImageView)itemView.findViewById(R.id.item_media_picker_imgView);
mItemTextView = (TextView)itemView.findViewById(R.id.item_media_picker_txtView);
mSelectedImageView = (ImageView)itemView.findViewById(R.id.item_media_picker_selected);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Animation clickedAnimation = AnimationUtils.loadAnimation(v.getContext(), R.anim.gallery_item_clicked);
v.startAnimation(clickedAnimation);
setupVideoView(mItem.getPath());
mSelectedImageView.setVisibility(View.VISIBLE);
if(mVideoItemList != null){
for(VideoItem item : mVideoItemList){ //remove the selected state for every item in our video list...
item.setSelected(false);
}
}
mItem.setSelected(true); //... except the current selected one
mVideoAdapter.notifyDataSetChanged();
}
});
}
public void setItem(VideoItem item) {
mItem = item;
}
}
private class VideoAdapter extends RecyclerView.Adapter<VideoHolder>{
private List<VideoItem> mItemList = new ArrayList<>();
public VideoAdapter(List<VideoItem> mList){
this.mItemList = mList;
}
public int getItemCount(){
return mItemList.size();
}
@Override //to prevent messed up animation when use notifyDatasetChanged()
public long getItemId(int position) {
return mItemList.get(position).hashCode();
}
@Override
public VideoHolder onCreateViewHolder(ViewGroup viewGroup, int position){
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_media_picker, viewGroup, false);
VideoHolder vh = new VideoHolder(v);
return vh;
}
@Override
public void onBindViewHolder(final VideoHolder holder, final int i){
final VideoItem item = mItemList.get(i);
String path = item.getPath();
Glide.with(getApplicationContext())
.load(Uri.fromFile(new File(path)))
.into(holder.mItemImageView);
holder.setItem(item);
if(item.isSelected()){
holder.mSelectedImageView.setVisibility(View.VISIBLE);
}else{
holder.mSelectedImageView.setVisibility(View.INVISIBLE);
}
holder.mItemTextView.setText(mItemList.get(i).getDuration());
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
}
private void setupVideoView(final String path){
try{
mVideoView.setVideoPath(path);
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mVideoView.start();
}
});
mVideoViewWrapper.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
startVideoActivity(path);
return true;
}
});
mVideoViewWrapper.setVisibility(View.VISIBLE);
mCurrentPlayingVideo = path;
}catch (Exception e){
e.printStackTrace();
}
}
private void startVideoActivity(String path){
Intent intent = new Intent();
intent.putExtra(RequestCode.EXTRA_VIDEO_PATH, path);
if(getIntent().getBooleanExtra(RequestCode.EXTRA_FROM_MISSION, false)){
intent.putExtra(RequestCode.EXTRA_FROM_MISSION, true);
}
setResult(RESULT_OK, intent);
finish();
}
private static class VideoItem{
private String path;
private String duration;
private boolean isSelected;
public VideoItem() {
}
public VideoItem(String path, String duration) {
this.path = path;
this.duration = duration;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getDuration() {
return duration;
}
public void setDuration(String duration) {
this.duration = duration;
}
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
}
}
activity_media_picker.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="match_parent"
android:orientation="vertical"
android:background="@color/black">
<FrameLayout
android:id="@+id/media_picker_videoView_wrapper"
android:layout_width="match_parent"
android:layout_height="200dp"
android:visibility="gone"
android:padding="5dp">
<VideoView
android:id="@+id/media_picker_videoView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"/>
</FrameLayout>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/media_picker_recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"/>
</LinearLayout>
getAllVideoPath(Context context)
:
public static ArrayList<String> getAllVideoPath(Context context) {
Uri uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
String[] projection = {MediaStore.Video.VideoColumns.DATA};
String orderBy = MediaStore.Video.Media.DATE_TAKEN;
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, orderBy + " DESC");
ArrayList<String> pathArrList = new ArrayList<String>();
//int vidsCount = 0;
if (cursor != null) {
//vidsCount = cursor.getCount();
while (cursor.moveToNext()) {
pathArrList.add(cursor.getString(0));
//Log.d(TAG, cursor.getString(0));
}
cursor.close();
}
//Log.d(TAG, "Total count of videos: " + vidsCount);
//return pathArrList.toArray(new String[pathArrList.size()]);
return pathArrList;
}
item_media_picker.xml
:
<?xml version="1.0" encoding="utf-8"?>
<!--To be enable back when it fit-->
<!--<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/gallery_item_wrapper"
android:layout_width="match_parent"
android:layout_height="120dp"
card_view:cardCornerRadius="3dp"
card_view:cardElevation="5dp"
card_view:cardUseCompatPadding="true">-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_media_picker_wrapper"
android:layout_width="match_parent"
android:layout_height="120dp"
android:padding="2dp">
<ImageView
android:id="@+id/item_media_picker_imgView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:scaleType="centerCrop">
</ImageView>
<TextView
android:id="@+id/item_media_picker_txtView"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_gravity="bottom"
android:background="@color/black_50"
android:paddingLeft="5dp"
android:text="00:00"
android:textColor="@color/white"/>
<ImageView
android:id="@+id/item_media_picker_selected"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="40dp"
android:src="@drawable/ic_done_white_24dp"
android:background="@color/black_65"
android:visibility="invisible"/>
</FrameLayout>
gallery_item_clicked.xml
put in res/anim
folder:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:fromAlpha = "1.0"
android:toAlpha = "0.5"
android:duration = "300">
</alpha>
<scale
android:fromXScale = "1"
android:toXScale = "0.9"
android:fromYScale = "1"
android:toYScale = "0.9"
android:pivotX="50%"
android:pivotY="50%"
android:duration = "50">
</scale>
</set>