无法在网格视图中显示视频缩略图 android

unable video thumbnails and show in a gridview android

我正在开发类似于 Shareit、Xender 的共享应用程序。我想在一个简单的网格视图中显示所有视频的缩略图,但加载缩略图会花费很多时间,尤其是当我有超过 1000 - 2000 个视频时。 因此,我编写了一个程序,如果尚未加载每个缩略图,则将其加载到新线程上,然后通知基本适配器。

密码是:-

package com.*.filetransfer.Server.File.Selection.SubFragments;

import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.*.filetransfer.R;
import com.*.filetransfer.Strings;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.*.filetransfer.Server.File.Selection.FileSelection.selected_item_counter_down;
import static com.*.filetransfer.Server.File.Selection.FileSelection.selected_item_counter_up;
import static com.*.filetransfer.Server.File.Selection.FileSelection.videoList;

public class VideoGalleryFragment extends Fragment {
    GridView gridView;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.gridview, container, false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        videoList = new ArrayList<Video>();
        gridView = view.findViewById(R.id.gridview);







        String[] projection = new String[] {
                MediaStore.Video.Media._ID,
                MediaStore.Video.Media.DISPLAY_NAME,
                MediaStore.Video.Media.DURATION,
                MediaStore.Video.Media.SIZE

        };
        String selection = MediaStore.Video.Media.DURATION +
                " >= ?";
        String[] selectionArgs = new String[] {
                String.valueOf(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MILLISECONDS))};
        String sortOrder = MediaStore.Video.Media.DATE_ADDED + " DESC";

        try (Cursor cursor = getContext().getApplicationContext().getContentResolver().query(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                projection,
                selection,
                selectionArgs,
                sortOrder
        )) {
            // Cache column indices.
            int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
            int nameColumn =
                    cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME);
            int durationColumn =
                    cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION);
            int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE);

            while (cursor.moveToNext()) {
                // Get values of columns for a given video.
                long id = cursor.getLong(idColumn);
                String name = cursor.getString(nameColumn);
                int duration = cursor.getInt(durationColumn);
                long size = cursor.getLong(sizeColumn);
                Uri contentUri = ContentUris.withAppendedId(
                        MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);

                // Stores column values and the contentUri in a local object
                // that represents the media file.





                videoList.add(new Video(contentUri, name, duration, size, null, false, false));

            }
            gridView.setAdapter(new GalleryGridViewAdapter(requireContext(), videoList){
                @Override
                public View getView(final int position, View convertView, ViewGroup parent) {


                    ImageView imageView = (ImageView) convertView;
                    if (imageView == null){
                        imageView = new ImageView(requireContext());
                        imageView.setLayoutParams(new GridView.LayoutParams(300,300));
                        imageView.setScaleType(ImageView.ScaleType.FIT_XY);
                        imageView.setPadding(20,20,20,20);

                    }

                    if (( (Video) getItem(position)).isChecked()){
                        imageView.setBackgroundColor(getResources().getColor(R.color.aqua));
                    }
                    else{
                        imageView.setBackgroundColor(Color.TRANSPARENT);
                    }


                    if (((Video)getItem(position)).getThumbnail() == null && (!((Video)getItem(position)).isImageLoading())){
                        ((Video)getItem(position)).setImageLoading(true);
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                String[] filePathColumn = {MediaStore.Images.Media.DATA};
                                Cursor cursor1 = requireContext().getContentResolver().query(((Video)getItem(position)).getUri(), filePathColumn, null, null, null);
                                cursor1.moveToFirst();
                                int columnIndex = cursor1.getColumnIndex(filePathColumn[0]);
                                String picturePath = cursor1.getString(columnIndex);
                                cursor1.close();

                                Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(picturePath, MediaStore.Video.Thumbnails.MINI_KIND);
                                ((Video)getItem(position)).setThumbnail(bitmap);
                                ((Video)getItem(position)).setImageLoading(true);
                                try {
                                    requireActivity().runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            notifyDataSetChanged();
                                        }
                                    });
                                }
                                catch (IllegalStateException ignored){

                                }
                            }
                        }).start();
                    }
                    if (((Video)getItem(position)).getThumbnail() != null)
                        imageView.setImageBitmap(((Video)getItem(position)).getThumbnail());


                    return imageView;
                }

            });


        }

        gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (!videoList.get(position).isChecked()) {
                    videoList.get(position).setChecked(true);
                    selected_item_counter_up();
                    Log.e(Strings.TAG, String.valueOf(videoList.get(position).getSize()));
                    try {
                        ((ImageView) view).setBackgroundColor(getResources().getColor(R.color.aqua));
                    } catch (Exception e) {

                    }
                }
                else{
                    videoList.get(position).setChecked(false);
                    selected_item_counter_down();
                    try {
                        ((ImageView) view).setBackgroundColor(Color.TRANSPARENT);
                    } catch (Exception e) {
                    }
                }
            }
        });


    }
}

class GalleryGridViewAdapter extends BaseAdapter {
    private List<Video> videos;
    private Context context;

    public GalleryGridViewAdapter(Context context, List<Video> videoList) {
        this.context = context;
        this.videos = videoList;
    }

    @Override
    public int getCount() {
        return videos.size();
    }

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

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return null;
    }
}

上面导入的静态方法和变量是:-

public static List<Video> videoList;
public static void selected_item_counter_up(){
        selected_counter++;
        view_counter.setText("Selected (" + selected_counter + ")");
    }
    public static void selected_item_counter_down(){
        selected_counter--;
        view_counter.setText("Selected (" + selected_counter + ")");
    }

我的视频class简单如下:-

package com.*.filetransfer.Server.File.Selection.SubFragments;

