在recyclerview中使用textureview播放视频

Posted

技术标签:

【中文标题】在recyclerview中使用textureview播放视频【英文标题】:Playing video using textureview in recyclerview 【发布时间】:2015-09-07 18:59:21 【问题描述】:

我正在尝试使用 vine 或 Instagram 应用等视频来实现列表。当列表项显示或完全可见时,他们播放视频的位置,当列表项被隐藏时,视频暂停。我正在使用带有媒体播放器的纹理视图来播放来自 url 的视频,并将其添加为 recyclerview 中的列表项。以下是我的代码。

VideosAdapter 类:

public class VideosAdapter extends RecyclerView.Adapter<VideosAdapter.ViewHolder> 

Context context;
private ArrayList<String> urls;

public static class ViewHolder extends RecyclerView.ViewHolder 

    public LinearLayout layout;
    public TextView textView;

    public ViewHolder(View v) 
        super(v);
        layout = (LinearLayout) v.findViewById(R.id.linearLayout);
        textView = (TextView) v.findViewById(R.id.textView);
    


public VideosAdapter(Context context, ArrayList<String> urls) 
    this.context = context;
    this.urls = urls;


// Create new views (invoked by the layout manager)
@Override
public VideosAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 
    // create a new view
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_main, parent, false);
    ViewHolder viewHolder = new ViewHolder(v);
    return viewHolder;


// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) 

    String url = urls.get(position);
    holder.textView.setText(url);
    playVideo(holder, url);


@Override
public int getItemCount() 
    return urls.size();


private void playVideo(ViewHolder holder, String url)

    final CustomVideoPlayer vid = new CustomVideoPlayer(String.valueOf(url), context);
    holder.layout.addView(vid);
    holder.layout.setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View v) 

            vid.changePlayState();
        
    );


CustomVideoPlayer 类:

public class CustomVideoPlayer extends  TextureView implements TextureView.SurfaceTextureListener


Context context;
String url;
MediaPlayer mp;
Surface surface;
SurfaceTexture s;

public CustomVideoPlayer(Context context, AttributeSet attrs)

    super(context, attrs);
    this.context = context;


public CustomVideoPlayer(String ur, Context context)

    super(context);
    this.setSurfaceTextureListener(this);
    this.url = ur;
    this.context = context;



@Override
public void onSurfaceTextureAvailable(final SurfaceTexture surface, int arg1, int arg2) 

    this.s = surface;
    Log.d("url", this.url);
    startVideo(surface);

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture arg0) 

    return true;

@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture arg0, int arg1,int arg2) 

@Override
public void onSurfaceTextureUpdated(SurfaceTexture arg0) 


public void setVideo(String url)

    this.url = url;


public void startVideo(SurfaceTexture t)

    this.surface = new Surface(t);
    this.mp = new MediaPlayer();
    this.mp.setSurface(this.surface);
    try 
        Uri uri = Uri.parse(this.url);
        this.mp.setDataSource(url);
        this.mp.prepareAsync();

        this.mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() 
            public void onPrepared(MediaPlayer mp) 

                mp.setLooping(true);
                mp.start();

            
        );
     catch (IllegalArgumentException e1) 
        e1.printStackTrace();
     catch (SecurityException e1) 
        e1.printStackTrace();
     catch (IllegalStateException e1) 
        e1.printStackTrace();
     catch (IOException e1) 
        e1.printStackTrace();
    
    try 

     catch (IllegalArgumentException e) 
        e.printStackTrace();
     catch (SecurityException e) 
        e.printStackTrace();
     catch (IllegalStateException e) 
        e.printStackTrace();
    
    try 

     catch (IllegalStateException e) 
        e.printStackTrace();
    




public void changePlayState()

    if(this.mp.isPlaying())
        this.mp.pause();
    else
        this.mp.start();


当我运行此代码时,其中存在多个问题。

1) 前两个项目/视频缓冲并播放正常。但是当我滚动它时,它不会加载第三个视频,并且第一个视频也会从列表中删除。

