【Android -- UI 开发】RecyclerView 实现视频列表自动播放

04.视频播放器通用架构实践 04.视频播放器通用架构实践 目录介绍 01.视频播放器的痛点 02.业务需求的目标 03.该播放器框架特点 04.播放器内核封装 05.播放器UI层封装 06.如何简单使用 07.如何自定义播放器 08.该案例的拓展性分享 09.关于视频缓存方案 10.如何监控视频埋点 11.待实现的需求分析 12.一些细节上优化 13.参考案例和博客记录 00.视频播放器通用框架 基础封装视频播放器player,可以在ExoPlayer、MediaPlayer,声网RTC视频播放器内核,原生MediaPlayer 阅读详情

一、效果图

在这里插入图片描述

二、准备

项目中视频使用的是 GSYVideoPlayer 这个开源库。

既然要实现类似微博那种滚动列表时,处于当前屏幕的项自动播放,那么肯定得监听列表的滚动事件,好在 Recyclerview 中给我们提供了接口:

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                switch (newState) {
                    case SCROLL_STATE_IDLE: //滚动停止
                        break;
                    case SCROLL_STATE_DRAGGING: //手指拖动
                        break;
                    case SCROLL_STATE_SETTLING: //惯性滚动
                        break;
                }
            }

           @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }
        });

如上 onScrollStateChanged 方法 给我们返回了三种状态:
SCROLL_STATE_DRAGGING: 手指按住屏幕拖动
SCROLL_STATE_SETTLING: 手指快速在屏幕滑一下后的惯性滑动
SCROLL_STATE_IDLE: 屏幕处于禁止状态

onScrolled 方法给我们返回了 dx:水平滚动距离、dy:垂直滚动距离。这两个值都是用手指开始触摸的位置减去移动后的位置,所以:
dx > 0 时为手指向左滑动,列表滚动显示右面的内容
dx < 0 时为手指向右滑动,列表滚动显示左面的内容
dy > 0 时为手指向上滑动,列表滚动显示下面的内容
dy < 0 时为手指向下滑动,列表滚动显示上面的内容
项目中暂时没用到这些,但是这些值很有用。

在这个方法中我们需要获取三个值:
(1) 在屏幕可见区域的第一项位置 : 通过 findFirstVisibleItemPosition() 方法获取
(2) 在屏幕可见区域的最后一项位置 : 通过 findLastVisibleItemPosition() 方法获取
(3)屏幕可见项的数目 : 用(2)减去(1)即可

为什么需要以上值,这里说下整体思路:
(1)获取当前处于屏幕可见的列表
(2)滑出屏幕的视频我们需要回收掉
(3)当屏幕处于静止状态时我们才开始播放视频

第一点:获取可见列表;上面获取的三个值已经解决了,直接看代码:

			public int firstVisibleItem, lastVisibleItem, visibleCount;

             @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
                lastVisibleItem = layoutManager.findLastVisibleItemPosition();
                visibleCount = lastVisibleItem - firstVisibleItem;
            }

第二点:回收滑出屏幕的视频,获取到当前播放的位置(开源库中有相关方法)判断是否在屏幕可见,不可见就回收;具体看代码

			@Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                //大于0说明有播放
                if (GSYVideoManager.instance().getPlayPosition() >= 0) {
                    //当前播放的位置
                    int position = GSYVideoManager.instance().getPlayPosition();
                    //对应的播放列表TAG
                    if (GSYVideoManager.instance().getPlayTag().equals(HomeAdapter.TAG) && (position < firstVisibleItem || position > lastVisibleItem)) {
                        GSYVideoManager.releaseAllVideos();
                    }
                }
            }

第三点:屏幕处于静止时才开始播放,只要播放的逻辑写在 onScrollStateChangedSCROLL_STATE_IDLE 状态下即可;

		   @Override
           public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                switch (newState) {
                    case SCROLL_STATE_IDLE: //滚动停止
                        autoPlayVideo(recyclerView);
                        break;
                }
            }

	private void autoPlayVideo(RecyclerView view) {
        RecyclerView.LayoutManager layoutManager = view.getLayoutManager();
        for (int i = 0; i < visibleCount; i++) {
            if (layoutManager != null && layoutManager.getChildAt(i) != null && layoutManager.getChildAt(i).findViewById(R.id.video_item_player) != null) {
                HomeGSYVideoPlayer homeGSYVideoPlayer = (HomeGSYVideoPlayer) layoutManager.getChildAt(i).findViewById(R.id.video_item_player);
                Rect rect = new Rect();
                homeGSYVideoPlayer.getLocalVisibleRect(rect);
                int videoheight = homeGSYVideoPlayer.getHeight();
                if (rect.top == 0 && rect.bottom == videoheight) {
                    if (homeGSYVideoPlayer.getCurrentState() == homeGSYVideoPlayer.CURRENT_STATE_NORMAL || homeGSYVideoPlayer.getCurrentState() == homeGSYVideoPlayer.CURRENT_STATE_ERROR) {
                        homeGSYVideoPlayer.getStartButton().performClick();
                    }
                    return;
                }

            }
        }
        GSYVideoPlayer.releaseAllVideos();
    }

三、实战

1. 效果图
在这里插入图片描述

2. 添加依赖:

implementation 'com.github.CarGuo.GSYVideoPlayer:GSYVideoPlayer:v8.2.0-release-jitpack'