import android.graphics.Bitmap;
import android.net.Uri;

public class Video {
    private final Uri uri;
    private final String name;
    private final int duration;
    private final long size;
    private Bitmap thumbnail;
    private boolean isImageLoading;
    private boolean isChecked;

    public Video(Uri uri, String name, int duration, long size, Bitmap bitmap, boolean isImageLoading, boolean isChecked) {
        this.uri = uri;
        this.name = name;
        this.duration = duration;
        this.size = size;
        this.thumbnail = bitmap;
        this.isImageLoading = isImageLoading;
        this.isChecked = isChecked;
    }

    public boolean isChecked() {
        return isChecked;
    }

    public boolean isImageLoading() {
        return isImageLoading;
    }

    public Uri getUri() {
        return uri;
    }

    public String getName() {
        return name;
    }

    public int getDuration() {
        return duration;
    }

    public long getSize() {
        return size;
    }

    public Bitmap getThumbnail() {
        return thumbnail;
    }


    public void setThumbnail(Bitmap thumbnail) {
        this.thumbnail = thumbnail;
    }

    public void setImageLoading(boolean imageLoading) {
        isImageLoading = imageLoading;
    }

    public void setChecked(boolean checked) {
        isChecked = checked;
    }
}

我面临的问题是,如果用户滚动得太快,大部分缩略图都会混淆,有些会加载多次。谁能发现这个漏洞并改正它。

如果可以,请建议另一种方法来加载和显示设备上的媒体缩略图,如果可能的话按日期分隔,因为我不知道该怎么做

编辑

我切换到 recyclerview 并且它滚动非常流畅,但由于某种原因它正在滚动到顶部。这是更新后的代码:-

package com.*.filetransfer.Server.File.Selection.SubFragments;

import android.graphics.Color;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.Glide;
import com.sugarsnooper.filetransfer.R;
import com.*.filetransfer.Server.File.Selection.Media;
import com.*.filetransfer.Server.Send_Activity;

import java.io.File;
import java.util.ArrayList;
import java.util.Map;

import static com.*.filetransfer.Server.File.Selection.FileSelection.imageList;


public class Photos extends Fragment {
    RecyclerView gridView;
    GridAdapter ga = new GridAdapter();
    private int max_padding = 20;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.gridview, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);


        new Thread(new Runnable() {
            @Override
            public void run() {

                imageList = new ArrayList<>();

                for (Map.Entry<String, Long> entry : Send_Activity.readableRoots.getImages().entrySet())
                {
                    File file = new File(entry.getKey());
                    imageList.add(new Media(Uri.fromFile(file), file.getName(), file.length(), entry.getValue()));
                }
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        gridView = view.findViewById(R.id.gridview);
                        gridView.setLayoutManager(new GridLayoutManager(requireContext(), 3));
                        gridView.setAdapter(ga);
                        gridView.addItemDecoration(new SpacesItemDecoration(30));
                        

                    }
                });
            }
        }).start();

        

    }

    
    class SpacesItemDecoration extends RecyclerView.ItemDecoration {
        private int space;

        public SpacesItemDecoration(int space) {
            this.space = space;
        }

        @Override
        public void getItemOffsets(Rect outRect, View view,
                                   RecyclerView parent, RecyclerView.State state) {
            outRect.left = space;
            outRect.right = space;
            outRect.bottom = space;

            // Add top margin only for the first item to avoid double space between items
            if (parent.getChildLayoutPosition(view) == 0) {
                outRect.top = space;
            } else {
                outRect.top = 0;
            }
        }
    }

    class GridAdapter extends RecyclerView.Adapter<GridAdapter.MyViewHolder> {

        @NonNull
        @Override
        public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

            View layout = LayoutInflater.from(requireActivity()).inflate(R.layout.grid_item, parent, false);
            return new MyViewHolder(layout);
        }

        @Override
        public void onBindViewHolder(@NonNull MyViewHolder holder, int i) {
            if (((Media) getItem(i)).isChecked()) {
                    holder.ivparent.setPadding(max_padding, max_padding, max_padding, max_padding);
                    holder.ivparent.setBackgroundColor(Color.parseColor("#777777"));
                }
                else{
                    holder.ivparent.setPadding(0, 0, 0, 0);
                    holder.ivparent.setBackgroundColor(Color.parseColor("#00777777"));
                }

            Glide.with(requireActivity()).fromUri().dontAnimate().load(((Media) getItem(i)).getUri()).into(holder.imageView);
        }

        @Override
        public int getItemCount() {
            return imageList.size();
        }

        @Override
        public void onViewRecycled(@NonNull MyViewHolder holder) {
            super.onViewRecycled(holder);
            holder.recycle();
        }

        public class MyViewHolder extends RecyclerView.ViewHolder {
            LinearLayout ivparent;
            ImageView imageView;
            public MyViewHolder(@NonNull View itemView) {
                super(itemView);
                 imageView = (ImageView) itemView.findViewById(R.id.imageView);
                 ivparent = itemView.findViewById(R.id.iv_parent);
            }


            public void recycle() {
                Glide.clear(imageView);
            }
        }

        public Object getItem(int i) {
            return imageList.get(i);
        }

    }

}
  • 为了实现相同的行为,我使用了 Recyclerview + GridLayoutManager

  • 并在 onBindViewHolder 内使用 Glide 从视频 uri 加载缩略图。

  • 因此仅创建当前加载到屏幕中的项目的缩略图,并处理异步加载和清除视图

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
           // Code for loading thumbnail using video file path
          Glide.with(context)
               .load(Uri.fromFile(new File(videoFilePath)))
               .thumbnail(0.5f)
               .into(imageView);
    }