2) 在滚动视频/列表项时,开始为已缓冲的项目再次缓冲。

3) 在快速滚动列表中会变得太迟钝,卡住并崩溃。

附件是我在列表滚动和视频播放时得到的 logcat 图像。

谁能指导我完成这个?像 vine app 这样创建列表的正确方法是什么?

【问题讨论】:

对同时播放的视频数量存在设备限制。确保在开始播放新视频之前停止播放上一个视频。 “M”版本计划添加一个 API,它会告诉您限制是什么——developer.android.com/preview/api-overview.html#video。 感谢您的回复。我添加了 3 个网址来播放。但没有开始播放并评论了这一行 mp.start();从 startVideo(surface) 方法开始。当我滚动时,我遇到了 listview 的问题卡住了 5-6 秒。为什么我会收到此问题如果此问题与播放或一次播放多个视频有关,因为现在我正在准备所有视频但尚未开始播放。 【参考方案1】:

我能够通过首先从 url 下载视频然后使用自定义播放器播放来实现这一点。如果其他人需要,我会这样做:

1) 获取所有需要播放的url

2) 开始从本地存储中的 url 下载视频(在队列中),并在首选项中保留一个标志(视频是否已下载)

3) 将 url 分配给 Adapter,在其中初始化处理视频播放的视频播放器控制器的对象

4) 设置 addOnScrollListener 以检查当前可见的位置/视频,并检查视频是否已经下载,如果是,则播放它。

以下是完整代码:

MainActivity

public class MainActivity extends ActionBarActivity implements IVideoDownloadListener 

private static String TAG = "MainActivity";

private Context context;
private RecyclerView mRecyclerView;
private ProgressBar progressBar;
private VideosAdapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private ArrayList<Video> urls;
VideosDownloader videosDownloader;

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    context = MainActivity.this;
    mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
    progressBar = (ProgressBar) findViewById(R.id.progressBar);
    urls = new ArrayList<Video>();
    mRecyclerView.setHasFixedSize(true);
    mLayoutManager = new LinearLayoutManager(this);
    mRecyclerView.setLayoutManager(mLayoutManager);
    mAdapter = new VideosAdapter(MainActivity.this, urls);
    mRecyclerView.setAdapter(mAdapter);

    videosDownloader = new VideosDownloader(context);
    videosDownloader.setOnVideoDownloadListener(this);

    if(Utils.hasConnection(context))
    
        getVideoUrls();

        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() 
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) 
                super.onScrolled(recyclerView, dx, dy);
            

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) 
                super.onScrollStateChanged(recyclerView, newState);

                if (newState == RecyclerView.SCROLL_STATE_IDLE) 

                    LinearLayoutManager layoutManager = ((LinearLayoutManager) recyclerView.getLayoutManager());
                    int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
                    int findFirstCompletelyVisibleItemPosition = layoutManager.findFirstCompletelyVisibleItemPosition();

                    Video video;
                    if (urls != null && urls.size() > 0)
                    
                        if (findFirstCompletelyVisibleItemPosition >= 0) 
                            video = urls.get(findFirstCompletelyVisibleItemPosition);
                            mAdapter.videoPlayerController.setcurrentPositionOfItemToPlay(findFirstCompletelyVisibleItemPosition);
                            mAdapter.videoPlayerController.handlePlayBack(video);
                        
                        else
                        
                            video = urls.get(firstVisiblePosition);
                            mAdapter.videoPlayerController.setcurrentPositionOfItemToPlay(firstVisiblePosition);
                            mAdapter.videoPlayerController.handlePlayBack(video);
                        
                    
                
            
        );
    
    else
        Toast.makeText(context, "No internet available", Toast.LENGTH_LONG).show();


@Override
public void onVideoDownloaded(Video video) 
    mAdapter.videoPlayerController.handlePlayBack(video);