3. 布局控件
item_home.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <com.hkt.nfcdemo.views.SampleCoverVideo
        android:id="@+id/video_item_player"
        android:layout_width="match_parent"
        android:layout_height="200dp"/>

</FrameLayout>

list_video_item.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <FrameLayout
        android:id="@+id/list_item_container"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:scaleType="centerCrop"
        android:src="@mipmap/a" />


    <ImageView
        android:id="@+id/list_item_btn"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_gravity="center_vertical"
        android:src="@drawable/video_click_play_selector" />

</RelativeLayout>

video_layout_cover.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black">

    <FrameLayout
        android:id="@+id/surface_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center">

    </FrameLayout>

    <RelativeLayout
        android:id="@+id/thumb"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:background="#000000"
        android:scaleType="fitCenter">

        <ImageView
            android:id="@+id/thumb_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop" />

    </RelativeLayout>

    <LinearLayout
        android:id="@+id/layout_bottom"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_alignParentBottom="true"
        android:background="#99000000"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:visibility="invisible">

        <TextView
            android:id="@+id/current"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:text="00:00"
            android:textColor="#ffffff" />

        <SeekBar
            android:id="@+id/progress"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_weight="1.0"
            android:background="@null"
            android:max="100"
            android:maxHeight="4dp"
            android:minHeight="4dp"
            android:paddingTop="8dp"
            android:paddingBottom="8dp"
            android:progressDrawable="@drawable/video_seek_progress"
            android:thumb="@drawable/video_seek_thumb" />

        <TextView
            android:id="@+id/total"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="16dp"
            android:text="00:00"
            android:textColor="#ffffff" />

        <ImageView
            android:id="@+id/fullscreen"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:paddingRight="16dp"
            android:scaleType="center"
            android:src="@drawable/video_enlarge" />
    </LinearLayout>

    <ProgressBar
        android:id="@+id/bottom_progressbar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="1.5dp"
        android:layout_alignParentBottom="true"
        android:max="100"
        android:progressDrawable="@drawable/video_progress" />

    <ImageView
        android:id="@+id/back_tiny"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginLeft="6dp"
        android:layout_marginTop="6dp"
        android:visibility="gone" />

    <LinearLayout
        android:id="@+id/layout_top"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:background="@drawable/video_title_bg"
        android:gravity="center_vertical">

        <ImageView
            android:id="@+id/back"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:paddingLeft="10dp"
            android:scaleType="centerInside"
            android:src="@drawable/video_back" />

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="10dp"
            android:textColor="@android:color/white"
            android:textSize="18sp" />
    </LinearLayout>

    <moe.codeest.enviews.ENDownloadView
        android:id="@+id/loading"
        android:layout_width="28dp"
        android:layout_height="28dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:visibility="invisible" />

    <moe.codeest.enviews.ENPlayView
        android:id="@+id/start"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_gravity="center_vertical" />


    <ImageView
        android:id="@+id/small_close"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:paddingLeft="10dp"
        android:paddingTop="10dp"
        android:scaleType="centerInside"
        android:src="@drawable/video_small_close"
        android:visibility="gone" />

    <ImageView
        android:id="@+id/lock_screen"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginRight="50dp"
        android:scaleType="centerInside"
        android:src="@drawable/unlock"
        android:visibility="gone" />

</RelativeLayout>

4. 自定义控件 SampleCoverVideo.java

/**
 * Created on 2022/6/1 14:07
 *
 * @author Gong Youqiang
 */
public class SampleCoverVideo extends StandardGSYVideoPlayer {
    ImageView mCoverImage;

    String mCoverOriginUrl;

    int  mCoverOriginId = 0;

    int mDefaultRes;

    public SampleCoverVideo(Context context, Boolean fullFlag) {
        super(context, fullFlag);
    }

    public SampleCoverVideo(Context context) {
        super(context);
    }

