Android 自定义viewpage + videoview 实现竖屏视频播放效果
- 效果图
- 实现步骤
- 前提概要
- 自定义 viewpage
- 自定义 videoview
- 主逻辑代码
效果图
由于用的是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。