private void getVideoUrls()

    Video video1 = new Video("0", "1", "http://techslides.com/demos/sample-videos/small.mp4");
    urls.add(video1);
    Video video2 = new Video("1", "2", "http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4");
    urls.add(video2);
    Video video3 = new Video("2", "3", "http://sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4");
    urls.add(video3);
    Video video4 = new Video("3", "4", "http://dev.exiv2.org/attachments/341/video-2012-07-05-02-29-27.mp4");
    urls.add(video4);
    Video video5 = new Video("4", "5", "http://techslides.com/demos/sample-videos/small.mp4");
    urls.add(video5);
    Video video6 = new Video("5", "6", "http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4");
    urls.add(video6);
    Video video7 = new Video("6", "7", "http://sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4");
    urls.add(video7);

    mAdapter.notifyDataSetChanged();
    progressBar.setVisibility(View.GONE);
    videosDownloader.startVideosDownloading(urls);
 

视频适配器

public class VideosAdapter extends RecyclerView.Adapter<VideosAdapter.ViewHolder> 

private static String TAG = "VideosAdapter";

Context context;
private ArrayList<Video> urls;
public VideoPlayerController videoPlayerController;

public static class ViewHolder extends RecyclerView.ViewHolder 

    public TextView textView;
    public ProgressBar progressBar;
    public RelativeLayout layout;

    public ViewHolder(View v) 
        super(v);
        layout = (RelativeLayout) v.findViewById(R.id.layout);
        textView = (TextView) v.findViewById(R.id.textView);
        progressBar = (ProgressBar) v.findViewById(R.id.progressBar);

    


public VideosAdapter(Context context, final ArrayList<Video> urls) 

    this.context = context;
    this.urls = urls;
    videoPlayerController = new VideoPlayerController(context);


// Create new views (invoked by the layout manager)
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 
    // create a new view
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_main, parent, false);

    Configuration configuration = context.getResources().getConfiguration();
    int screenWidthDp = configuration.screenWidthDp; //The current width of the available screen space, in dp units, corresponding to screen width resource qualifier.
    int smallestScreenWidthDp = configuration.smallestScreenWidthDp; //The smallest screen size an application will see in normal operation, corresponding to smallest screen width resource qualifier.

    ViewHolder viewHolder = new ViewHolder(v);

    int screenWidthPixels = Utils.convertDpToPixel(screenWidthDp, context);
    RelativeLayout.LayoutParams rel_btn = new RelativeLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT, screenWidthPixels);
    viewHolder.layout.setLayoutParams(rel_btn);

    return viewHolder;


// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(final ViewHolder holder, int position) 

    Video video = urls.get(position);
    holder.textView.setText("Video " + video.getId());

    final VideoPlayer videoPlayer = new VideoPlayer(context);
    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
    videoPlayer.setLayoutParams(params);

    holder.layout.addView(videoPlayer);
    videoPlayerController.loadVideo(video, videoPlayer, holder.progressBar);
    videoPlayer.setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View v) 

            videoPlayer.changePlayState();
        
    );


@Override
public void onViewRecycled(ViewHolder holder) 
    super.onViewRecycled(holder);
    Log.d(TAG, "onViewRecycledCalled");
    holder.layout.removeAllViews();



@Override
public int getItemCount() 
    return urls.size();



视频下载器

public class VideosDownloader 

private static String TAG = "VideosDownloader";

Context context;
FileCache fileCache;
IVideoDownloadListener iVideoDownloadListener;

public VideosDownloader(Context context) 
    this.context = context;
    fileCache = new FileCache(context);


/////////////////////////////////////////////////////////////////
// Start downloading all videos from given urls