    public SampleCoverVideo(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void init(Context context) {
        super.init(context);
        mCoverImage = (ImageView) findViewById(R.id.thumb_image);

        if (mThumbImageViewLayout != null &&
                (mCurrentState == -1 || mCurrentState == CURRENT_STATE_NORMAL || mCurrentState == CURRENT_STATE_ERROR)) {
            mThumbImageViewLayout.setVisibility(VISIBLE);
        }
    }

    @Override
    public int getLayoutId() {
        return R.layout.video_layout_cover;
    }

    public void loadCoverImage(String url, int res) {
        mCoverOriginUrl = url;
        mDefaultRes = res;
        Glide.with(getContext().getApplicationContext())
                .setDefaultRequestOptions(
                        new RequestOptions()
                                .frame(1000000)
                                .centerCrop()
                                .error(res)
                                .placeholder(res))
                .load(url)
                .into(mCoverImage);
    }

    public void loadCoverImageBy(int id, int res) {
        mCoverOriginId = id;
        mDefaultRes = res;
        mCoverImage.setImageResource(id);
    }

    @Override
    public GSYBaseVideoPlayer startWindowFullscreen(Context context, boolean actionBar, boolean statusBar) {
        GSYBaseVideoPlayer gsyBaseVideoPlayer = super.startWindowFullscreen(context, actionBar, statusBar);
        SampleCoverVideo sampleCoverVideo = (SampleCoverVideo) gsyBaseVideoPlayer;
        if(mCoverOriginUrl != null) {
            sampleCoverVideo.loadCoverImage(mCoverOriginUrl, mDefaultRes);
        } else  if(mCoverOriginId != 0) {
            sampleCoverVideo.loadCoverImageBy(mCoverOriginId, mDefaultRes);
        }
        return gsyBaseVideoPlayer;
    }


    @Override
    public GSYBaseVideoPlayer showSmallVideo(Point size, boolean actionBar, boolean statusBar) {
        //下面这里替换成你自己的强制转化
        SampleCoverVideo sampleCoverVideo = (SampleCoverVideo) super.showSmallVideo(size, actionBar, statusBar);
        sampleCoverVideo.mStartButton.setVisibility(GONE);
        sampleCoverVideo.mStartButton = null;
        return sampleCoverVideo;
    }

    @Override
    protected void cloneParams(GSYBaseVideoPlayer from, GSYBaseVideoPlayer to) {
        super.cloneParams(from, to);
        SampleCoverVideo sf = (SampleCoverVideo) from;
        SampleCoverVideo st = (SampleCoverVideo) to;
        st.mShowFullAnimation = sf.mShowFullAnimation;
    }


    /**
     * 退出window层播放全屏效果
     */
    @SuppressWarnings("ResourceType")
    @Override
    protected void clearFullscreenLayout() {
        if (!mFullAnimEnd) {
            return;
        }
        mIfCurrentIsFullscreen = false;
        int delay = 0;
        // ------- !!!如果不需要旋转屏幕,可以不调用!!!-------
        // 不需要屏幕旋转,还需要设置 setNeedOrientationUtils(false)
        if (mOrientationUtils != null) {
            delay = mOrientationUtils.backToProtVideo();
            mOrientationUtils.setEnable(false);
            if (mOrientationUtils != null) {
                mOrientationUtils.releaseListener();
                mOrientationUtils = null;
            }
        }

        if (!mShowFullAnimation) {
            delay = 0;
        }

        final ViewGroup vp = (CommonUtil.scanForActivity(getContext())).findViewById(Window.ID_ANDROID_CONTENT);
        final View oldF = vp.findViewById(getFullId());
        if (oldF != null) {
            //此处fix bug#265,推出全屏的时候,虚拟按键问题
            SampleCoverVideo gsyVideoPlayer = (SampleCoverVideo) oldF;
            gsyVideoPlayer.mIfCurrentIsFullscreen = false;
        }

        if (delay == 0) {
            backToNormal();
        } else {
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    backToNormal();
                }
            }, delay);
        }

    }


    /******************* 下方两个重载方法,在播放开始前不屏蔽封面,不需要可屏蔽 ********************/
    @Override
    public void onSurfaceUpdated(Surface surface) {
        super.onSurfaceUpdated(surface);
        if (mThumbImageViewLayout != null && mThumbImageViewLayout.getVisibility() == VISIBLE) {
            mThumbImageViewLayout.setVisibility(INVISIBLE);
        }
    }

    @Override
    protected void setViewShowState(View view, int visibility) {
        if (view == mThumbImageViewLayout && visibility != VISIBLE) {
            return;
        }
        super.setViewShowState(view, visibility);
    }

    @Override
    public void onSurfaceAvailable(Surface surface) {
        super.onSurfaceAvailable(surface);
        if (GSYVideoType.getRenderType() != GSYVideoType.TEXTURE) {
            if (mThumbImageViewLayout != null && mThumbImageViewLayout.getVisibility() == VISIBLE) {
                mThumbImageViewLayout.setVisibility(INVISIBLE);
            }
        }
    }

    /******************* 下方重载方法,在播放开始不显示底部进度和按键,不需要可屏蔽 ********************/

    protected boolean byStartedClick;

    @Override
    protected void onClickUiToggle(MotionEvent e) {
        if (mIfCurrentIsFullscreen && mLockCurScreen && mNeedLockFull) {
            setViewShowState(mLockScreen, VISIBLE);
            return;
        }
        byStartedClick = true;
        super.onClickUiToggle(e);

    }

    @Override
    protected void changeUiToNormal() {
        super.changeUiToNormal();
        byStartedClick = false;
    }

    @Override
    protected void changeUiToPreparingShow() {
        super.changeUiToPreparingShow();
        Debuger.printfLog("Sample changeUiToPreparingShow");
        setViewShowState(mBottomContainer, INVISIBLE);
        setViewShowState(mStartButton, INVISIBLE);
    }

    @Override
    protected void changeUiToPlayingBufferingShow() {
        super.changeUiToPlayingBufferingShow();
        Debuger.printfLog("Sample changeUiToPlayingBufferingShow");
        if (!byStartedClick) {
            setViewShowState(mBottomContainer, INVISIBLE);
            setViewShowState(mStartButton, INVISIBLE);
        }
    }

    @Override
    protected void changeUiToPlayingShow() {
        super.changeUiToPlayingShow();
        Debuger.printfLog("Sample changeUiToPlayingShow");
        if (!byStartedClick) {
            setViewShowState(mBottomContainer, INVISIBLE);
            setViewShowState(mStartButton, INVISIBLE);
        }
    }

    @Override
    public void startAfterPrepared() {
        super.startAfterPrepared();
        Debuger.printfLog("Sample startAfterPrepared");
        setViewShowState(mBottomContainer, INVISIBLE);
        setViewShowState(mStartButton, INVISIBLE);
        setViewShowState(mBottomProgressBar, VISIBLE);
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        byStartedClick = true;
        super.onStartTrackingTouch(seekBar);
    }
}

