Android 自定义viewpage + videoview 实现竖屏视频播放效果

  • 效果图
  • 实现步骤
  • 前提概要
  • 自定义 viewpage
  • 自定义 videoview
  • 主逻辑代码


效果图

Android RadioGroup竖屏 安卓手机怎么竖屏播放_android studio

由于用的是viewpage 和 vidoview 虽然查了很多资料,最后Bug还是有点多。

实现步骤

前提概要

如果你是和我一样使用的是网上开源的接口来播放视频那么不妨在build.gradle加上这些

implementation 'com.alibaba:fastjson:1.2.68'
    implementation 'com.squareup.okhttp3:okhttp:4.4.1'

还需要在AndroidManifest 打开权限

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

如果使用的是明文HTTP,则需要在application中加上:

android:usesCleartextTraffic="true"

自定义 viewpage

要想实现viewpage竖直方向滑动需要自己写一个类继承androidx.viewpager.widget.ViewPage
建议先看一下官方文档
https://developer.android.com/training/animation/screen-slide.html?hl=zh-cn

实现向上下滑动的VerticalPageTransformer 才是主要的代码

public class VerticalViewPager extends ViewPager {
    public VerticalViewPager(Context context) {
        super(context);
        init();
    }

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

    private void init() {
        // The majority of the magic happens here
        setPageTransformer(true, new VerticalPageTransformer());
        // The easiest way to get rid of the overscroll drawing that happens on the left and right
        setOverScrollMode(OVER_SCROLL_NEVER);
    }

    private class VerticalPageTransformer implements ViewPager.PageTransformer {
        @Override
        public void transformPage(View view, float position) {
            if (position < -1) { // [-Infinity,-1)
                // This page is way off-screen to the left.\
                view.setAlpha(0);
            } else if (position <= 1) { // [-1,1]
                view.setAlpha(1);
                // Counteract the default slide transition
                view.setTranslationX(view.getWidth() * -position);
                //set Y position to swipe in from top
                float yPosition = position * view.getHeight();
                view.setTranslationY(yPosition);
            } else { // (1,+Infinity]
                // This page is way off-screen to the right.
                view.setAlpha(0);
            }
        }
    }

    /**
     * Swaps the X and Y coordinates of your touch event.
     */
    private MotionEvent swapXY(MotionEvent ev) {
        float width = getWidth();
        float height = getHeight();
        float newX = (ev.getY() / height) * width;
        float newY = (ev.getX() / width) * height;
        ev.setLocation(newX, newY);
        return ev;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev){
        boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
        swapXY(ev); // return touch coordinates to original reference frame for any child views
        return intercepted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(swapXY(ev));
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    }

}

要使用的时候需要在main.xml文档里全类名这样引用就行了

<com.mhr.view.VerticalViewPager
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center">
             </com.mhr.view.VerticalViewPager>

由于在外面的布局还加了其它的东西,全部代码我就放在github上面了。

在来写每个页面里要放的内容。
创建个xml文件
eg.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:orientation="vertical">
        <!-- A CardView that contains a TextView -->


        <com.mhr.view.CustomVideoView
            android:id="@+id/videoView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent">
        </com.mhr.view.CustomVideoView>

    </LinearLayout>

这里使用的是自定义的videoview作用是强制占满全屏,不过这样会导致画面拉伸严重,解决方法也有建议换一个播放器。本来想使用recycleview和饺子视频播放的,但才刚开始还是从简单的来。

自定义 videoview

写一个类继承 android.widget.VideoView; 主要是在onMeasure中setMeasuredDimension。还需要写一个viewpage的adapter这在后面放出。

public class CustomVideoView extends VideoView {
    public CustomVideoView(Context context) {
        super(context);
    }

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

    public CustomVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public CustomVideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = getDefaultSize(0,widthMeasureSpec);
        int height = getDefaultSize(0,heightMeasureSpec);

        setMeasuredDimension(width,height);
    }

}

主逻辑代码

只放出部分代码。
使用okhttp获得接口返回的json数据,再通过fastjson解析json数组,将json数组放到videobean中。

以下是部分代码