public void startVideosDownloading(final ArrayList<Video> videosList)

    Thread thread = new Thread(new Runnable() 
        @Override
        public void run()
        
            for(int i=0; i<videosList.size(); i++)
            
                final Video video = videosList.get(i);
                String id = video.getId();
                String url = video.getUrl();

                String isVideoDownloaded = Utils.readPreferences(context, video.getUrl(), "false");
                boolean isVideoAvailable = Boolean.valueOf(isVideoDownloaded);
                if(!isVideoAvailable)
                
                    //Download video from url
                    String downloadedPath = downloadVideo(url);
                    //Log.i(TAG, "Vides downloaded at: " + downloadedPath);
                    Activity activity = (Activity) context;
                    activity.runOnUiThread(new Runnable() 
                        @Override
                        public void run() 
                            Utils.savePreferences(context, video.getUrl(), "true");
                             iVideoDownloadListener.onVideoDownloaded(video);
                        
                    );
                

            
        
    );
    thread.start();


/////////////////////////////////////////////////////////////////

private String downloadVideo(String urlStr)

    URL url = null;
    File file = null;
    try
    
        file = fileCache.getFile(urlStr);
        url = new URL(urlStr);
        long startTime = System.currentTimeMillis();
        URLConnection ucon = null;
        ucon = url.openConnection();
        InputStream is = ucon.getInputStream();
        BufferedInputStream inStream = new BufferedInputStream(is, 1024 * 5);
        FileOutputStream outStream = new FileOutputStream(file);
        byte[] buff = new byte[5 * 1024];

        //Read bytes (and store them) until there is nothing more to read(-1)
        int len;
        while ((len = inStream.read(buff)) != -1) 
            outStream.write(buff, 0, len);
        

        //clean up
        outStream.flush();
        outStream.close();
        inStream.close();

    
    catch (MalformedURLException e) 
        e.printStackTrace();
    
    catch (IOException e) 
        e.printStackTrace();
    
    return file.getAbsolutePath();



public void setOnVideoDownloadListener(IVideoDownloadListener iVideoDownloadListener) 
    this.iVideoDownloadListener = iVideoDownloadListener;


VideoPlayerController

public class VideoPlayerController 

private static String TAG = "VideoPlayerController";

Context context;
FileCache fileCache;
int currentPositionOfItemToPlay = 0;
Video currentPlayingVideo;

private Map<String, VideoPlayer> videos = Collections.synchronizedMap(new WeakHashMap<String, VideoPlayer>());
private Map<String, ProgressBar> videosSpinner = Collections.synchronizedMap(new WeakHashMap<String, ProgressBar>());

public VideoPlayerController(Context context) 

    this.context = context;
    fileCache = new FileCache(context);


public void loadVideo(Video video, VideoPlayer videoPlayer, ProgressBar progressBar) 

    //Add video to map
    videos.put(video.getIndexPosition(), videoPlayer);
    videosSpinner.put(video.getIndexPosition(), progressBar);

    handlePlayBack(video);


//This method would check two things
//First if video is downloaded or its local path exist
//Second if the videoplayer of this video is currently showing in the list or visible

public void handlePlayBack(Video video)

    //Check if video is available
    if(isVideoDownloaded(video))
    

        // then check if it is currently at a visible or playable position in the listview
        if(isVideoVisible(video))
        
            //IF yes then playvideo
            playVideo(video);
        
    


private void playVideo(final Video video)

    //Before playing it check if this video is already playing

    if(currentPlayingVideo != video)
    
        //Start playing new url
        if(videos.containsKey(video.getIndexPosition()))
        
            final VideoPlayer videoPlayer2 = videos.get(video.getIndexPosition());
            String localPath = fileCache.getFile(video.getUrl()).getAbsolutePath();
            if(!videoPlayer2.isLoaded)
            
                videoPlayer2.loadVideo(localPath, video);
                videoPlayer2.setOnVideoPreparedListener(new IVideoPreparedListener() 
                    @Override
                    public void onVideoPrepared(Video mVideo) 

                        //Pause current playing video if any
                        if(video.getIndexPosition() == mVideo.getIndexPosition())
                        
                            if(currentPlayingVideo!=null)
                            
                                VideoPlayer videoPlayer1 = videos.get(currentPlayingVideo.getIndexPosition());
                                videoPlayer1.pausePlay();
                            
                            videoPlayer2.mp.start();
                            currentPlayingVideo = mVideo;
                        


                    
                );
            
            else
            
                //Pause current playing video if any
                if(currentPlayingVideo!=null)
                
                    VideoPlayer videoPlayer1 = videos.get(currentPlayingVideo.getIndexPosition());
                    videoPlayer1.pausePlay();
                

                boolean isStarted = videoPlayer2.startPlay();
                
                    //Log.i(TAG, "Started playing Video Index: " + video.getIndexPosition());
                    //Log.i(TAG, "Started playing Video: " + video.getUrl());
                
                currentPlayingVideo = video;
            
        
    
    else
    
        //Log.i(TAG, "Already playing Video: " + video.getUrl());
    