5. 工具类 ScrollCalculatorHelper.java

/**
 * Created on 2022/6/1 14:32
 *
 * @author Gong Youqiang
 */
public class ScrollCalculatorHelper {
    private int firstVisible = 0;
    private int lastVisible = 0;
    private int visibleCount = 0;
    private int playId;
    private int rangeTop;
    private int rangeBottom;
    private PlayRunnable runnable;

    private Handler playHandler = new Handler();

    public ScrollCalculatorHelper(int playId, int rangeTop, int rangeBottom) {
        this.playId = playId;
        this.rangeTop = rangeTop;
        this.rangeBottom = rangeBottom;
    }

    public void onScrollStateChanged(RecyclerView view, int scrollState) {
        switch (scrollState) {
            case RecyclerView.SCROLL_STATE_IDLE:
                playVideo(view);
                break;
        }
    }

    public void onScroll(RecyclerView view, int firstVisibleItem, int lastVisibleItem, int visibleItemCount) {
        if (firstVisible == firstVisibleItem) {
            return;
        }
        firstVisible = firstVisibleItem;
        lastVisible = lastVisibleItem;
        visibleCount = visibleItemCount;
    }


    void playVideo(RecyclerView view) {

        if (view == null) {
            return;
        }

        RecyclerView.LayoutManager layoutManager = view.getLayoutManager();

        GSYBaseVideoPlayer gsyBaseVideoPlayer = null;

        boolean needPlay = false;

        for (int i = 0; i < visibleCount; i++) {
            if (layoutManager.getChildAt(i) != null && layoutManager.getChildAt(i).findViewById(playId) != null) {
                GSYBaseVideoPlayer player = (GSYBaseVideoPlayer) layoutManager.getChildAt(i).findViewById(playId);
                Rect rect = new Rect();
                player.getLocalVisibleRect(rect);
                int height = player.getHeight();
                //说明第一个完全可视
                if (rect.top == 0 && rect.bottom == height) {
                    gsyBaseVideoPlayer = player;
                    if ((player.getCurrentPlayer().getCurrentState() == GSYBaseVideoPlayer.CURRENT_STATE_NORMAL
                            || player.getCurrentPlayer().getCurrentState() == GSYBaseVideoPlayer.CURRENT_STATE_ERROR)) {
                        needPlay = true;
                    }
                    break;
                }

            }
        }

        if (gsyBaseVideoPlayer != null && needPlay) {
            if (runnable != null) {
                GSYBaseVideoPlayer tmpPlayer = runnable.gsyBaseVideoPlayer;
                playHandler.removeCallbacks(runnable);
                runnable = null;
                if (tmpPlayer == gsyBaseVideoPlayer) {
                    return;
                }
            }
            runnable = new PlayRunnable(gsyBaseVideoPlayer);
            //降低频率
            playHandler.postDelayed(runnable, 400);
        }


    }

    private class PlayRunnable implements Runnable {

        GSYBaseVideoPlayer gsyBaseVideoPlayer;

        public PlayRunnable(GSYBaseVideoPlayer gsyBaseVideoPlayer) {
            this.gsyBaseVideoPlayer = gsyBaseVideoPlayer;
        }

        @Override
        public void run() {
            boolean inPosition = false;
            //如果未播放,需要播放
            if (gsyBaseVideoPlayer != null) {
                int[] screenPosition = new int[2];
                gsyBaseVideoPlayer.getLocationOnScreen(screenPosition);
                int halfHeight = gsyBaseVideoPlayer.getHeight() / 2;
                int rangePosition = screenPosition[1] + halfHeight;
                //中心点在播放区域内
                if (rangePosition >= rangeTop && rangePosition <= rangeBottom) {
                    inPosition = true;
                }
                if (inPosition) {
                    startPlayLogic(gsyBaseVideoPlayer, gsyBaseVideoPlayer.getContext());
                    //gsyBaseVideoPlayer.startPlayLogic();
                }
            }
        }
    }


    /***************************************自动播放的点击播放确认******************************************/
    private void startPlayLogic(GSYBaseVideoPlayer gsyBaseVideoPlayer, Context context) {
        if (!com.shuyu.gsyvideoplayer.utils.CommonUtil.isWifiConnected(context)) {
            //这里判断是否wifi
            showWifiDialog(gsyBaseVideoPlayer, context);
            return;
        }
        gsyBaseVideoPlayer.startPlayLogic();
    }

    private void showWifiDialog(final GSYBaseVideoPlayer gsyBaseVideoPlayer, Context context) {
        if (!NetworkUtils.isAvailable(context)) {
            Toast.makeText(context, context.getResources().getString(com.shuyu.gsyvideoplayer.R.string.no_net), Toast.LENGTH_LONG).show();
            return;
        }
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setMessage(context.getResources().getString(com.shuyu.gsyvideoplayer.R.string.tips_not_wifi));
        builder.setPositiveButton(context.getResources().getString(com.shuyu.gsyvideoplayer.R.string.tips_not_wifi_confirm), new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
                gsyBaseVideoPlayer.startPlayLogic();
            }
        });
        builder.setNegativeButton(context.getResources().getString(com.shuyu.gsyvideoplayer.R.string.tips_not_wifi_cancel), new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });
        builder.create().show();
    }
}

