vxh.viet
7/8/2016 - 6:42 AM

RecyclerView and common quirks (item get display in random position, messed up custom animation)

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>