private boolean isVideoVisible(Video video) 

    //To check if the video is visible in the listview or it is currently at a playable position
    //we need the position of this video in listview and current scroll position of the listview
    int positionOfVideo = Integer.valueOf(video.getIndexPosition());

    if(currentPositionOfItemToPlay == positionOfVideo)
        return true;

    return false;


private boolean isVideoDownloaded(Video video) 

    String isVideoDownloaded = Utils.readPreferences(context, video.getUrl(), "false");
    boolean isVideoAvailable = Boolean.valueOf(isVideoDownloaded);
    if(isVideoAvailable)
    
        //If video is downloaded then hide its progress
        hideProgressSpinner(video);
        return true;
    

    showProgressSpinner(video);
    return false;


private void showProgressSpinner(Video video) 
    ProgressBar progressBar = videosSpinner.get(video.getIndexPosition());
    if(progressBar!=null)
        progressBar.setVisibility(View.VISIBLE);


private void hideProgressSpinner(Video video) 

    ProgressBar progressBar = videosSpinner.get(video.getIndexPosition());
    if(progressBar!=null && progressBar.isShown())
    
        progressBar.setVisibility(View.GONE);
        Log.i(TAG, "ProgressSpinner Hided Index: " + video.getIndexPosition());
    


public void setcurrentPositionOfItemToPlay(int mCurrentPositionOfItemToPlay) 
    currentPositionOfItemToPlay = mCurrentPositionOfItemToPlay;


视频播放器

public class VideoPlayer extends TextureView implements TextureView.SurfaceTextureListener 

private static String TAG = "VideoPlayer";

/**This flag determines that if current VideoPlayer object is first item of the list if it is first item of list*/
boolean isFirstListItem;

boolean isLoaded;
boolean isMpPrepared;

IVideoPreparedListener iVideoPreparedListener;

Video video;
String url;
MediaPlayer mp;
Surface surface;
SurfaceTexture s;

public VideoPlayer(Context context) 
    super(context);


public VideoPlayer(Context context, AttributeSet attrs)

    super(context, attrs);


public void loadVideo(String localPath, Video video) 

    this.url = localPath;
    this.video = video;
    isLoaded = true;

    if (this.isAvailable()) 
        prepareVideo(getSurfaceTexture());
    

    setSurfaceTextureListener(this);


@Override
public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) 
    isMpPrepared = false;
    prepareVideo(surface);


@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) 



@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) 

    if(mp!=null)
    
        mp.stop();
        mp.reset();
        mp.release();
        mp = null;
    

    return false;


@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) 


public void prepareVideo(SurfaceTexture t)


    this.surface = new Surface(t);
    mp = new MediaPlayer();
    mp.setSurface(this.surface);

    try 
        mp.setDataSource(url);
        mp.prepareAsync();

        mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() 
            public void onPrepared(MediaPlayer mp) 
                isMpPrepared = true;
                mp.setLooping(true);
                iVideoPreparedListener.onVideoPrepared(video);
            


        );
     catch (IllegalArgumentException e1) 
        e1.printStackTrace();
     catch (SecurityException e1) 
        e1.printStackTrace();
     catch (IllegalStateException e1) 
        e1.printStackTrace();
     catch (IOException e1) 
        e1.printStackTrace();
    
    try 

     catch (IllegalArgumentException e) 
        e.printStackTrace();
     catch (SecurityException e) 
        e.printStackTrace();
     catch (IllegalStateException e) 
        e.printStackTrace();
    
    try 

     catch (IllegalStateException e) 
        e.printStackTrace();
    