6. 适配器
6.1 RecyclerBaseAdapter.java

/**
 * Created on 2022/6/1 14:17
 *
 * @author Gong Youqiang
 */
public class RecyclerBaseAdapter extends RecyclerView.Adapter {
    private final static String TAG = "RecyclerBaseAdapter";

    private List<VideoModel> itemDataList = null;

    private Context context = null;

    private GSYVideoHelper smallVideoHelper;

    private GSYVideoHelper.GSYVideoHelperBuilder gsySmallVideoHelperBuilder;

    public RecyclerBaseAdapter(Context context, List<VideoModel> itemDataList) {
        this.itemDataList = itemDataList;
        this.context = context;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
                                                      int viewType) {
        View v = LayoutInflater.from(context).inflate(R.layout.list_video_item, parent, false);
        final RecyclerView.ViewHolder holder = new RecyclerItemViewHolder(context, v);
        return holder;

    }

    @Override
    public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
        RecyclerItemViewHolder recyclerItemViewHolder = (RecyclerItemViewHolder) holder;
        recyclerItemViewHolder.setVideoHelper(smallVideoHelper, gsySmallVideoHelperBuilder);
        recyclerItemViewHolder.setRecyclerBaseAdapter(this);
        recyclerItemViewHolder.onBind(position, itemDataList.get(position));
    }

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


    @Override
    public int getItemViewType(int position) {
        return 1;
    }

    public void setListData(List<VideoModel> data) {
        itemDataList = data;
        notifyDataSetChanged();
    }

    public GSYVideoHelper getVideoHelper() {
        return smallVideoHelper;
    }

    public void setVideoHelper(GSYVideoHelper smallVideoHelper, GSYVideoHelper.GSYVideoHelperBuilder gsySmallVideoHelperBuilder) {
        this.smallVideoHelper = smallVideoHelper;
        this.gsySmallVideoHelperBuilder = gsySmallVideoHelperBuilder;
    }
}

6.2 RecyclerNormalAdapter.java

/**
 * Created on 2022/6/1 9:42
 *
 * @author Gong Youqiang
 */
public class RecyclerNormalAdapter extends RecyclerView.Adapter {
    private final static String TAG = "RecyclerBaseAdapter";

    private List<VideoModel> itemDataList = null;
    private Context context = null;

    public RecyclerNormalAdapter(Context context, List<VideoModel> itemDataList) {
        this.itemDataList = itemDataList;
        this.context = context;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
                                                      int viewType) {
        View v = LayoutInflater.from(context).inflate(R.layout.item_home, parent, false);
        final RecyclerView.ViewHolder holder = new RecyclerItemNormalHolder(context, v);
        return holder;

    }

    @Override
    public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
        RecyclerItemNormalHolder recyclerItemViewHolder = (RecyclerItemNormalHolder) holder;
        recyclerItemViewHolder.setRecyclerBaseAdapter(this);
        recyclerItemViewHolder.onBind(position, itemDataList.get(position));
    }

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


    @Override
    public int getItemViewType(int position) {
        return 1;
    }

    public void setListData(List<VideoModel> data) {
        itemDataList = data;
        notifyDataSetChanged();
    }
}

7. holder
7.1 RecyclerItemBaseHolder.java

/**
 * Created on 2022/6/1 14:02
 *
 * @author Gong Youqiang
 */
public class RecyclerItemBaseHolder extends RecyclerView.ViewHolder {
    RecyclerView.Adapter recyclerBaseAdapter;

    public RecyclerItemBaseHolder(View itemView) {
        super(itemView);
    }

    public RecyclerView.Adapter getRecyclerBaseAdapter() {
        return recyclerBaseAdapter;
    }

    public void setRecyclerBaseAdapter(RecyclerView.Adapter recyclerBaseAdapter) {
        this.recyclerBaseAdapter = recyclerBaseAdapter;
    }
}

7.2 RecyclerItemNormalHolder.java

/**
 * Created on 2022/6/1 9:43
 *
 * @author Gong Youqiang
 */
public class RecyclerItemNormalHolder extends RecyclerItemBaseHolder {
    public final static String TAG = "RecyclerView2List";

    protected Context context;

    SampleCoverVideo gsyVideoPlayer;

    ImageView imageView;

    GSYVideoOptionBuilder gsyVideoOptionBuilder;

    public RecyclerItemNormalHolder(Context context, View v) {
        super(v);
        this.context = context;
        gsyVideoPlayer = v.findViewById(R.id.video_item_player);
        imageView = new ImageView(context);
        gsyVideoOptionBuilder = new GSYVideoOptionBuilder();
    }