private class inintData implements Runnable{
        //接受信息
        @SuppressLint("HandlerLeak") Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //msg.what对应子线程中msg的标签,在子线程中进行赋值
                if(msg.what==1){
                    videoBeans = (List<VideoBean>) msg.obj;
                    //开始设置viewpage
                    viewPager.setCurrentItem(0);
                    viewPager.setOffscreenPageLimit(2);
                    viewPager.setAdapter(new MyViewPagerAdapter(MainActivity.this,videoBeans));
                }else {
                    Toast.makeText(MainActivity.this,"网络请求错误",Toast.LENGTH_SHORT).show();
                }
            }
        };
        @Override
        public void run() {
            HttpUtils.sendOkHttpRequest(url,new Callback(){
                @Override
                public void onFailure(Call call, IOException e) {
                    //异常处理 
                }
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    String httpRs = response.body().string();
                    JSONObject jsonObject = null;
                    try {
                        jsonObject = new JSONObject(httpRs);
                        String code = jsonObject.getString("code");
                        if (code.equals("200")) {
                            String arr = jsonObject.getString("result");
                            if (arr.length() > 1) {
                                List<VideoBean> videoBeans = JSON.parseArray(arr, VideoBean.class);
                                Message message = new Message();
                                message.what = 1;
                                message.obj = videoBeans;
                                mHandler.sendMessage(message);
                            }
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

获得了数据传到viewpage中,其中videoview和viewpage的也就只有这几个
viewPager.setCurrentItem(0);
viewPager.setOffscreenPageLimit(2);
viewPager.setAdapter(new MyViewPagerAdapter(MainActivity.this,videoBeans));

下面是为viewpage设置适配器:

public class MyViewPagerAdapter extends PagerAdapter {

    Context context;
    //主线程传来的数据 每一个videoBean 是一个数据集
    private List<VideoBean> videoBeans;
    //存放viewpage视频信息
    private Map<Integer,VideoView> mapVideo = new HashMap<>();
    //当前的view
    private View mCurrentView;
    //视频在哪
    int point;

    public MyViewPagerAdapter(){
    }

    public MyViewPagerAdapter(Context context,List<VideoBean> videoBeans){
            this.context = context;
            this.videoBeans = videoBeans;
    }

    @Override
    public int getCount() {
        //页卡的数量
        return videoBeans.size();
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
        return view == object;
    }


    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        System.out.println(position+"---------------"+container.getChildCount()+"-----"+videoBeans.size());
        View view = View.inflate(context, R.layout.main_item, null);
        final VideoView currentVideoView = view.findViewById(R.id.videoView);
        VideoView beforeVideo = null;
        currentVideoView.setVideoPath(videoBeans.get(position).getVideo());
        currentVideoView.setKeepScreenOn(true);
        currentVideoView.requestFocus();
        currentVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                mp.setLooping(true);
            }
        });

        //设置缩率图
        BitmapDrawable imageBitmap = new BitMapUtils().getImageBitmap(videoBeans.get(position).getThumbnail());
      //  System.out.println( null == imageBitmap ? "111" : "222"); 判断bitmap是否为空
        currentVideoView.setBackground(imageBitmap);
        currentVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                currentVideoView.setBackground(null);
            }
        });

        mapVideo.put(position,currentVideoView);
        container.addView(view);
        return view;
    }


    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        container.removeView((View) object);
    }


    @Override
    public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
    }

    @Nullable
    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void finishUpdate(@NonNull ViewGroup container) {
        System.out.println(mapVideo.size()+"mapvideosize");

        //准备开始时
        if(mapVideo.size()  == 2){
            mapVideo.get(0).start();
            mapVideo.get(1).pause();
        }else {
            mapVideo.get(mapVideo.size()-1).pause();
            mapVideo.get(mapVideo.size()-2).start();
            mapVideo.get(mapVideo.size()-3).pause();

        }

        //向上翻时
        if(!getCurrentVideoView(mCurrentView).isPlaying()){
            getCurrentVideoView(mCurrentView).start();
            //当前位置的viedoview正在播放时
            if(point >= 1) {
                if (mapVideo.get(point).isPlaying()) {
                    mapVideo.get(point+1).pause();
                    mapVideo.get(point-1).pause();
                }
            }else {
                mapVideo.get(point+1).pause();
            }
        }


    }

    @Override
    public void startUpdate(@NonNull ViewGroup container) {


    }

    @Override
    public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        super.setPrimaryItem(container, position, object);
        mCurrentView = (View)object;
        point = position;
        System.out.println(point+"这是point");
    }

    public VideoView getCurrentVideoView(View currentView){
        return currentView.findViewById(R.id.videoView);
    }
}

先不谈有些代码看起来很蠢,videoview的bug着实有点多,这边的代码写的有些混乱。
具体说一下遇到的问题吧。
在viewpage 放入 viedoview 中 ,运行会出下如下两种情况
1、预加载的视频都进行了播放
2、翻页时会出现白屏。
1、的话具体逻辑代码我也懒的改了,2的出现的问题应该是加了layout的缘故,设置主题透明就行了。
主题透明经过我的多此设置这样好
AndroidManifest.xml 的 application中
android:theme="@style/Transparent"
在style.xml中加上,现在写着看上去也很蠢,害,初学者。

<style name="Transparent" parent="EasyPermissions.Transparent">
        <item name="android:windowIsTranslucent">false</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowIsFloating">false</item>
        <item name="android:backgroundDimEnabled">false</item>
        <item name="windowActionBar">false</item>
        <item name="android:windowBackground">@android:color/transparent</item>
    </style>

后面的个人主页的话放出来给你们看看
用了fragment + tablayout + viewpage ,代码倒是挺简单的。也尝试着在欢迎界面使用videoview。

Android RadioGroup竖屏 安卓手机怎么竖屏播放_android_02

Android RadioGroup竖屏 安卓手机怎么竖屏播放_ide_03