最近在做一个视频播放器

现在市场上,一个比较完善的视频播放器

大概具有以下功能:

快进、快退、声音、亮度控制

这一次就根据这几个基础的功能

通过系统的手势控制类GestureDetector来完成

做了一个实用的工具类

只需要简单的配置

就可以实现对视频播放器控件的手势进行监听

工具类内部实现了相关功能,不需要视频播放器自己写

实现了代码解耦,也方便复用

下面就开始介绍这个工具类的使用,以及实现的原理。

先把工具类的实际代码贴上来

public class VideoGestureControlUtil {private static final String TAG = "VideoGestureControlUtil";private Context mContext;private GestureDetector mGestureDetector;//手势类private MyGestureListener myGestureListener;//手势监听private OnVideoControlListener videoControlListener;//视频View回调监听private Rect VideoViewRect = null;//视频控件范围//状态相关private static int SCROLL_FLAG = 0;//记录状态private static final int SCROLL_FLAG_RESET = 0;//初始状态,无任何操作private static final int SCROLL_FLAG_TOPBOTTOM_LEFT = 1;//左边屏幕上下滑动private static final int SCROLL_FLAG_TOPBOTTOM_RIGHT = 2;//右边屏幕上下滑动private static final int SCROLL_FLAG_LEFTRIGHT = 3;//左右滑动//拖动相关protected static final float FLIP_DISTANCE = 50;//确定滑动方向的最小滑动距离private int SCROLL_VIDEO_SCROLL_RANGE = 1000;//拖动范围 0~1000private float SCROLL_VIDEO_PLAY_RANGE = 0.25f;//视频可拖动部分的范围//视频进度相关private float SCROLL_VIDEO_PLAY_INDEX = 0f;//拖动的视频进度private double videoIndex = 0;//拖动的视频毫秒数private double newVideoIndex;//拖动结束后视频的位置,单位毫秒private double SCROLL_VIDEO_PLAYING_INDEX = 0;//视频播放位置private double SCROLL_VIDEO_PLAYING_INDEX_CATCH = 0;//缓存的视频播放位置(手指按下去的视频播放位置)private double SCROLL_VIDEO_LENTH = 0;//视频总长度 单位毫秒//声音和亮度相关private AudioManager systemService;private int SCROLL_VIDEO_VOICE_INDEX = 0;//拖动的视频声音private double SCROLL_VIDEO_LIGHT_INDEX = 0;//拖动的视频亮度private int SCROLL_VOICE_MAX = 0;//声音总长度private int SCROLL_LIGHT_MAX = 255;//亮度总长度private int SCREEN_LIGHT = 0;//屏幕亮度private int SCREEN_LIGHT_CATCH = 0;//缓存的屏幕亮度private int SCREEN_VOICE = 0;//音量大小//弹框部分private Dialog indexDialog;//弹框private View videoDialogView;//弹框Viewprivate ImageView indeximg;private LinearLayout indexll, voicell;private TextView indextv;private ImageView voiceLightImg;private SeekBar voiceLightsb;private WindowManager.LayoutParams indexDialogLp;private DisplayMetrics displayMetrics;public VideoGestureControlUtil(Context context, View view) {myGestureListener = new MyGestureListener();this.mContext = context;systemService = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);SCROLL_VOICE_MAX = systemService.getStreamMaxVolume(AudioManager.STREAM_MUSIC);mGestureDetector = new GestureDetector(context, myGestureListener);mGestureDetector.setOnDoubleTapListener(myGestureListener);LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);videoDialogView = layoutInflater.inflate(R.layout.video_gesture_dialog, null);indexll = videoDialogView.findViewById(R.id.indexll);voicell = videoDialogView.findViewById(R.id.voicell);indeximg = videoDialogView.findViewById(R.id.indeximg);indextv = videoDialogView.findViewById(R.id.indextv);voiceLightImg = videoDialogView.findViewById(R.id.voice_light_img);voiceLightsb = videoDialogView.findViewById(R.id.voice_light_sb);indexDialog = new Dialog(context);/*随意定义个Dialog*/Window dialogWindow = indexDialog.getWindow();/*实例化Window*/indexDialogLp = dialogWindow.getAttributes();/*实例化Window操作者*/indexDialogLp.x = 0; // 新位置X坐标indexDialogLp.y = 0; // 新位置Y坐标dialogWindow.setGravity(Gravity.CENTER);dialogWindow.getDecorView().setBackground(null);dialogWindow.setAttributes(indexDialogLp);/*放置属性*/indexDialog.setContentView(videoDialogView, new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT));dialogWindow.setDimAmount(0);dialogWindow.setBackgroundDrawableResource(android.R.color.transparent);try {int dividerID = indexDialog.getContext().getResources().getIdentifier("android:id/titleDivider", null, null);View divider = indexDialog.findViewById(dividerID);if (divider != null) {divider.setBackgroundColor(Color.TRANSPARENT);}} catch (Exception e) {//上面的代码,是用来去除Holo主题的蓝色线条e.printStackTrace();}indexDialog.setCanceledOnTouchOutside(false);}public boolean touch(MotionEvent event) {boolean detectedUp = event.getAction() == MotionEvent.ACTION_UP;if (!mGestureDetector.onTouchEvent(event) && detectedUp) {//手指抬起时触发if (SCROLL_FLAG == SCROLL_FLAG_LEFTRIGHT) {//设置当前播放进度videoControlListener.setScrollVideoPlayingIndex(SCROLL_VIDEO_PLAYING_INDEX);}SCROLL_FLAG = SCROLL_FLAG_RESET;VideoViewRect = null;dismissIndexDialog();} else if (event.getAction() == MotionEvent.ACTION_CANCEL) {//熄屏时会触发SCROLL_FLAG = SCROLL_FLAG_RESET;VideoViewRect = null;dismissIndexDialog();}return true;}/** 手势监听类*/class MyGestureListener extends GestureDetector.SimpleOnGestureListener {public MyGestureListener() {super();}@Overridepublic boolean onDoubleTap(MotionEvent e) {Log.e(TAG, "双击");videoControlListener.onDoubleTap();return true;}@Overridepublic boolean onSingleTapConfirmed(MotionEvent e) {Log.e(TAG, "单击");videoControlListener.onSingleTap();return true;}@Overridepublic boolean onDoubleTapEvent(MotionEvent e) {Log.e(TAG, "onDoubleTapEvent");return true;}@Overridepublic boolean onContextClick(MotionEvent e) {Log.e(TAG, "onContextClick");return true;}@Overridepublic boolean onDown(MotionEvent e) {Log.e(TAG, "onDown");return true;}@Overridepublic void onShowPress(MotionEvent e) {Log.e(TAG, "onShowPress");}@Overridepublic boolean onSingleTapUp(MotionEvent e) {Log.e(TAG, "onSingleTapUp");return true;}@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {switch (SCROLL_FLAG) {case SCROLL_FLAG_RESET:WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);displayMetrics = new DisplayMetrics();windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);if (e1.getX() < FLIP_DISTANCE || e1.getX() > displayMetrics.widthPixels - FLIP_DISTANCE || e1.getY() > displayMetrics.heightPixels - FLIP_DISTANCE) {Log.d(TAG, "onScroll: 无效动作" + e1.getX());//这里主要是处理屏幕边缘滑动的问题,防止和边缘的滑动返回等操作冲突,所以屏蔽了边缘的部分滑动。return true;}if (VideoViewRect == null) {VideoViewRect = videoControlListener.getVideoViewRect();Log.d(TAG, "高度: " + displayMetrics.heightPixels);Log.d(TAG, "宽度: " + displayMetrics.widthPixels);if (VideoViewRect != null) {Log.d(TAG, "left: " + VideoViewRect.left);Log.d(TAG, "right: " + VideoViewRect.right);Log.d(TAG, "top: " + VideoViewRect.top);Log.d(TAG, "bottom: " + VideoViewRect.bottom);indexDialogLp.x = ((VideoViewRect.right - VideoViewRect.left) / 2) - (displayMetrics.widthPixels / 2);indexDialogLp.y = ((VideoViewRect.bottom - VideoViewRect.top) / 2) - (displayMetrics.heightPixels / 2) + VideoViewRect.top;}}//初始化,没有滑动方向if (e1.getX() - e2.getX() > FLIP_DISTANCE || e2.getX() - e1.getX() > FLIP_DISTANCE) {Log.i(TAG, "向左右滑...");SCROLL_FLAG = SCROLL_FLAG_LEFTRIGHT;SCROLL_VIDEO_PLAY_INDEX = 0f;videoIndex = 0;//得到视频长度和播放位置SCROLL_VIDEO_LENTH = videoControlListener.getScrollVideoLenth();SCROLL_VIDEO_PLAYING_INDEX_CATCH = SCROLL_VIDEO_PLAYING_INDEX = videoControlListener.getScrollVideoPlayingIndex();indexll.setVisibility(View.VISIBLE);voicell.setVisibility(View.GONE);showIndexDialog();return true;} else if (e1.getY() - e2.getY() > FLIP_DISTANCE || e2.getY() - e1.getY() > FLIP_DISTANCE) {if (e1.getX() < (displayMetrics.widthPixels / 2)) {//左边上下滑动滑动SCROLL_FLAG = SCROLL_FLAG_TOPBOTTOM_LEFT;Log.i(TAG, "左屏幕向上下滑...");SCROLL_VIDEO_LIGHT_INDEX = 0;voiceLightImg.setImageResource(R.drawable.video_lightimg);try {//当前屏幕亮度SCREEN_LIGHT_CATCH = SCREEN_LIGHT = Settings.System.getInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS);voiceLightsb.setMax(SCROLL_LIGHT_MAX);voiceLightsb.setProgress(SCREEN_LIGHT);} catch (Settings.SettingNotFoundException e) {e.printStackTrace();}} else {//右边上下滑动SCROLL_FLAG = SCROLL_FLAG_TOPBOTTOM_RIGHT;Log.i(TAG, "右屏幕向上下滑...");SCROLL_VIDEO_VOICE_INDEX = 0;//当前声音大小SCREEN_VOICE = systemService.getStreamVolume(AudioManager.STREAM_MUSIC);voiceLightImg.setImageResource(R.drawable.video_voiceimg);voiceLightsb.setMax(SCROLL_VOICE_MAX);voiceLightsb.setProgress(SCREEN_VOICE);}indexll.setVisibility(View.GONE);voicell.setVisibility(View.VISIBLE);showIndexDialog();return true;}break;case SCROLL_FLAG_TOPBOTTOM_LEFT://设置亮度if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {if (!Settings.System.canWrite(mContext)) {Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);intent.setData(Uri.parse("package:" + mContext.getPackageName()));intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);mContext.startActivity(intent);} else {//左屏幕上下滑动SCROLL_VIDEO_LIGHT_INDEX += distanceY;Log.e(TAG, "toponScroll:" + SCROLL_VIDEO_LIGHT_INDEX);double lightIndex = (((double) SCROLL_VIDEO_LIGHT_INDEX / (double) SCROLL_VIDEO_SCROLL_RANGE)) * SCROLL_LIGHT_MAX;
//                            lightIndex = -lightIndex;//取反double newLightIndex = lightIndex + SCREEN_LIGHT_CATCH;if (newLightIndex > SCROLL_LIGHT_MAX) {//说明拖动到末尾SCREEN_LIGHT = SCROLL_LIGHT_MAX;} else if (newLightIndex < 0) {//说明拖动到开头SCREEN_LIGHT = 0;} else {SCREEN_LIGHT = (int) newLightIndex;}// 申请权限后做的操作// 设置系统亮度Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, SCREEN_LIGHT);Log.d(TAG, "屏幕亮度: " + SCREEN_LIGHT);voiceLightsb.setProgress(SCREEN_LIGHT);}}return true;
//                    break;case SCROLL_FLAG_TOPBOTTOM_RIGHT://右屏幕上下滑动 设置声音SCROLL_VIDEO_VOICE_INDEX += distanceY;
//                    SCROLL_VIDEO_VOICE_INDEX = (int) distanceY;double voiceIndex = (((double) SCROLL_VIDEO_VOICE_INDEX / (double) SCROLL_VIDEO_SCROLL_RANGE)) * SCROLL_VOICE_MAX;
//                            lightIndex = -lightIndex;//取反if (voiceIndex > 1) {voiceIndex = 1;SCROLL_VIDEO_VOICE_INDEX = 0;} else if (voiceIndex < -1) {voiceIndex = -1;SCROLL_VIDEO_VOICE_INDEX = 0;} else {voiceIndex = 0;}Log.d(TAG, "voiceIndex: " + voiceIndex);double newVoiceIndex = voiceIndex + SCREEN_VOICE;if (newVoiceIndex > SCROLL_VOICE_MAX) {//说明拖动到末尾SCREEN_VOICE = SCROLL_VOICE_MAX;} else if (newVoiceIndex < 0) {//说明拖动到开头SCREEN_VOICE = 0;} else {SCREEN_VOICE = (int) newVoiceIndex;}Log.d(TAG, "结束声音大小: " + SCREEN_VOICE);systemService.setStreamVolume(AudioManager.STREAM_MUSIC, SCREEN_VOICE, AudioManager.FLAG_PLAY_SOUND);voiceLightsb.setProgress(SCREEN_VOICE);return true;
//                    break;case SCROLL_FLAG_LEFTRIGHT://左右滑动if (Math.abs(distanceY) > 1) {break;}SCROLL_VIDEO_PLAY_INDEX += distanceX;//得到当前视频进度videoIndex = (((double) SCROLL_VIDEO_PLAY_INDEX / (double) SCROLL_VIDEO_SCROLL_RANGE)) * (SCROLL_VIDEO_LENTH * SCROLL_VIDEO_PLAY_RANGE);//说明是进度滑动videoIndex = -videoIndex;//取反newVideoIndex = videoIndex + SCROLL_VIDEO_PLAYING_INDEX_CATCH;
//                    Log.d("print", "videoIndex: " + videoIndex + "-----newVideoIndex:" + newVideoIndex);if (newVideoIndex > SCROLL_VIDEO_LENTH) {//说明拖动到末尾SCROLL_VIDEO_PLAYING_INDEX = SCROLL_VIDEO_LENTH;} else if (newVideoIndex < 0) {//说明拖动到开头SCROLL_VIDEO_PLAYING_INDEX = 0;} else {SCROLL_VIDEO_PLAYING_INDEX = newVideoIndex;}if ((videoIndex / 1000) > 0f) {//快进indeximg.setImageResource(R.drawable.indeximg_left);} else {//快退indeximg.setImageResource(R.drawable.indeximg_right);}
//                    Log.d("print", "滑动结束,拖动进度为" + videoIndex / 1000 + "秒");
//                    Log.d("print", "滑动结束,当前进度为" + SCROLL_VIDEO_PLAYING_INDEX / 1000 + "秒");indextv.setText(parse2TimeStr(SCROLL_VIDEO_PLAYING_INDEX) + "/" + parse2TimeStr(SCROLL_VIDEO_LENTH));return true;
//                    break;}return false;}@Overridepublic void onLongPress(MotionEvent e) {Log.e(TAG, "onLongPress");}@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {Log.e(TAG, "onFling");return false;}}private void showIndexDialog() {indexDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);indexDialog.show();}private void dismissIndexDialog() {if (indexDialog != null) {indexDialog.dismiss();}}public interface OnVideoControlListener {//得到视频总长度double getScrollVideoLenth();//得到视频当前播放位置double getScrollVideoPlayingIndex();//设置视频当前播放位置void setScrollVideoPlayingIndex(double playIndex);//双击屏幕void onDoubleTap();//单击屏幕void onSingleTap();//得到视频控件的大小范围Rect getVideoViewRect();}public void setOnVideoControlListener(OnVideoControlListener videoControlListener) {this.videoControlListener = videoControlListener;}private String parse2TimeStr(double timeStr) {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss");simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+00:00"));if (timeStr / 1000 > 3600) {//说明超过一小时simpleDateFormat.applyPattern("HH:mm:ss");} else {//说明没超过一小时simpleDateFormat.applyPattern("mm:ss");}return simpleDateFormat.format(timeStr);}}

使用起来其实也很简单,我们这里自定义了一个控件MyVideoView

假设这个MyVideoView就是我们的视频控件

那么如何让这个控件使用我们的工具类

从而实现手势控制播放进度,声音,亮度的功能呢?

看下面的代码

public class MyVideoView extends View {private VideoGestureControlUtil videoGestureControlUtil;private double playIndex = 0;//视频当前播放位置public MyVideoView(Context context) {this(context, null);}public MyVideoView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public MyVideoView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}private void init(Context context) {videoGestureControlUtil = new VideoGestureControlUtil(context, this);videoGestureControlUtil.setOnVideoControlListener(new VideoGestureControlUtil.OnVideoControlListener() {@Overridepublic double getScrollVideoLenth() {return 36 * 60 * 1000;//36分钟的视频}@Overridepublic double getScrollVideoPlayingIndex() {return playIndex;}@Overridepublic void setScrollVideoPlayingIndex(double playIndex) {MyVideoView.this.playIndex = playIndex;}@Overridepublic void onDoubleTap() {Log.d("print", "onSingleTap: 双击");}@Overridepublic void onSingleTap() {Log.d("print", "onSingleTap: 单击");}@Overridepublic Rect getVideoViewRect() {Rect rect = new Rect();boolean localVisibleRect = getGlobalVisibleRect(rect);if (localVisibleRect) {return rect;}return null;}});}@Overridepublic boolean onTouchEvent(MotionEvent event) {return videoGestureControlUtil.touch(event);}
}

可以看到实现起来其实很简单

核心在onTouchEvent里面

把控件的触摸监听反馈给这个工具类即可

同时需要实现五个接口

这五个接口

需要返回视频的时长,当前播放进度

同时拖动完成后,会返回当前应该播放的进度

单击,双击的监听也都已经实现

这边需要重点介绍getVideoViewRect这个方法

这个方法是用来返回这个控件在屏幕上的位置的

具体可以参考该篇博客

(转载)Android:6种方式让你高效 & 正确地获取View的坐标位置

为什么需要控件把位置传递过去呢?

那是因为我们的工具类里面

已经实现了手指拖动时播放进度、亮度,声音变化时的弹窗

需要根据用户的位置,来调整这个弹窗的位置

当然,也可以去掉这个功能,自己注释掉工具类的dialog代码即可

关于dialog弹窗位置如何设置,可以参考这篇博客或者百度搜索下即可

(转载)Android 关于dialog的显示位置设置

这样,这个视频播放器就实现了通过手势控制播放进度,亮度,声音,以及单击,双击的功能

说完了基本使用

再来说说原理

首先是手势的监听

可以参考这篇博客

(转载)Android GestureDetector详解

接下来分析我们工具类里的具体实现

parse2TimeStr方法主要是对时间进行格式化

方便弹窗的时候,播放进度的文字显示一致

touch方法主要是用来监听手指抬起(MotionEvent.ACTION_UP)

或者手指按住但此时息屏(MotionEvent.ACTION_CANCEL)的情况

这个时候需要关闭弹窗,同时根据拖动的距离,去回调方法

告诉视频播放器当前拖动到的播放进度

核心监听MyGestureListener类

就是对双击,单击,拖动进行处理的地方了

注意,这边将拖动进行了不同状态的划分

分别是

SCROLL_FLAG_RESET                            初始状态,也就是手指没按下去的状态

SCROLL_FLAG_TOPBOTTOM_LEFT       左边屏幕上下滑动,控制亮度

SCROLL_FLAG_TOPBOTTOM_RIGHT    右边屏幕上下滑动,控制声音

SCROLL_FLAG_LEFTRIGHT                    左右滑动,控制播放进度

手指拖动时会判断状态,抬起后状态复原

另外注意这段代码

if (e1.getX() < FLIP_DISTANCE || e1.getX() > displayMetrics.widthPixels - FLIP_DISTANCE || e1.getY() > displayMetrics.heightPixels - FLIP_DISTANCE) {Log.d(TAG, "onScroll: 无效动作" + e1.getX());//这里主要是处理屏幕边缘滑动的问题,防止和边缘的滑动返回等操作冲突,所以屏蔽了边缘的部分滑动。return true;}

主要来防止在屏幕边缘(比如底部或者两侧)进行拖动,不需要时可以注释掉

dialog的位置设置主要是以下代码,可以根据实际需要进行修改

indexDialogLp.x = ((VideoViewRect.right - VideoViewRect.left) / 2) - (displayMetrics.widthPixels / 2);
indexDialogLp.y = ((VideoViewRect.bottom - VideoViewRect.top) / 2) - (displayMetrics.heightPixels / 2) + VideoViewRect.top;

最后,因为弹窗需要用到一些图片什么的,这里也一并上传

在网盘里下载即可

链接:

https://pan.baidu.com/s/1_E8IeJhXz2jTwdigLGgkaA 
提取码:

kkzu

(原创)视频播放器的手势控制工具类相关推荐

  1. 测试:视频播放器、视频通话、视频类网站 测试点

    文章目录 前言 一.视频播放器测试用例 二.视频通话测试用例 三.视频网站测试用例 参考文章 前言 百度和字节面试被问到视频相关的测试用例,作为一个研究方向是视频编解码的人来说,这个问题回答不好,着实 ...

  2. ( 持续更新,目前含 200+ 工具类 ) DevUtils 是一个 Android 工具库, 主要根据不同功能模块,封装快捷使用的工具类及 API 方法调用。

    DevUtils GitHub About ( 持续更新,目前含 200+ 工具类 ) Roadmap DevUtils 是一个 Android 工具库,主要根据不同功能模块,封装快捷使用的工具类及 ...

  3. 基于NDK、C++、FFmpeg的android视频播放器开发实战-夏曹俊-专题视频课程

    基于NDK.C++.FFmpeg的android视频播放器开发实战-1796人已学习 课程介绍         课程包含了对流媒体(拉流)的播放,演示了播放rtmp的香港卫视,支持rtsp摄像头和ht ...

  4. 视频教程-基于NDK、C++、FFmpeg的android视频播放器开发实战-Android

    基于NDK.C++.FFmpeg的android视频播放器开发实战 夏曹俊:南京捷帝科技有限公司创始人,南京大学计算机硕士毕业,有15年c++跨平台项目研发的经验,领导开发过大量的c++虚拟仿真,计算 ...

  5. Android的媒体播放器——简易视频播放器(详解)

    一.案例效果 二.主界面的设计与功能 2.1 案例效果 2.2 布局界面 activity_main.xml 参考代码: <?xml version="1.0" encodi ...

  6. (持续更新, 目前含100+工具类) DevUtils 是一个 Android 工具库

    DevUtils Github About (持续更新, 目前含100+工具类) DevUtils 是一个 Android 工具库, 主要根据不同功能模块,封装快捷使用的工具类及 API 方法调用. ...

  7. Android Studio——简单视频播放器

    1.界面分析 三个界面:主界面,视频列表界面,视频播放界面,如下图所示: 2.实现思路 (1)主界面设计 很简单,就一个背景和一个控件,然后为控件设置点击事件,然后跳转到视频列表界面 activity ...

  8. 线程工具类 - CountDownLatch(倒计时器)

    CountDownLatch官方文档 一.原理 CountDownLatch是一个非常实用的多线程控制工具类.Count Down在英文中意为倒计时,Latch意为门闩,可以简单的将CountDown ...

  9. Android开发之视频播放器

    Android开发之视频播放器 一.效果图 二.build.gradle中导入依赖 三.主布局文件(activity_video) 四.布局文件(video_item) 五.布局文件(activity ...

最新文章

  1. SAP RETAIL 如何确定自动补货触发的单据类型
  2. Spring Boot项目(Maven\Gradle)三种启动方式及后台运行详解
  3. Qt Creator编码
  4. 关于Vue.use()详解
  5. node模块加载机制。
  6. 原好未来CFO罗戎加盟百度 出任百度集团CFO
  7. TensorFlow基础篇(七)——tf.nn.conv2d()
  8. 关于mysql的wait_timeout参数 设置不生效的问题【转】
  9. 谁是应用软件商店急需的外援
  10. 小米网络信号测试软件,WiFi测速
  11. 搜狗微信临时链接转换成永久链接
  12. [深度长文] 996的经济学
  13. 如何安装PrCC2019
  14. 网红神盾七号重疾险再创新高,自带住院津贴,还能赔两次!
  15. Google AppSheet: 无需编程构建零代码应用
  16. ROS MoveIT1(Noetic)安装总结
  17. vue下载excel模板
  18. Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day7】 —— 数据库2(事务)
  19. 天眼查新方式信息爬取
  20. java 布尔值的判断

热门文章

  1. wordpress 多语言插件-wordpress 不同双语言三语言插件
  2. 51单片机之定时器篇
  3. CentOS7.3服务器内网离线部署docker和容器
  4. On-chip Variations
  5. IE文档模式--添加水印
  6. 知乎和简书的夜间模式实现套路
  7. sout语句在控制台乱码
  8. Java实现 蓝桥杯 基础练习 字母图形
  9. 利用Microsoft.XMLHTTP和Microsoft.XMLDOM实现xml文件的读取
  10. 腾讯 Android 面试题