    public void onBind(final int position, VideoModel videoModel) {

        String url;
        String title;
        if (position % 2 == 0) {
            url = "https://pointshow.oss-cn-hangzhou.aliyuncs.com/McTk51586843620689.mp4";
            title = "这是title";
        } else {
            url = "http://9890.vod.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f20.mp4";
            title = "哦?Title?";
        }


        Map<String, String> header = new HashMap<>();
        header.put("ee", "33");

        //防止错位,离开释放
        //gsyVideoPlayer.initUIState();
        gsyVideoOptionBuilder
                .setIsTouchWiget(false)
                //.setThumbImageView(imageView)
                .setUrl(url)
                .setVideoTitle(title)
                .setCacheWithPlay(false)
                .setRotateViewAuto(true)
                .setLockLand(true)
                .setPlayTag(TAG)
                .setMapHeadData(header)
                .setShowFullAnimation(true)
                .setNeedLockFull(true)
                .setPlayPosition(position)
                .setVideoAllCallBack(new GSYSampleCallBack() {
                    @Override
                    public void onPrepared(String url, Object... objects) {
                        super.onPrepared(url, objects);
                        if (!gsyVideoPlayer.isIfCurrentIsFullscreen()) {
                            //静音
                            GSYVideoManager.instance().setNeedMute(true);
                        }

                    }

                    @Override
                    public void onQuitFullscreen(String url, Object... objects) {
                        super.onQuitFullscreen(url, objects);
                        //全屏不静音
                        GSYVideoManager.instance().setNeedMute(true);
                    }

                    @Override
                    public void onEnterFullscreen(String url, Object... objects) {
                        super.onEnterFullscreen(url, objects);
                        GSYVideoManager.instance().setNeedMute(false);
                        gsyVideoPlayer.getCurrentPlayer().getTitleTextView().setText((String) objects[0]);
                    }
                }).build(gsyVideoPlayer);


        //增加title
        gsyVideoPlayer.getTitleTextView().setVisibility(View.GONE);

        //设置返回键
        gsyVideoPlayer.getBackButton().setVisibility(View.GONE);

        //设置全屏按键功能
        gsyVideoPlayer.getFullscreenButton().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                resolveFullBtn(gsyVideoPlayer);
            }
        });

        gsyVideoPlayer.loadCoverImageBy(R.mipmap.a, R.mipmap.b);
    }

    /**
     * 全屏幕按键处理
     */
    private void resolveFullBtn(final StandardGSYVideoPlayer standardGSYVideoPlayer) {
        standardGSYVideoPlayer.startWindowFullscreen(context, true, true);
    }

    public SampleCoverVideo getPlayer() {
        return gsyVideoPlayer;
    }
}

7.3 RecyclerItemViewHolder

/**
 * Created on 2022/6/1 14:25
 *
 * @author Gong Youqiang
 */
public class RecyclerItemViewHolder extends RecyclerItemBaseHolder {
    public final static String TAG = "RecyclerView2List";

    protected Context context ;

    FrameLayout listItemContainer;

    ImageView listItemBtn;

    ImageView imageView;

    private GSYVideoHelper smallVideoHelper;

    private GSYVideoHelper.GSYVideoHelperBuilder gsySmallVideoHelperBuilder;

    public RecyclerItemViewHolder(Context context, View v) {
        super(v);
        this.context = context;
        listItemContainer = v.findViewById(R.id.list_item_container);
        listItemBtn = v.findViewById(R.id.list_item_btn);
        imageView = new ImageView(context);
    }

    public void onBind(final int position, VideoModel videoModel) {

        //增加封面
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        imageView.setImageResource(R.mipmap.a);

        smallVideoHelper.addVideoPlayer(position, imageView, TAG, listItemContainer, listItemBtn);

        listItemBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                smallVideoHelper.setPlayPositionAndTag(position, TAG);
                getRecyclerBaseAdapter().notifyDataSetChanged();
                //listVideoUtil.setLoop(true);
                String url;
                if (position % 2 == 0) {
                    url = "https://res.exexm.com/cw_145225549855002";
                } else {
                    url = "http://7xjmzj.com1.z0.glb.clouddn.com/20171026175005_JObCxCE2.mp4";
                }
                //listVideoUtil.setCachePath(new File(FileUtils.getPath()));

                gsySmallVideoHelperBuilder.setVideoTitle("title " + position).setUrl(url);

                smallVideoHelper.startPlay();

                //必须在startPlay之后设置才能生效
                //listVideoUtil.getGsyVideoPlayer().getTitleTextView().setVisibility(View.VISIBLE);
            }
        });
    }


    public void setVideoHelper(GSYVideoHelper smallVideoHelper, GSYVideoHelper.GSYVideoHelperBuilder gsySmallVideoHelperBuilder) {
        this.smallVideoHelper = smallVideoHelper;
        this.gsySmallVideoHelperBuilder = gsySmallVideoHelperBuilder;
    }
}