@Override
protected void onAttachedToWindow() 
    super.onAttachedToWindow();


@Override
protected void onVisibilityChanged(View changedView, int visibility) 
    super.onVisibilityChanged(changedView, visibility);


public boolean startPlay()

    if(mp!=null)
        if(!mp.isPlaying())
        
            mp.start();
            return true;
        

    return false;


public void pausePlay()

    if(mp!=null)
        mp.pause();


public void stopPlay()

    if(mp!=null)
        mp.stop();


public void changePlayState()

    if(mp!=null)
    
        if(mp.isPlaying())
            mp.pause();
        else
            mp.start();
    



public void setOnVideoPreparedListener(IVideoPreparedListener iVideoPreparedListener) 
    this.iVideoPreparedListener = iVideoPreparedListener;


IVideoDownloadListener

public interface IVideoDownloadListener 
public void onVideoDownloaded(Video video);

IVideoPreparedListener

public interface IVideoPreparedListener 

public void onVideoPrepared(Video video);

【讨论】:

嗨@Nouman Bhatti。您可以将此代码作为单个项目共享吗?这对我完全有帮助。谢谢。 你能在工作模式下分享你的完整源代码吗?或者通过 test4fusioni@gmail.com 给我发邮件。谢谢 对不起,我现在没有完整的源代码,因为它是一个旧项目。但是上面的代码除了xml文件外几乎是完整的 我可以为视频项目设置自定义视图 xml 吗?因为在 My RecyclerView 中有两个单元格,一个用于视频,另一个用于图像.. 是的,你可以做到,但我没有完全理解你的意思。如果 videoview 和 imageview 有单独的单元格,那么问题是什么?【参考方案2】:

为什么不在布局文件“view_main”本身中添加自定义视频视图。 检查视频视图的可见性并仅在视图可见时播放。

public static boolean isViewVisible(View subView, View parentView) 
    Rect scrollBounds = new Rect();
    parentView.getHitRect(scrollBounds);
    if (subView.getLocalVisibleRect(scrollBounds)) 
        return true;
    
    return false;

检查可见性的代码。当滚动状态为空闲时,在滚动状态更改侦听器中调用此方法。

此外,您还必须使用 AsyncTask 来下载视频,但一次只能下载一个视频,否则您可能会出现内存不足错误。

【讨论】:

不能使用视频视图,因为它不适用于滚动。您还可以详细说明在哪里调用上述方法来获取可见的列表视图项? 视频视图是指您的自定义视图。 您必须覆盖列表的 onScrollListener 并获取顶部可见项目。顺便问一下,你使用 Recycler 视图还是 List 视图? 我正在使用回收站视图 我不能这样做,因为在那种情况下,我只能访问那个可见的视图,但在可见视图的基础上,我还需要决定可见视图的下一个和上一个视图应该停止并暂停 【参考方案3】:

您应该通过在后端下载视频来维护本地缓存,并从本地内存一次播放一个视频,以保持列表滚动流畅。

【讨论】:

您更喜欢用什么来实现视频缓存? @sajad-khan

以上是关于在recyclerview中使用textureview播放视频的主要内容,如果未能解决你的问题,请参考以下文章

如何在 RecyclerView 中使用 ItemAnimator?

在选项卡式活动中使用 RecyclerView 的 OnScreenRotation

如何在 RecyclerView 中使用 offsetChildrenHorizo​​ntal()

如何在我的 recyclerview 中使用 cardview 设置 onclick 侦听器,并在单击 recyclerview 时从 firebase 数据库中检索数据?

在 RecyclerView 的单个界面中使用多个 onClick 方法

RecyclerView使用