8. 使用 MainActivity.java

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();

    LinearLayoutManager linearLayoutManager;

    RecyclerBaseAdapter recyclerBaseAdapter;

    List<VideoModel> dataList = new ArrayList<>();

    boolean mFull = false;

    ScrollCalculatorHelper scrollCalculatorHelper;

    @BindView(R.id.rv_home)
    RecyclerView mRecyclerView;

    @BindView(R.id.topbar)
    QMUITopBar mTopBar;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
            getWindow().setEnterTransition(new Explode());
            getWindow().setExitTransition(new Explode());
        }
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        resolveData();

        mTopBar.setTitle("视频号");

        //限定范围为屏幕一半的上下偏移180
        int playTop = CommonUtil.getScreenHeight(this) / 2 - CommonUtil.dip2px(this, 180);
        int playBottom = CommonUtil.getScreenHeight(this) / 2 + CommonUtil.dip2px(this, 180);
        //自定播放帮助类
        scrollCalculatorHelper = new ScrollCalculatorHelper(R.id.video_item_player, playTop, playBottom);

        final RecyclerNormalAdapter recyclerNormalAdapter = new RecyclerNormalAdapter(this, dataList);
        linearLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(linearLayoutManager);
        mRecyclerView.addItemDecoration(new MyItemDecoration());
        mRecyclerView.setAdapter(recyclerNormalAdapter);

        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            int firstVisibleItem, lastVisibleItem;

            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                scrollCalculatorHelper.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                firstVisibleItem = linearLayoutManager.findFirstVisibleItemPosition();
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();

                //这是滑动自动播放的代码
                if (!mFull) {
                    scrollCalculatorHelper.onScroll(recyclerView, firstVisibleItem, lastVisibleItem, lastVisibleItem - firstVisibleItem);
                }
            }
        });


    }

    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        //如果旋转了就全屏
        mFull = newConfig.orientation == ActivityInfo.SCREEN_ORIENTATION_USER;

    }

    @Override
    public void onBackPressed() {
        if (GSYVideoManager.backFromWindowFull(this)) {
            return;
        }
        super.onBackPressed();
    }

    @Override
    protected void onPause() {
        super.onPause();
        GSYVideoManager.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        GSYVideoManager.onResume();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        GSYVideoManager.releaseAllVideos();
    }


    private void resolveData() {
        for (int i = 0; i < 19; i++) {
            VideoModel videoModel = new VideoModel();
            dataList.add(videoModel);
        }
        if (recyclerBaseAdapter != null)
            recyclerBaseAdapter.notifyDataSetChanged();
    }

    public static class MyItemDecoration extends RecyclerView.ItemDecoration{
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            outRect.set(10,20,10,20);
        }
    }

}
在可滑动列表(ListView & RecyclerView)中实现视频播放 Facebook: Instagram: Magisto 这篇文章里的技术是基于VideoPlayerManager这个开源类库的。 类库包含所有的代码以及一个示例。我在这篇文章里会跳过一些东西,所以如果要搞清楚某个细节,那最好还是把源码导入IDE然后再看这篇文章吧。就算你不看源码,这篇文章还是可以让你理解我以何种方式解决了什么问题。 两个问题 阅读详情

相关推荐

Android列表嵌入视频播放

Android列表中嵌入视频播放,当Item消失时会自动停止正在播放的视频

上帝在镜子里 6222

android RecyclerView列表自动播放实现

android RecyclerView列表自动播放实现

实现Android仿快手视频列表RecyclerView与Banner联动效果

本文还有配套的精品资源,点击获取 简介:在Android开发中,要实现类似快手视频列表的用户体验,需要将 RecyclerView 和 Banner 组件进行有效联动。 RecyclerView 作为性能优越的列表视图,提供动态数据加载和多种布局管理选项。而 Banner 组件负责展示轮播内容。本文将深入探讨如何实现两...

weixin_32047493的博客 1096

toro:视频列表自动播放变得简单,专为RecyclerView构建

托罗 视频列表自动播放变得简单,专为RecyclerView构建 发布 菜单 产品特点 核心: 在用户交互时自动启动/暂停媒体播放:滚动,打开/关闭App。 可选的播放位置保存/恢复(默认值:禁用)。 如果启用,Toro还将在配置更改(方向更改,多窗口模式...)时保存/恢复它们。 可自定义的播放组件:MediaPlayer或ExoPlayer均可使用。 Toro随附了默认的帮助程序类来支持这些2。 可自定义的播放器选择器:在许多其他播放器中,自定义要开始播放的选择。 依次支持单个/多个播放器。 强大的默认实现,足以应付各种使用情况。 加上Alpha: 对ExoPlayer 2的一流支持。 演示(Youtube视频) 入门,基本实施 1.更新模块build.gradle。 最新版本: ext { latest_release = ' 3.6.2.2804 ' // TODO check above for latest version } dependencies { implementation " im.ene.toro3:toro: ${ latest_

Android 视频列表(RecyclerView)实现自动播放

最近公司出了新的需求, 想让视频列表滑到哪里,哪里就自动播放. 于是乎,深海开始动手了: 第一步,先让视频可以手动点击播放 这个简单:就往ViewHolder里放一个视频播放器就可以了 播放器我用的这个第三方:https://blog.csdn.net/qq_39731011/article/details/90672491 着重说一下,因为播放器框架问题以及性能相关问题,播放完一个视频...

赵星海 1万+

Android Recyclerview列表自动播放视频

前言:最近项目比较忙很久没写博客了,一个月迭代了三个版本也是醉了。。。谁叫我们是苦逼的程序猿呢,回归正传;最近主要在弄一个跟视频有关的项目,里面也学到一些东西,现在记录一下;其中一个是仿微博的视频列表自动播放功能,具体可以看下图: 项目中视频使用的是GSYVideoPlayer 这个开源库。 ps:作者很热心,nice。 废话不多说,直接进入重点;既然要实现类似微博那种滚动列表时,处于当...

mackkill的专栏 2万+

Android在滚动列表实现视频的播放(RecyclerView)

新版的百度贴吧,网易新闻中有看视频的界面。 是随着view的滚动自动加载的。 如图所示,很方便查看。 因为项目需要,我在开发一个APP,也需要查看视频,便想实现一个差不多功能的。 经过搜索,我发现GITHUB上有这个开源的东西,可以很方便的实现这样的效果 VideoPlayerManager 试着做了个Demo,在此记录下,以后自己查起来也方便。 ...

bahunj93679的博客 1312

Android开发,使用RecyclerView实现商品列表

本文介绍了在Android实现商品列表展示的步骤:1)创建ProductInfo数据模型类存储商品信息;2)编写继承RecyclerView.Adapter的ProductListAdapter适配器类,处理数据绑定和视图展示;3)在布局文件中添加RecyclerView控件并配置布局管理器;4)创建商品项的布局文件。通过这四个步骤即可完成RecyclerView的商品列表功能实现,包含商品图片、标题、风格、尺寸和价格等信息的展示。

商城app+新闻app+备忘录记事本app视频教程在某站【浩宇软件开发】 5481

Android列表视频自动播放

IDE相关视频讲解:C语言程序设计入门之环境安装实现Android列表视频自动播放教程 介绍 作为一名经验丰富的开发者,我将教你如何在Android应用中实现列表视频自动播放功能。这对于刚入行的小白可能有些困难,但只要按照下面的步骤进行,你将轻松掌握这项技能。 流程 下面是整个实现过程的步骤表格: Android列表视频...

weixin_28837357的博客 147

Android文件列表RecyclerView中点击视频播放

Android文件列表RecyclerView中点击视频播放 1. app gradle dependencies { implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'com.google.android.exoplayer:exoplayer:2.11.1' 2. AndroidManifest...

greatyoulv的专栏 3185

Android 列表视频滑动自动播放—2—实现具体代码

RecyclerView1+RecyclerView2结构,所以视频播放时在里层RecyclerView2,如果不是二维,那么就RecyclerView2其实就是视频播放容器,dataItem.getVideoPosition()其实就是告诉我们RecyclerView2中第几条数据播放视频,默认是-1,表示没有视频,需要我们在网络请求后,遍历数据生成对应可以展示视频position。列表自动播放,我们需要监听recyclerView.addOnScrollListener(this),并实现对应方法。

qq_21467035的博客 1243

RecyclerView监听滑动,自动播放视频列表,有点意思

GitHub https://github.com/AnJiaoDe/RecyclerViewVideoPlayList APK_demo https://github.com/AnJiaoDe/RecyclerViewVideoPlayList/blob/master/app/build/outputs/apk/app-debug.apk 视频列表自动播放 1自动播放功能-用户上滑流程图 ...

奋斗的IT青年 3715

Android 企业应用系列开发教程

本课程为 Android 从0入门开发,需要有Java 基础 两种学习方式,第一种学习方式是每天学习1-2小时,大概2个月左右可以学习完成,第二种学习方式是集中上3-4天时间,系统学习一下,每次学习完成后,参照视频手动写一遍代码,学习完成后就可以上手开发项目 本课程分为六个阶段,从易到难,学习完成后,可以独立开发适用于Android平台的应用程序本课程以Demo案例入手,来详细讲解App的开发,可以帮助学员形成一定的开发思维第一阶段是 初级,从联系人列表入手,通过几个Demo讲解,可以形成初级的App开发系统思维,内容包含基础View的使用以及页面排版使用第二个阶段是通过一个App开发来讲解一般的App应用程序搭建主结构的过程,倒计时、底部导航栏等等第三个阶段是View 专题、图片专题、动画专题等等专题性的讲解,用于深化开发,是高级内容,会有原理分析以及部分源码分析、

视频播放器GSYVideoPlayer

基于IJKPlayer,实现了多功能的视频播放器。 (请仔细阅读下方各项说明,大多数问题可在下方找到解答)。 状态 功能 支持 边播边缓存,使用了AndroidVideoCache。 支持 其他协议和编码concat、rtsp、hls、rtmp、crypto、mpeg等。 支持 DEMO演示获取视频第一帧等实现。 支持 简

Android小飞の博客 1万+

RecyclerView实现图片和视频列表展示、查看、删除等功能

使用RecyclerView实现图片和视频列表的展示、查看、删除等功能,可以很好支持大数据量图片和视频的展示,滚动流畅,支持点击列表进行大图的滑动查看。

qq_34215018的博客 3224

Toro:为RecyclerView量身打造的视频列表自动播放

在移动应用开发中,视频内容的展示和播放是一个常见但复杂的需求。特别是在使用RecyclerView来展示视频列表时,如何实现流畅的自动播放功能,一直是开发者面临的挑战。今天,我们要介绍的Toro库,正是为了解决这一难题而生。 ## 项目介绍 Toro是一个专为RecyclerView设计的视频列表自动播放库。它简化了在RecyclerView实现视频自动播放的复杂性,提供了丰富的功能和灵活的...

gitblog_01173的博客 372

android recyclerview列表播放视频视频全屏翻页自动播放高仿微视之路

我这文章暂时是打广告的,要使用列表播放的朋友 http://github.com/qssq/videoplayer 视频效果http://www.iqiyi.com/w_19rxx2xtg5.html 要做2件事情,第一 要监听recyclerview的滚动状态变为空闲的时候寻找videoview 进行播放 第二 ,要再recyclerview的布局改变的时候也寻找videoview进行播放 第三...

情随事迁个人博客 712
上一篇: 【Android -- 网络请求】Retrofit + RxJava + OkHttp 网络请求
下一篇: 【Android -- 开源库】RecyclerView 的开源 LayoutManager
Kevin-Dev
博客等级 码龄10年 5万+粉丝 661原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kevin-Dev

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值