01 项目介绍
需求分析:产品经理拿市面上有的播放器玩玩,写出需求
1. 引导界面
2. 主界面
2.1 标题
2.2 指示器
3. 视频播放界面
3.1 填充视频列表
3.2 视频播放模块(VideoView)
3.3 系统电量
3.4 音量控制
3.5 手势滑动,控制音量和屏幕亮度
3.6 播放进度控制
3.7 隐藏控制面板(单击、双击、长按)
3.8 全屏
3.9 响应文件管理器和网络调用
3.10 集成第三方视频插件(Vitamio)
优点:1> 性能强,效率高,不卡顿
2> 支持的音频格式更多,比系统的多很多
4. 音乐播放界面
4.1 填充音乐列表
4.2 后台Service播放歌曲
4.3 示波器
4.4 播放顺序
4.5 自定义布局的通知
5. 自定义歌词控件
#项目框架搭建
02 项目初始化
包结构划分:
1.按照业务逻辑划分
银行系统:
转账 com.icbc.cash
短信 com.icbc.sms
贷款 com.icbc.pay
2.按照功能模块(Android推荐使用此方法)
activity : cn.itcast.mobileplayer.activity
fragment: cn.itcast.mobileplayer.fragment
服务: cn.itcast.mobileplayer.service
广播接收者:cn.itcast.mobileplayer.receiver
工具类: cn.itcast.mobileplayer.utils
数据库: cn.itcast.mobileplayer.db
JavaBean: cn.itcast.mobileplayer.bean
03 创建BaseActivity
作用:1. 规范代码结构
2. 提供公共方法
04 抽取BaseFragment
作用:1. 规范代码结构
2. 提供公共方法
UIInterface接口:抽取BaseActivity和BaseFragment的公共方法
#闪屏页和主界面
05 创建SplashActivity闪屏页
作用:初始化数据;示公司Logo;展示广告(网易新闻客户端)
延迟2秒进入主界面
06 主界面布局
完成主界面布局
抽取BaseFragment
07 ViewPager生成两个界面
左右滑动的视频和音频界面
#标题颜色和字体大小
08 滑动界面修改标题颜色
OnPageChangeListener的onPageSelected监听方法修改标题的颜色和大小
09 滑动界面修改标题大小
添加nineoldandroids属性动画包,用属性动画scaleX和scaleY缩放
ViewPropertyAnimator.animate(tvVideo).scaleX(1.2f);
ViewPropertyAnimator.animate(tvVideo).scaleY(1.2f);
10 处理标题点击事件
设置当前选择页面
修改标题颜色和字体大小
#指示器宽度和位置
11 初始化指示器的宽度
设置指示器的宽度 = 手机屏幕/2
指示器宽度:mIndiactor.getLayoutParams().width
view.requestLayout()重新计算大小,并刷新界面
view.invalidate()刷新界面
12 滑动界面修改指示器位置
-
当页面左右滑动的时候移动指示器
int width = mIndiactor.getWidth(); //2. 起始位置 = position*指示器宽度 int startX = position*width; //3. 偏移位置 = 指示器宽度*手指划过屏幕百分比 float offsetX = width*positionOffset; //1. 指示器的位置=起始位置+偏移位置 float transX = startX+offsetX; //设置滑动位置 ViewHelper.setTranslationX(mIndiactor,transX);
#视频列表展示
13 从MediaProvide里查询视频数据
-
从MediaProvide内容提供者中查询多媒体
遍历SD卡,效率太低,速度慢。为了提高效率,google工程师把音视频、图片的存储路径都放到数据库里了,读取数据库比遍历SD里所有文件快多了 -
查看external数据库的files表
文件路径:data 名字:title 时间:duration 大小:size -
查询Cursor
ContentResolver resolver = getActivity().getContentResolver(); Cursor cursor = resolver.query(Media.EXTERNAL_CONTENT_URI,new String[]{Media._ID, Media.DATA, Media.SIZE,Media.DURATION, Media.TITLE}, null, null, null); 小细节:不写_id会报错
14 提取ListView布局
抽取listview到一个单独的xml布局文件中,项目中有相同样式的listview就可以直接include引用。
15 使用CursorAdapter显示视频列表
newView()创建新的view
bindView()给View填充要展示的数据
16 格式化文件大小
17 格式化时间字符串
private static final int SEC = 1000;
private static final int MIN = 60*1000;
private static final int HOUR= 60*60*1000;
/**
* 格式化时间
* @param time
* @return
* 分秒 00:00
时分秒:00:00:00
*/
public static String formatTime(int time){
int hour = time/HOUR;
int min = time%HOUR/MIN;
int sec = time %MIN/SEC;
String t;
if (hour < 1){
//分秒 00:00
t = String.format("%02d:%02d",min,sec);
}else{
//时分秒:00:00:00
t = String.format("%02d:%02d:%02d",hour,min,sec);
}
return t;
}
#视频播放
18 点击跳转到视频播放界面
用意图传递bean需实现Serializable接口
19 简单视频播放
用VideoView播放视频
屏幕横向:
android:screenOrientation="landscape"
视频播放页全屏:
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
20 完成顶部栏布局
21 自定义SeekBar
指示器图片
android:thumb="@drawable/video_progress_thumb"
指示器只显示一半的解决办法
android:thumbOffset="0dp"
修改进度条、背景色的图片
参照style="@android:style/Widget.SeekBar"
创建 android:progressDrawable="@drawable/video_seekbar_drawable"
<SeekBar
android:id="@+id/sb_video_voice"
style="@android:style/Widget.SeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="5dp"
android:minHeight="2dp"
android:progress="50"
android:progressDrawable="@drawable/video_seekbar_drawable"
android:thumb="@drawable/video_progress_thumb"
android:thumbOffset="0dp"/>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
//进度条的背景
<item android:id="@android:id/background" android:drawable="@drawable/video_seekbar_bg"/>
//网络缓冲第二进度
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="5dip" />
<solid android:color="#77ffffff" />
</shape>
</clip>
</item>
//进度条的进度
<item android:id="@android:id/progress" android:drawable="@drawable/video_seekbar_progress"/>
</layer-list>
22 完成视频播放界面布局
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/btn_play_pressed"
android:state_pressed="true"/>
<item android:drawable="@drawable/btn_play_normal"/>
</selector>
24 播放暂停状态切换
MediaPlayer 是Android系统里唯一用于播放音视频的类
VideoView 是SurfaceView的子类,封装了MediaPlayer,只是用于视频画面的显示
25 MediaPlayer生命周期
1. 空闲:reset() new MediaPlayer()
2. 初始化:setDataSource()
3. 准备:
* 同步:prepare()
* 异步:prepareAsync()
4. 播放状态:start()
5. 播放完成
6. 停止:stop()
7. 暂停:pause()
8. 结束:release()
9. 错误:出错了
04.02_初始化项目&包结构划分
04.03_创建BaseActivity
/**
*模板设计模式
*/
public abstract class BaseActivity extends FragmentActivity implements UiInterface {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout());
initView();
initData();
initListener();
}
}
04.04_创建BaseFragment
public abstract class BaseFragment extends Fragment implements UiInterface {
private View mView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mView = inflater.inflate(getLayout(), null);
initView();
initData();
initListener();
return mView;
}
/**
* 加载fragment布局里的控件
* @param resId
* @return
*/
创建接口
public interface UiInterface {
/**
* 加载布局
*/
public abstract int getLayout();
/**
* 初始化控件
*/
public abstract void initView();
/**
* 初始化数据
*/
public abstract void initData();
/**
* 初始化监听
*/
public abstract void initListener();
}
04.05_日志工具类
public class LogUtils {
public static boolean DEBUG = true;
public static void i(String TAG, String msg) {
if (DEBUG) {
Log.i(TAG, msg);
}
}
public static void d(String TAG, String msg) {
if (DEBUG) {
Log.d(TAG, msg);
}
}
public static void w(String TAG, String msg) {
if (DEBUG) {
Log.w(TAG, msg);
}
}
public static void e(String TAG, String msg) {
if (DEBUG) {
Log.e(TAG, msg);
}
}
public static void v(String TAG, String msg) {
if (DEBUG) {
Log.v(TAG, msg);
}
}
}
04.06_闪屏页
布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/base_bg"
tools:context=".ui.activity.SplashActivity">
<ImageView
android:id="@+id/iv_app_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@drawable/icon" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/iv_app_icon"
android:layout_centerHorizontal="true"
android:layout_marginTop="5dp"
android:text="努比亚音乐"
android:textColor="@color/half_white"
android:textSize="18sp" />
</RelativeLayout>
代码:
package cn.nubia.nubiamusic.ui.activity;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import cn.nubia.nubiamusic.R;
/**
* 闪屏页
*/
public class SplashActivity extends BaseActivity {
@Override
public int getLayout() {
return R.layout.activity_splash;
}
@Override
public void initView() {
}
@Override
public void initData() {
//延迟3秒调到主页面
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Intent intent = new Intent();
intent.setClass(SplashActivity.this,MainActivity.class);
startActivity(intent);
finish();
}
};
handler.sendEmptyMessageDelayed(0, 3000);
}
@Override
public void initListener() {
}
}
04.07_主页面布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/base_bg"
android:orientation="vertical"
tools:context=".ui.activity.MainActivity">
<!--标题栏-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="@drawable/base_titlebar_bg"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/title_video"
android:textColor="@color/half_white"
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/title_audio"
android:textColor="@color/half_white"
android:textSize="18sp" />
</LinearLayout>
<!--指示器-->
<View
android:layout_width="200dp"
android:layout_height="2dp"
android:background="@color/green"></View>
<!--播放列表-->
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
</LinearLayout>
04.08_简单加载视频&音乐播放列表
在ViewPager中加载Fragment
视频:
package cn.nubia.nubiamusic.ui.fragment;
import cn.nubia.nubiamusic.R;
/**
* author : gordon
* e-mail : gordon_sun07@163.com
* date : 2019/4/9 23:59
* version: 1.0
* desc : 主页视频播放列表
*/
public class VideoFragment extends BaseFragment {
@Override
public int getLayout() {
return R.layout.fragment_video_list;
}
@Override
public void initView() {
}
@Override
public void initData() {
}
@Override
public void initListener() {
}
}
音乐:
package cn.nubia.nubiamusic.ui.fragment;
import cn.nubia.nubiamusic.R;
/**
* author : gordon
* e-mail : gordon_sun07@163.com
* date : 2019/4/9 23:59
* version: 1.0
* desc : 主页音乐播放列表
*/
public class AudioFragment extends BaseFragment {
@Override
public int getLayout() {
return R.layout.fragment_audio_list;
}
@Override
public void initView() {
}
@Override
public void initData() {
}
@Override
public void initListener() {
}
}
主页代码:
package cn.nubia.nubiamusic.ui.activity;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
import cn.nubia.nubiamusic.R;
import cn.nubia.nubiamusic.adapter.PlayerListAdapter;
import cn.nubia.nubiamusic.ui.fragment.AudioFragment;
import cn.nubia.nubiamusic.ui.fragment.BaseFragment;
import cn.nubia.nubiamusic.ui.fragment.VideoFragment;
public class MainActivity extends BaseActivity {
private ViewPager mViewPager;
@Override
public int getLayout() {
return R.layout.activity_main;
}
@Override
public void initView() {
mViewPager = findViewById(R.id.view_pager);
}
@Override
public void initData() {
}
@Override
public void initListener() {
FragmentManager fm = getSupportFragmentManager();
List<BaseFragment> list = new ArrayList<>();
list.add(new VideoFragment());
list.add(new AudioFragment());
PlayerListAdapter mPlayerListAdapter = new PlayerListAdapter(fm, list);
mViewPager.setAdapter(mPlayerListAdapter);
}
}
04.09_修改标题颜色
//设置ViewPager滑动监听
mViewPager.setOnPageChangeListener(new PageChangeListener());
private class PageChangeListener implements ViewPager.OnPageChangeListener {
//触发onTouchEvent时调用
@Override
public void onPageScrolled(int position, float positionOffset, int i1) {
}
//选中某个页面调用
@Override
public void onPageSelected(int position) {
updateTabs(position);
}
//页面滑动时调用
@Override
public void onPageScrollStateChanged(int position) {
}
}
/**
* 修改标题字体大小
*
* @param position
*/
private void updateTabs(int position) {
int tab = TABS_VIDEO;
if (position == tab) {
//视频播放列表
tvVideoTitle.setTextColor(getResources().getColor(R.color.green));
tvAudioTitle.setTextColor(getResources().getColor(R.color.half_white));
} else {
//音乐播放列表
tvVideoTitle.setTextColor(getResources().getColor(R.color.half_white));
tvAudioTitle.setTextColor(getResources().getColor(R.color.green));
}
}
04.10_修改标题大小
属性动画
增加jar包
/**
* 修改标题字体大小
* 属性动画设置放大字体,jar包
* @param position
*/
private void updateTabs(int position) {
int tab = TABS_VIDEO;
if (position == tab) {
//视频播放列表
tvVideoTitle.setTextColor(getResources().getColor(R.color.green));
tvAudioTitle.setTextColor(getResources().getColor(R.color.half_white));
ViewPropertyAnimator.animate(tvVideoTitle).scaleX(1.2f);
ViewPropertyAnimator.animate(tvVideoTitle).scaleY(1.2f);
ViewPropertyAnimator.animate(tvAudioTitle).scaleX(1.0f);
ViewPropertyAnimator.animate(tvAudioTitle).scaleY(1.0f);
} else {
//音乐播放列表
tvVideoTitle.setTextColor(getResources().getColor(R.color.half_white));
tvAudioTitle.setTextColor(getResources().getColor(R.color.green));
ViewPropertyAnimator.animate(tvAudioTitle).scaleX(1.2f);
ViewPropertyAnimator.animate(tvAudioTitle).scaleY(1.2f);
ViewPropertyAnimator.animate(tvVideoTitle).scaleX(1.0f);
ViewPropertyAnimator.animate(tvVideoTitle).scaleY(1.0f);
}
}
04.11_点击标题
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_audio_title:
switchTabs(TABS_AUDIO);
break;
case R.id.tv_video_title:
switchTabs(TABS_VIDEO);
break;
}
}
/**
* 点击播放列表
* @param tab
*/
private void switchTabs(int tab) {
updateTabs(tab);
//切换播放列表
mViewPager.setCurrentItem(tab);
}
04.12_设置指示器宽度
1
//指示器
mIndictor = findViewById(R.id.indictor);
2
//手机屏幕宽度
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
int screenWidth = wm.getDefaultDisplay().getWidth();
//初始化指示器宽度
mIndictor.getLayoutParams().width = screenWidth / 2;
04.13_滑动页面移动指示器
/**
* 触发onTouchEvent时调用
*
* @param position
* @param positionOffset 滑动百分比
* @param i1
*/
@Override
public void onPageScrolled(int position, float positionOffset, int i1) {
LogUtils.e("gordon", "positionOffset = " + positionOffset);
//3、起始位置 = 页面位置 * 指示器的宽度
int width = mIndictor.getWidth();
int startX = position * width;
//2、移动的位置 = 划过屏幕的百分比 * 指示器的宽度
float moveX = positionOffset * width;
//1、最终的位置 = 起始位置 + 移动位置
int finalX = (int) (startX + moveX);
//属性动画
ViewHelper.setTranslationX(mIndictor, finalX);
}
04.14_利用内容提供者获取多媒体数据里的视频
数据库在 data/data/com.android.providers.media/databases/external.db
//利用内容提供者查询多媒体数据库的数据
ContentResolver resolver = getActivity().getContentResolver();
Cursor cursor = resolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Video.Media.DATA, MediaStore.Video.Media.TITLE, MediaStore.Video.Media.DURATION, MediaStore.Video.Media.SIZE}, null, null, null);
while (cursor.moveToNext()) {
VideoContentBean contentBean = VideoContentBean.newInstanceFromCursor(cursor);
LogUtils.e("gordon", "contentBean = " + contentBean);
封装bean:
package cn.nubia.nubiamusic.bean;
import android.database.Cursor;
import android.provider.MediaStore;
/**
* author : gordon
* e-mail : gordon_sun07@163.com
* date : 2019/4/11 0:17
* version: 1.0
* desc : 视频数据
*/
public class VideoContentBean {
private String title;
private String data;
private int time;
private long size;
public static VideoContentBean newInstanceFromCursor(Cursor cursor) {
VideoContentBean bean = new VideoContentBean();
if (cursor == null || cursor.getCount() == 0) {
return bean;
}
bean.title = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.TITLE));
bean.data = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA));
bean.time = cursor.getInt(cursor.getColumnIndex(MediaStore.Video.Media.DURATION));
bean.size = cursor.getLong(cursor.getColumnIndex(MediaStore.Video.Media.SIZE));
return bean;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDate() {
return data;
}
public void setDate(String date) {
this.data = date;
}
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
@Override
public String toString() {
return "VideoContentBean{" +
"title='" + title + '\'' +
", data='" + data + '\'' +
", time=" + time +
", size=" + size +
'}';
}
}
权限报错:
04-11 00:31:46.971 19416 19416 E AndroidRuntime: FATAL EXCEPTION: main
04-11 00:31:46.971 19416 19416 E AndroidRuntime: Process: cn.nubia.nubiamusic, PID: 19416
04-11 00:31:46.971 19416 19416 E AndroidRuntime: java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/video/media from pid=19416, uid=10143 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.os.Parcel.createException(Parcel.java:1950)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.os.Parcel.readException(Parcel.java:1918)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.content.ContentProviderProxy.query(ContentProviderNative.java:418)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.content.ContentResolver.query(ContentResolver.java:819)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.content.ContentResolver.query(ContentResolver.java:765)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.content.ContentResolver.query(ContentResolver.java:723)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at cn.nubia.nubiamusic.ui.fragment.VideoFragment.initData(VideoFragment.java:34)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at cn.nubia.nubiamusic.ui.fragment.BaseFragment.onCreateView(BaseFragment.java:28)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.app.Fragment.performCreateView(Fragment.java:2439)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:802)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:2243)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:654)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:146)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.view.ViewPager.populate(ViewPager.java:1244)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.view.ViewPager.populate(ViewPager.java:1092)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1622)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.View.measure(View.java:23319)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6768)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.widget.LinearLayout.measureVertical(LinearLayout.java:825)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.widget.LinearLayout.onMeasure(LinearLayout.java:704)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.View.measure(View.java:23319)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6768)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.View.measure(View.java:23319)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6768)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.widget.LinearLayout.measureVertical(LinearLayout.java:825)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.widget.LinearLayout.onMeasure(LinearLayout.java:704)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.View.measure(View.java:23319)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6768)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at com.android.internal.policy.DecorView.onMeasure(DecorView.java:803)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.View.measure(View.java:23319)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3001)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1815)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2106)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1699)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7730)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1012)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.Choreographer.doCallbacks(Choreographer.java:823)
04-11 00:31:46.971 19416 19416 E AndroidRuntime: at android.view.Choreographer.doFrame(Choreographer.java:758)
04-11 00:31:46.972 19416 19416 E AndroidRuntime: at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:998)
04-11 00:31:46.972 19416 19416 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:873)
04-11 00:31:46.972 19416 19416 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:99)
04-11 00:31:46.972 19416 19416 E AndroidRuntime: at android.os.Looper.loop(Looper.java:215)
04-11 00:31:46.972 19416 19416 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7445)
04-11 00:31:46.972 19416 19416 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
04-11 00:31:46.972 19416 19416 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:500)
04-11 00:31:46.972 19416 19416 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:865)
解决:
清单文件增加
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@android:color/transparent"
android:divider="@color/half_white"
android:dividerHeight="1dp"></ListView>
报错:
04-12 22:55:58.995 27808-27808/cn.nubia.nubiamusic E/AndroidRuntime: FATAL EXCEPTION: main
Process: cn.nubia.nubiamusic, PID: 27808
java.lang.IllegalArgumentException: column '_id' does not exist. Available columns: [_data, title, duration, _size]
at android.database.AbstractCursor.getColumnIndexOrThrow(AbstractCursor.java:340)
at android.database.CursorWrapper.getColumnIndexOrThrow(CursorWrapper.java:87)
at android.widget.CursorAdapter.init(CursorAdapter.java:180)
at android.widget.CursorAdapter.<init>(CursorAdapter.java:128)
at cn.nubia.nubiamusic.adapter.VideoListAdapter.<init>(VideoListAdapter.java:28)
at cn.nubia.nubiamusic.ui.fragment.VideoFragment.initData(VideoFragment.java:59)
at cn.nubia.nubiamusic.ui.fragment.BaseFragment.onCreateView(BaseFragment.java:28)
at android.support.v4.app.Fragment.performCreateView(Fragment.java:2439)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:802)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)
at android.support.v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:2243)
at android.support.v4.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:654)
at android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:146)
at android.support.v4.view.ViewPager.populate(ViewPager.java:1244)
at android.support.v4.view.ViewPager.populate(ViewPager.java:1092)
at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1622)
at android.view.View.measure(View.java:23319)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6768)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:825)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:704)
at android.view.View.measure(View.java:23319)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6768)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at android.view.View.measure(View.java:23319)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6768)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:825)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:704)
at android.view.View.measure(View.java:23319)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6768)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at com.android.internal.policy.DecorView.onMeasure(DecorView.java:803)
at android.view.View.measure(View.java:23319)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3001)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1815)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2106)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1699)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7730)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1012)
at android.view.Choreographer.doCallbacks(Choreographer.java:823)
at android.view.Choreographer.doFrame(Choreographer.java:758)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:998)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:215)
at android.app.ActivityThread.main(ActivityThread.java:7445)
at java.lang.reflect.Method.invoke(Native Method)
2019-04-12 22:55:58.995 27808-27808/cn.nubia.nubiamusic E/AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:500)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:865)
修改:必须写_id这列
Cursor cursor = resolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Video.Media._ID, MediaStore.Video.Media.DATA, MediaStore.Video.Media.TITLE, MediaStore.Video.Media.DURATION, MediaStore.Video.Media.SIZE}, null, null, null);
04.17_显示视频播放列表
@Override
public void initData() {
LogUtils.e("gordon", "VideoFragment-initData():current_sdk_version = " + android.os.Build.VERSION.SDK_INT);
//利用内容提供者查询多媒体数据库的数据
ContentResolver resolver = getActivity().getContentResolver();
Cursor cursor = resolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Video.Media._ID, MediaStore.Video.Media.DATA, MediaStore.Video.Media.TITLE, MediaStore.Video.Media.DURATION, MediaStore.Video.Media.SIZE}, null, null, null);
mVideoListAdapter = new VideoListAdapter(mActivty, cursor);
lvListView.setAdapter(mVideoListAdapter);
}
适配器:
package cn.nubia.nubiamusic.adapter;
import android.content.Context;
import android.database.Cursor;
import android.text.format.Formatter;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.nineoldandroids.view.ViewHelper;
import cn.nubia.nubiamusic.R;
import cn.nubia.nubiamusic.bean.VideoContentBean;
/**
* author : gordon
* e-mail : gordon_sun07@163.com
* date : 2019/4/12 22:16
* version: 1.0
* desc : 视频列表的adapter
*/
public class VideoListAdapter extends CursorAdapter {
private View mView;
public VideoListAdapter(Context context, Cursor cursor) {
super(context, cursor);
}
/**
* 加载布局
* @param context
* @param cursor
* @param parent
* @return
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
mView = View.inflate(context, R.layout.adapter_list_item, null);
mView.setTag(new ViewHolder());
return mView;
}
/**
* 设置数据
* @param view
* @param context
* @param cursor
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
VideoContentBean contentBean = VideoContentBean.newInstanceFromCursor(cursor);
ViewHolder holder = (ViewHolder) mView.getTag();
holder.tvTitle.setText(contentBean.getTitle());
holder.tvTime.setText(String.valueOf(contentBean.getTime()));
String size = Formatter.formatFileSize(context, contentBean.getSize());
holder.tvSize.setText(size);
}
class ViewHolder {
private ImageView ivIcon;
private TextView tvTitle;
private TextView tvTime;
private TextView tvSize;
public ViewHolder() {
ivIcon = mView.findViewById(R.id.iv_video_icon);
tvTitle = mView.findViewById(R.id.tv_video_title);
tvTime = mView.findViewById(R.id.tv_video_time);
tvSize = mView.findViewById(R.id.tv_video_size);
}
}
}
04.19_格式化时间
package cn.nubia.nubiamusic.utils;
/**
* author : gordon
* e-mail : gordon_sun07@163.com
* date : 2019/4/14 12:03
* version: 1.0
* desc :
*/
public class StringUtils {
public static final int HOUR = 60 * 60 * 1000;
public static final int MIN = 60 * 1000;
public static final int SECOND = 1000;
public static String formatTime(int time) {
int h = time / HOUR;
int m = time % HOUR / MIN;
int s = time % MIN / SECOND;
if (h >= 1) {//00:00:00
return String.format("%02d:%02d:%02d", h, m, s);
} else {//00:00
return String.format("%02d:%02d", m, s);
}
}
}
04.20_传递视频的bean
1、将bean序列化
public class VideoContentBean implements Serializable
2、
intent.putExtra("videoContentBean", videoContentBean);
3、
@Override
public void initData() {
Intent intent = getIntent();
VideoContentBean videoContentBean = (VideoContentBean) intent.getSerializableExtra("videoContentBean");
LogUtils.e("gordon", "videobean = " + videoContentBean);
mVideoView.setVideoURI(Uri.parse(videoContentBean.getDate()));
mVideoView.start();
}
横屏:
<activity
android:name=".ui.activity.VideoPlayerActivity"
android:screenOrientation="landscape"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"></activity>
04.23_自定义SeekBar
1、引用
<SeekBar
android:id="@+id/seekBar"
style="@android:style/Widget.SeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="6dp"
android:minHeight="6dp"
android:progress="50"
android:progressDrawable="@drawable/video_seek_bar"
android:secondaryProgress="80"
android:thumb="@drawable/video_progress_thumb"
android:thumbOffset="0dp" />
2、自定义布局
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@android:id/background"
android:drawable="@drawable/video_seekbar_bg"></item>
<!--网络缓存第二进度-->
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="5dip" />
<gradient
android:angle="270"
android:centerColor="#66ffffff"
android:centerY="0.75"
android:endColor="#66ffffff"
android:startColor="#66ffffff" />
</shape>
</clip>
</item>
<!--已播放进度-->
<item
android:id="@android:id/progress"
android:drawable="@drawable/video_seekbar_progress"></item>
</layer-list>
04.24_完成视频播放页面布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context=".ui.activity.VideoPlayerActivity">
<VideoView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!--顶部栏-->
<include
layout="@layout/video_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true" />
<!--底部栏-->
<include
layout="@layout/video_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" />
</RelativeLayout>
顶部栏布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--系统控制栏-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="@drawable/bg_video_system_status"
android:gravity="center"
android:orientation="horizontal"
android:padding="5dp">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="视频标题"
android:textColor="@color/half_white" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_battery_100" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="系统时间"
android:textColor="@color/half_white" />
</LinearLayout>
<!--音量-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/bg_video_volume_control"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/video_mute_selector" />
<SeekBar
android:id="@+id/seekBar"
style="@android:style/Widget.SeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:maxHeight="6dp"
android:minHeight="6dp"
android:progress="50"
android:progressDrawable="@drawable/video_seek_bar"
android:secondaryProgress="80"
android:thumb="@drawable/video_progress_thumb"
android:thumbOffset="0dp" />
</LinearLayout>
</LinearLayout>
底部栏布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/bg_video_duration_control"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="3dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="已播放时间"
android:textColor="@color/half_white"
android:textSize="16sp" />
<SeekBar
android:id="@+id/seekBar"
style="@android:style/Widget.SeekBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:maxHeight="6dp"
android:minHeight="6dp"
android:progress="50"
android:progressDrawable="@drawable/video_seek_bar"
android:secondaryProgress="80"
android:thumb="@drawable/video_progress_thumb"
android:thumbOffset="0dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="视频总时间"
android:textColor="@color/half_white"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:background="@drawable/bg_video_bottom_control"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/bt_exit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_exit" />
<Button
android:id="@+id/bt_pre"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_pre_selector" />
<Button
android:id="@+id/bt_pause"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_pause_selector" />
<Button
android:id="@+id/bt_next"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_next_selector" />
<Button
android:id="@+id/bt_full_screen"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_full_screen_normal" />
<Button
android:id="@+id/bt_default_screen"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_default_screen_normal" />
</LinearLayout>
</LinearLayout>
04.25_播放&暂停功能
package cn.nubia.nubiamusic.ui.activity;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.VideoView;
import java.io.Serializable;
import cn.nubia.nubiamusic.R;
import cn.nubia.nubiamusic.bean.VideoContentBean;
import cn.nubia.nubiamusic.utils.LogUtils;
/**
* 视频播放列表
*/
public class VideoPlayerActivity extends BaseActivity implements View.OnClickListener {
private VideoView mVideoView;
private Button btPause;
@Override
public int getLayout() {
return R.layout.activity_video_player;
}
@Override
public void initView() {
mVideoView = findViewById(R.id.video_view);
btPause = findViewById(R.id.bt_pause);
}
@Override
public void initData() {
Intent intent = getIntent();
VideoContentBean videoContentBean = (VideoContentBean) intent.getSerializableExtra("videoContentBean");
LogUtils.e("gordon", "videobean = " + videoContentBean);
mVideoView.setVideoURI(Uri.parse(videoContentBean.getDate()));
}
@Override
public void initListener() {
btPause.setOnClickListener(this);
mVideoView.setOnCompletionListener(new VideoCompletionListener());
mVideoView.setOnPreparedListener(new VideoPreparedListener());
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_pause://播放与暂停
switchPlayOrPause();
break;
}
}
private void switchPlayOrPause() {
if (mVideoView.isPlaying()) {
mVideoView.pause();
} else {
mVideoView.start();
}
updatePlayPause();
}
//更新播放暂停背景
private void updatePlayPause() {
if (mVideoView.isPlaying()) {
btPause.setBackgroundResource(R.drawable.btn_pause_pressed);
} else {
btPause.setBackgroundResource(R.drawable.btn_play_normal);
}
}
/**
* 准备播放的监听
*/
private class VideoPreparedListener implements MediaPlayer.OnPreparedListener {
@Override
public void onPrepared(MediaPlayer mp) {
mVideoView.start();
updatePlayPause();
}
}
/**
* 视频播放完成的监听
*/
private class VideoCompletionListener implements MediaPlayer.OnCompletionListener {
@Override
public void onCompletion(MediaPlayer mp) {
}
}
}
04.26_初始化播放暂停的背景
/**
* 准备播放的监听
*/
private class VideoPreparedListener implements MediaPlayer.OnPreparedListener {
@Override
public void onPrepared(MediaPlayer mp) {
mVideoView.start();
updatePlayPause();
}
}
04.27_MediaPlayer生命周期
9种状态
05.02_设置视频标题
05.03_更新系统时间
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_UPDATE_SYSTEM_TIME:
startSystemTime();
break;
}
}
};
private void startSystemTime() {
long currentTimeMillis = System.currentTimeMillis();
tvSystemTime.setText(StringUtils.formatSystemTime(currentTimeMillis));
//每隔一秒发送一次消息
mHandler.sendEmptyMessageDelayed(MSG_UPDATE_SYSTEM_TIME, 1000);
}
内存泄漏:
@Override
protected void onDestroy() {
super.onDestroy();
//销毁前移除所有消息
mHandler.removeCallbacksAndMessages(null);
}
05.04_系统电池电量变化
//注册系统电池电量变化的广播
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
batteryBroadcastReceiver = new BatteryBroadcastReceiver();
registerReceiver(batteryBroadcastReceiver, intentFilter);
/**
* 电池电量变化
*/
private class BatteryBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int level = intent.getIntExtra("level", -1);
updateSystemBatteryStatus(level);
}
}
05.05_修改系统的音量
//获取系统音量
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int streamMaxVolume = getStreamMaxVolume();
LogUtils.e("gordon", "最大音量 = " + streamMaxVolume);
mSeekBarVolume.setMax(streamMaxVolume);
//系统最大音量
private int getStreamMaxVolume() {
return mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
}
/**
* seekbar拖动的监听
*/
private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//记录当前是否为用户操作
if (!fromUser) return;
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
05.06_静音按钮处理
/**
* 静音按钮的处理
* 如果是静音,回复上一次的音量
* 如果不是静音,设置为静音
*/
private void switchMute() {
int currentStreamVolume = getCurrentStreamVolume();
if (currentStreamVolume == 0) {
//回复上一次音量
updateSystemVolume(mCurrentVolume);
} else {
//静音之前保存之前的音量值
mCurrentVolume = getCurrentStreamVolume();
updateSystemVolume(0);
}
}
05.07_手势修改系统音量
/**
* 屏幕上下滑动改变音量大小
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = event.getY();//按下时的y坐标
downVolume = getCurrentStreamVolume();
break;
case MotionEvent.ACTION_MOVE:
//4. 划过屏幕的距离=移动的坐标-按下时坐标
float offsetY = event.getY() - startY;
//3. 划过屏幕的百分比=划过屏幕的距离/手机屏幕的高度
float percent = offsetY / mScreenHeight;
//2. 变化音量=划过屏幕的百分比*最大音量
float changeVolume = percent * getStreamMaxVolume();
//1. 最终音量=按下音量+变化音量
int finalVolume = (int) (downVolume + changeVolume);
updateSystemVolume(finalVolume);
break;
}
return super.onTouchEvent(event);
}
05.08_视频总时间
/**
* 准备播放的监听
*/
private class VideoPreparedListener implements MediaPlayer.OnPreparedListener {
@Override
public void onPrepared(MediaPlayer mp) {
mVideoView.start();
//开始播放后,设置视频时间
tvTotalTime.setText(StringUtils.formatTime(mp.getDuration()));
updatePlayPause();
}
}
05.09_更新已播放时间
/**
* 更新已播放时间
*/
private void updatePlayPosition() {
int currentPosition = mVideoView.getCurrentPosition();
tvPlayTime.setText(StringUtils.formatTime(currentPosition));
mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAY_TIME, 1000);
}
05.10_自动更新播放时间进度和位置
private class VideoPreparedListener implements MediaPlayer.OnPreparedListener {
@Override
public void onPrepared(MediaPlayer mp) {
mVideoView.start();
//开始播放后,设置视频时间
tvTotalTime.setText(StringUtils.formatTime(mp.getDuration()));
//更新播放、暂停按钮
updatePlayPause();
//设置最大值
sbPlayPs.setMax(mp.getDuration());
//更新已播放时间
updatePlayPosition();
}
}
/**
* 更新已播放时间
*/
private void updatePlayPosition() {
int currentPosition = mVideoView.getCurrentPosition();
tvPlayTime.setText(StringUtils.formatTime(currentPosition));
sbPlayPs.setProgress(currentPosition);
mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAY_TIME, 1000);
}
05.11_随着时间进度条拖动更新播放进度
/**
* seekbar拖动的监听
*/
private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//记录当前是否为用户操作
if (!fromUser) return;
if (seekBar.getId() == R.id.seekBar) {
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0);
} else {
updatePlayTimeAndProgress(progress);
mVideoView.seekTo(progress);//进度
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
05.12_修复播放完bug’-进度回退
/**
* 视频播放完成的监听
*/
private class VideoCompletionListener implements MediaPlayer.OnCompletionListener {
@Override
public void onCompletion(MediaPlayer mp) {
int max = mp.getDuration();
updatePlayTimeAndProgress(max);
}
}
05.14_返回键
case R.id.bt_exit://返回
finish();
break;
05.15_传递集合到视频播放界面
/**
* 传递集合
* @param cursor
* @return
*/
public static ArrayList<VideoContentBean> getListFronCursor(Cursor cursor) {
ArrayList<VideoContentBean> list = new ArrayList<>();
if (cursor == null || cursor.getCount() == 0) {
return list;
}
//cursoradapter源码默认移动角标
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
VideoContentBean bean = newInstanceFromCursor(cursor);
list.add(bean);
}
return list;
}
传递对应集合
Intent intent = new Intent();
//VideoContentBean videoContentBean = VideoContentBean.newInstanceFromCursor(cursor);
//intent.putExtra("videoContentBean", videoContentBean);
ArrayList<VideoContentBean> listFronCursor = VideoContentBean.getListFronCursor(cursor);
intent.putExtra("VideoList", listFronCursor);
intent.putExtra("position", position);
intent.setClass(mActivty, VideoPlayerActivity.class);
startActivity(intent);
播放视频
ArrayList<VideoContentBean> videoList = (ArrayList<VideoContentBean>) intent.getSerializableExtra("VideoList");
int position = intent.getIntExtra("position", -1);
VideoContentBean videoContentBean = videoList.get(position);
mVideoView.setVideoURI(Uri.parse(videoContentBean.getDate()));
05.19_上一曲&下一曲
/**
* 下一曲
*/
private void playNext() {
if (mPosition != mVideoList.size() - 1) {
mPosition++;
playItemPreOrNext();
}
}
/**
* 上一曲
*/
private void playPre() {
if (mPosition != 0) {
mPosition--;
playItemPreOrNext();
}
}
private void playItemPreOrNext() {
//防止数组越界
btPre.setEnabled(mPosition != 0);
btNext.setEnabled(mPosition != mVideoList.size() - 1);
VideoContentBean videoContentBean = mVideoList.get(mPosition);
mVideoView.setVideoURI(Uri.parse(videoContentBean.getDate()));
//设置标题
tvTitle.setText(videoContentBean.getTitle());
}
05.20_自定义VideoView-全屏
未实现
05.22_单击&双击&长按
GestureDetector mGestureDetector = new GestureDetector(new SimpleOnGestureListener());
private class SimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener {
//单击
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return super.onSingleTapConfirmed(e);
}
//双击
@Override
public boolean onDoubleTap(MotionEvent e) {
return super.onDoubleTap(e);
}
//长按
@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
}
}
05.24_单击隐藏&显示控制面板
mGestureDetector = new GestureDetector(new SimpleOnGestureListener());
@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
}
private boolean isShowCtrl = true;
private void switchCtrl() {
if (isShowCtrl) {
ViewPropertyAnimator.animate(llVideoTop).translationY(-llVideoTop.getHeight());
ViewPropertyAnimator.animate(llVideoBottom).translationY(llVideoBottom.getHeight());
isShowCtrl = false;
} else {
ViewPropertyAnimator.animate(llVideoTop).translationY(0);
ViewPropertyAnimator.animate(llVideoBottom).translationY(0);
isShowCtrl = true;
}
}
05.25_自动隐藏控制面板
视频准备完成后发5秒延迟消息,隐藏控制面板
private void notifyAutoHideCtrl() {
//延迟5秒发送隐藏控制面板的消息
mHandler.sendEmptyMessageDelayed(MSG_AUTO_HIDE_CTRL, 5000);
}
private void showCtrl() {
ViewPropertyAnimator.animate(llVideoTop).translationY(0);
ViewPropertyAnimator.animate(llVideoBottom).translationY(0);
isShowCtrl = true;
}
private void hideCtrl() {
ViewPropertyAnimator.animate(llVideoTop).translationY(-llVideoTop.getHeight());
ViewPropertyAnimator.animate(llVideoBottom).translationY(llVideoBottom.getHeight());
isShowCtrl = false;
}
05.26_响应应用程序外调用
<activity
android:name=".ui.activity.VideoPlayerActivity"
android:screenOrientation="landscape"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="rtsp"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="video/*"/>
<data android:mimeType="application/sdp"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:mimeType="video/mp4"/>
<data android:mimeType="video/3gp"/>
<data android:mimeType="video/3gpp"/>
<data android:mimeType="video/3gpp2"/>
</intent-filter>
</activity>
Uri data = intent.getData();
if (data==null) {
mVideoList = (ArrayList<VideoContentBean>) intent.getSerializableExtra("VideoList");
mPosition = intent.getIntExtra("position", -1);
//播放视频
playItemPreOrNext();
} else {
mVideoView.setVideoURI(data);
//设置标题
tvTitle.setText(data.toString());
btNext.setEnabled(false);
btPre.setEnabled(false);
}
05.27_播放网络视频
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("http://192.168.13.69:8080/player/gril.mp4"), "video/mp4");
startActivity(intent);
05.29_网络缓冲
/**
* 设置网络缓存的监听
*/
private class VideoOnInfoListener implements MediaPlayer.OnInfoListener {
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
switch (what) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START:
pbProgressBar.setVisibility(View.VISIBLE);
break;
case MediaPlayer.MEDIA_INFO_BUFFERING_END:
pbProgressBar.setVisibility(View.GONE);
break;
}
return false;
}
}
05.30_网络缓冲的第二进度
未实现
06.02_利用内容提供者获取音乐的数据
public static AudioContentBean newInstanceFromCursor(Cursor cursor) {
AudioContentBean bean = new AudioContentBean();
if (cursor == null || cursor.getCount() == 0) {
return bean;
}
bean.time = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION));
String preTitle = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME));
bean.title = StringUtils.getTitle(preTitle);
bean.data = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
bean.size = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE));
return bean;
}
mActivity = (MainActivity) getActivity();
ContentResolver resolver = mActivity.getContentResolver();
Cursor cursor = resolver.query(Audio.Media.EXTERNAL_CONTENT_URI, new String[]{Audio.Media._ID, Audio.Media.SIZE, Audio.Media.DURATION, Audio.Media.DISPLAY_NAME, Audio.Media.DATA}, null, null, null);
while (cursor.moveToNext()) {
AudioContentBean audioContentBean = AudioContentBean.newInstanceFromCursor(cursor);
LogUtils.e("gordon", "audio_bean:" + audioContentBean);
}
06.05_音乐界面布局之中间歌词-帧动画
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item
android:drawable="@drawable/audio_anim_01"
android:duration="200" />
<item
android:drawable="@drawable/audio_anim_02"
android:duration="200" />
<item
android:drawable="@drawable/audio_anim_03"
android:duration="200" />
<item
android:drawable="@drawable/audio_anim_04"
android:duration="200" />
<item
android:drawable="@drawable/audio_anim_05"
android:duration="200" />
<item
android:drawable="@drawable/audio_anim_06"
android:duration="200" />
<item
android:drawable="@drawable/audio_anim_07"
android:duration="200" />
<item
android:drawable="@drawable/audio_anim_08"
android:duration="200" />
<item
android:drawable="@drawable/audio_anim_09"
android:duration="200" />
</animation-list>
06.06_音乐界面布局之底部兰
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="3dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:text="已播放时间/总时间"
android:textColor="@color/half_white"
android:textSize="16sp" />
<SeekBar
android:id="@+id/audio_seekBar"
style="@android:style/Widget.SeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginTop="3dp"
android:layout_marginBottom="3dp"
android:progress="50"
android:progressDrawable="@drawable/audio_seek_bar"
android:thumb="@drawable/audio_seek_thumb"
android:thumbOffset="0dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="75dp"
android:orientation="horizontal"
android:padding="3dp">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_playmode_singlerepeat_normal" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_audio_pre" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_audio_play" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_audio_next" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/btn_playlist" />
</LinearLayout>
</LinearLayout>
06.07_传递播放列表到播放页面
public static ArrayList<AudioContentBean> getBeanFormCursor(Cursor cursor) {
ArrayList<AudioContentBean> list = new ArrayList<>();
if (cursor == null || cursor.getCount() == 0) {
return list;
}
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
AudioContentBean bean = newInstanceFromCursor(cursor);
list.add(bean);
}
return list;
}
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ArrayList<AudioContentBean> beanFormCursor = AudioContentBean.getBeanFormCursor(cursor);
Intent intent = new Intent();
intent.setClass(mActivity, AudioPlayerActivity.class);
intent.putExtra("list", beanFormCursor);
intent.putExtra("pos", position);
startActivity(intent);
}
});
Intent intent = getIntent();
ArrayList<AudioContentBean> mAudioList = (ArrayList<AudioContentBean>) intent.getSerializableExtra("list");
int mPosition = intent.getIntExtra("pos", -1);
try {
if (mediaPlayer != null) {
mediaPlayer.reset();
}
AudioContentBean audioContentBean = mAudioList.get(mPosition);
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(audioContentBean.getData());
mediaPlayer.prepare();
mediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
06.08_音乐MVC功能设计
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
ArrayList<AudioContentBean> mAudioList = (ArrayList<AudioContentBean>) intent.getSerializableExtra("list");
int mPosition = intent.getIntExtra("pos", -1);
try {
if (mediaPlayer != null) {
mediaPlayer.stop();
//mediaPlayer.reset();
mediaPlayer.release();
mediaPlayer=null;
}
AudioContentBean audioContentBean = mAudioList.get(mPosition);
mediaPlayer.setDataSource(audioContentBean.getData());
mediaPlayer.prepare();
mediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void initData() {
Intent intent = new Intent(getIntent());
intent.setClass(this, AudioService.class);
startService(intent);
}
06.10_混合方式开启服务&播放暂停音乐
V:
package cn.nubia.nubiamusic.ui.activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.view.View;
import android.widget.Button;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import cn.nubia.nubiamusic.R;
import cn.nubia.nubiamusic.bean.AudioContentBean;
import cn.nubia.nubiamusic.service.AudioService;
import cn.nubia.nubiamusic.service.MusicInterface;
/**
* MVC
* M:AudioContentBean
* V:ActivityPlayerActivity
* C:AudioService
*/
public class AudioPlayerActivity extends BaseActivity implements View.OnClickListener {
private AudioService musicService;
private Button btnPlayPause;
@Override
public int getLayout() {
return R.layout.activity_audio_player;
}
@Override
public void initView() {
btnPlayPause = findViewById(R.id.btn_play_pause);
}
@Override
public void initData() {
//start开启服务,让其长期运行在后台
Intent intent = new Intent(getIntent());
intent.setClass(this, AudioService.class);
startService(intent);
//bind服务
AudioServiceConnection conn = new AudioServiceConnection();
bindService(intent, conn, BIND_AUTO_CREATE);
}
@Override
public void initListener() {
btnPlayPause.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_play_pause:
switchPlayPause();
break;
}
}
/**
* 播放与暂停
*/
private void switchPlayPause() {
musicService.switchPlayPause();
}
private class AudioServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MusicInterface music = (MusicInterface) service;
musicService = music.getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
M
package cn.nubia.nubiamusic.bean;
import android.database.Cursor;
import android.provider.MediaStore;
import java.io.Serializable;
import java.util.ArrayList;
import cn.nubia.nubiamusic.utils.StringUtils;
/**
* author : gordon
* e-mail : gordon_sun07@163.com
* date : 2019/4/21 17:13
* version: 1.0
* desc :
*/
public class AudioContentBean implements Serializable {
private String data;
private String title;
private int time;
private long size;
public static ArrayList<AudioContentBean> getBeanFormCursor(Cursor cursor) {
ArrayList<AudioContentBean> list = new ArrayList<>();
if (cursor == null || cursor.getCount() == 0) {
return list;
}
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
AudioContentBean bean = newInstanceFromCursor(cursor);
list.add(bean);
}
return list;
}
public static AudioContentBean newInstanceFromCursor(Cursor cursor) {
AudioContentBean bean = new AudioContentBean();
if (cursor == null || cursor.getCount() == 0) {
return bean;
}
bean.time = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION));
String preTitle = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME));
bean.title = StringUtils.getTitle(preTitle);
bean.data = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
bean.size = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE));
return bean;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
@Override
public String toString() {
return "AudioContentBean{" +
"data='" + data + '\'' +
", title='" + title + '\'' +
", time=" + time +
", size=" + size +
'}';
}
}
C
package cn.nubia.nubiamusic.service;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.IBinder;
import java.io.IOException;
import java.util.ArrayList;
import cn.nubia.nubiamusic.bean.AudioContentBean;
/**
* 播放音乐的服务
* 播放模式:上一曲、下一曲、播放、暂停、展示
*/
public class AudioService extends Service {
private MediaPlayer mediaPlayer;
public AudioService() {
}
@Override
public IBinder onBind(Intent intent) {
return new Music();
}
@Override
public void onCreate() {
super.onCreate();
mediaPlayer = new MediaPlayer();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
ArrayList<AudioContentBean> mAudioList = (ArrayList<AudioContentBean>) intent.getSerializableExtra("list");
int mPosition = intent.getIntExtra("pos", -1);
try {
if (mediaPlayer != null) {
mediaPlayer.stop();
}
AudioContentBean audioContentBean = mAudioList.get(mPosition);
mediaPlayer.setDataSource(audioContentBean.getData());
mediaPlayer.prepare();
mediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
/**
* 播放与暂停
*/
public void switchPlayPause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
} else {
mediaPlayer.start();
}
}
private class Music extends Binder implements MusicInterface {
@Override
public AudioService getService() {
return AudioService.this;
}
}
}
接口
package cn.nubia.nubiamusic.service;
/**
* author : gordon
* e-mail : gordon_sun07@163.com
* date : 2019/4/21 23:11
* version: 1.0
* desc : 音乐的接口
*/
public interface MusicInterface {
public AudioService getService();
}
06.11_更新播放暂停按钮背景
/**
* 更新播放、暂停按钮的状态
*/
private void updatePlayPauseStatus() {
if (musicService.isPlaying()) {
btnPlayPause.setBackgroundResource(R.drawable.btn_audio_pause);
} else {
btnPlayPause.setBackgroundResource(R.drawable.btn_audio_play);
}
}
06.13_设置音乐标题
/**
* 音乐准备完成-发送广播通知刷新界面
*/
private class AudioOnPreparedListener implements MediaPlayer.OnPreparedListener {
@Override
public void onPrepared(MediaPlayer mp) {
Intent intent = new Intent();
intent.setAction(ACTION_PREPARED);
intent.putExtra("audioBean",mAudioList.get(mPosition));
sendBroadcast(intent);
}
}
private class AudioBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
updatePlayPauseStatus();
//设置音乐标题
AudioContentBean audioBean = (AudioContentBean) intent.getSerializableExtra("audioBean");
tvTitle.setText(audioBean.getTitle());
}
}
06.14_音乐示波器
//开启示波器的帧动画
AnimationDrawable ad = (AnimationDrawable) ivWave.getBackground();
ad.start();
06.15_更新已播放时间
/**
* 设置音乐已播放、总时间
*/
private void startPlayTotalTime() {
int currentTime = musicService.getCurrentTime();
updatePlayedTime(currentTime);
mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAY_TIME, 500);
}
/**
* 更新音乐的已播放时间,修改进度
*/
private void updatePlayedTime(int currentTime) {
int totalTime = musicService.getTotalTime();
tvPlayTotalTime.setText(StringUtils.formatTime(currentTime) + "/" + StringUtils.formatTime(totalTime));
//把进度条的最大值和音乐总时间关联
audioSeekBar.setProgress(currentTime);
}
06.16_拖动进度条更新音乐播放进度
/**
* 音乐播放进度改变的监听
*/
private class AudioSeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//bug:很卡
if (!fromUser) return;
musicService.seekTo(progress);
updatePlayedTime(progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
06.17_上一曲&下一曲
/**
*上一曲
*/
public void playPre() {
if (mPosition != 0) {
mPosition--;
playItem();
}
}
/**
*下一曲
*/
public void playNext() {
if (mPosition != mAudioList.size() - 1) {
mPosition++;
playItem();
}
}
06.18_修改播放模式背景图片
public static final int PLAY_MODE_REPATE_ALL = 0;//顺序播放
public static final int PLAY_MODE_REPATE_SINGLE = 1; //单曲循环
public static final int PLAY_MODE_RANDOM = 2;//随机播放
private int playMode = PLAY_MODE_REPATE_ALL;
/**
* 切换播放模式
*/
public void switchPlayMode() {
switch (playMode) {
case PLAY_MODE_REPATE_ALL:
playMode = PLAY_MODE_REPATE_SINGLE;
break;
case PLAY_MODE_REPATE_SINGLE:
playMode = PLAY_MODE_RANDOM;
break;
case PLAY_MODE_RANDOM:
playMode = PLAY_MODE_REPATE_ALL;
break;
}
}
case R.id.bt_play_mode:
musicService.switchPlayMode();
updatePlayModeStatus();//修改播放模式的背景图片
break;
/**
* 修改播放模式的背景图片
*/
public void updatePlayModeStatus() {
switch (musicService.getPlayMode()) {
case AudioService.PLAY_MODE_REPATE_ALL:
btPlayMode.setBackgroundResource(R.drawable.btn_playmode_all_repeat_normal);
break;
case AudioService.PLAY_MODE_REPATE_SINGLE:
btPlayMode.setBackgroundResource(R.drawable.btn_playmode_singlerepeat_normal);
break;
case AudioService.PLAY_MODE_RANDOM:
btPlayMode.setBackgroundResource(R.drawable.btn_playmode_random_normal);
break;
}
}
06.19_自动播放下一首歌
private class AudioCompletionListener implements MediaPlayer.OnCompletionListener {
@Override
public void onCompletion(MediaPlayer mp) {
autoPlayNextSong();
}
}
/**
* 自动播放下一首歌
*/
private void autoPlayNextSong() {
switch (playMode) {
case PLAY_MODE_REPATE_ALL:
if (mPosition == mAudioList.size() - 1) {
mPosition = 0;
} else {
mPosition++;
}
break;
case PLAY_MODE_REPATE_SINGLE:
break;
case PLAY_MODE_RANDOM:
Random random = new Random();
int num = random.nextInt(mAudioList.size() - 1);
mPosition = num;
break;
}
playItem();
}
06.20_存储播放模式
@Override
public void onCreate() {
super.onCreate();
mSp = getSharedPreferences("config", 0);
playMode = mSp.getInt("play_mode", PLAY_MODE_REPATE_ALL);
}
//存储播放模式
mSp.edit().putInt("play_mode", playMode).commit();
06.21_复习通知栏
/**
* 展示系统通知栏和状态栏
*/
public void showNotification() {
Notification.Builder builder = new Notification.Builder(this);
//状态栏
builder.setTicker("状态栏");
builder.setSmallIcon(R.drawable.notification_music_playing);
//通知栏
builder.setContentTitle("通知栏");
builder.setContentText("通知栏的内容");
builder.setWhen(System.currentTimeMillis());
Notification notification = builder.build();
//显示通知栏
NotificationManager mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNm.notify(0, notification);
}
public void removeNotification() {
//删除通知栏
NotificationManager mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNm.cancel(0);
}
06.22_自定义通知栏
/**
* 展示自定义通知栏
*/
public void showDefineNotification() {
Notification.Builder builder = new Notification.Builder(this);
//状态栏
builder.setTicker("状态栏");
builder.setSmallIcon(R.drawable.notification_music_playing);
//通知栏
builder.setContent(getRemoteView());
Notification notification = builder.build();
//显示通知栏
NotificationManager mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNm.notify(1, notification);
}
private PendingIntent getPendingIntent() {
Intent intent = new Intent(this, AudioPlayerActivity.class);
intent.putExtra("msg", "点击通知栏");
//更新上次通知
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return pi;
}
/**
* 获取定义通知栏布局,控件的监听
*
* @return
*/
private RemoteViews getRemoteView() {
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notify_audio);
remoteViews.setOnClickPendingIntent(R.id.iv_notify_pre, getPrePendingIntent());
remoteViews.setOnClickPendingIntent(R.id.iv_notify_next, getNextPendingIntent());
return remoteViews;
}
/**
* 上一曲的pendingIntent
* @return
*/
private PendingIntent getPrePendingIntent() {
Intent intent = new Intent(this, AudioPlayerActivity.class);
intent.putExtra("notifyPre", "上一曲");
//更新上次通知
PendingIntent pi = PendingIntent.getActivity(this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return pi;
}
/**
* 下一曲的pendingIntent
* @return
*/
private PendingIntent getNextPendingIntent() {
Intent intent = new Intent(this, AudioPlayerActivity.class);
intent.putExtra("notifyNext", "下一曲");
//更新上次通知
PendingIntent pi = PendingIntent.getActivity(this, 2, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return pi;
}
07.03_移植通知栏
/**
* 展示自定义通知栏
*/
public void showAudioNotification() {
Notification.Builder builder = new Notification.Builder(this);
//状态栏
builder.setTicker("正在播放:" + mAudioList.get(mPosition).getTitle());
builder.setSmallIcon(R.drawable.notification_music_playing);
//通知栏
builder.setContent(getRemoteView());
builder.setContentIntent(getPendingIntent());
Notification notification = builder.build();
//显示通知栏,在onCreate中初始化
//mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNm.notify(0, notification);
}
public void removeAudioNotification() {
//删除通知栏
mNm.cancel(0);
}
private PendingIntent getPendingIntent() {
Intent intent = new Intent(this, AudioPlayerActivity.class);
intent.putExtra("msg", "点击通知栏");
//更新上次通知
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return pi;
}
/**
* 获取定义通知栏布局,控件的监听
*
* @return
*/
private RemoteViews getRemoteView() {
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notify_audio);
//通知栏标题
remoteViews.setTextViewText(R.id.tv_notify_title, mAudioList.get(mPosition).getTitle());
//通知栏上控件的监听
remoteViews.setOnClickPendingIntent(R.id.iv_notify_pre, getPrePendingIntent());
remoteViews.setOnClickPendingIntent(R.id.iv_notify_next, getNextPendingIntent());
return remoteViews;
}
/**
* 上一曲的pendingIntent
*
* @return
*/
private PendingIntent getPrePendingIntent() {
Intent intent = new Intent(this, AudioPlayerActivity.class);
intent.putExtra("notifyPre", "上一曲");
//更新上次通知
PendingIntent pi = PendingIntent.getActivity(this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return pi;
}
/**
* 下一曲的pendingIntent
*
* @return
*/
private PendingIntent getNextPendingIntent() {
Intent intent = new Intent(this, AudioPlayerActivity.class);
intent.putExtra("notifyNext", "下一曲");
//更新上次通知
PendingIntent pi = PendingIntent.getActivity(this, 2, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return pi;
}
07.04_操作通知栏
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int type = intent.getIntExtra(notifyType, -1);
switch (type) {
case NOTIFY_CONTENT://通知栏
notifyUpdateUI();
break;
case NOTIFY_PRE://上一曲
playPre();
break;
case NOTIFY_NEXT://下一曲
playNext();
break;
case -1:
default:
mAudioList = (ArrayList<AudioContentBean>) intent.getSerializableExtra("list");
mPosition = intent.getIntExtra("pos", -1);
playItem();
break;
}
return super.onStartCommand(intent, flags, startId);
}
07.05_点击同一首歌操作
default:
int position = intent.getIntExtra("pos", -1);
if (mPosition == position) {
//同一首歌不重复播放
} else {
mPosition = position;
mAudioList = (ArrayList<AudioContentBean>) intent.getSerializableExtra("list");
playItem();
}
break;
07.02_绘制歌词的思路
07.06_绘制单行居中歌词
package cn.nubia.nubiamusic.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.TextView;
import cn.nubia.nubiamusic.R;
/**
* author : gordon
* e-mail : gordon_sun07@163.com
* date : 2019/4/24 18:59
* version: 1.0
* desc :
*/
@SuppressLint("AppCompatCustomView")
public class LyricView extends TextView {
private int mHighLightColor;
private float mHighLightSize;
private int mNormalColor;
private float mNormalSize;
private Paint mPaint;
//控件一半的宽高
private int mHalfViewW;
private int mHalfViewH;
public LyricView(Context context) {
this(context, null);
}
public LyricView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public LyricView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取字体的宽高的一半
String text = "正在玩命加载中";
/*控件宽高1*/
Rect rect = new Rect();
mPaint.getTextBounds(text, 0, text.length(), rect);
int mHalfTextW = rect.width() / 2;
int mHalfTextH = rect.height() / 2;
/*控件宽高2*/
int mHalfTextW2 = (int) mPaint.measureText(text, 0, text.length()) / 2;
//居中行X/Y坐标
int drawX = mHalfViewW - mHalfTextW;
int drawY = mHalfViewH + mHalfTextH;
canvas.drawText(text, drawX, drawY, mPaint);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mHalfViewW = w / 2;
mHalfViewH = h / 2;
}
private void init() {
//高亮居中行
mHighLightColor = getResources().getColor(R.color.green);
mHighLightSize = getResources().getDimension(R.dimen.high_light_text);
//普通行字体
mNormalColor = getResources().getColor(R.color.half_white);
mNormalSize = getResources().getDimension(R.dimen.normal_text);
//初始化画笔
mPaint = new Paint();
mPaint.setTextSize(mHighLightSize);
mPaint.setColor(mHighLightColor);
//抗锯齿
mPaint.setAntiAlias(true);
}
}
07.07_绘制多行歌词
在这里插入代码片
07.08_按行滚动歌词
07.10_从歌词文件中加载歌词
在这里插入代码片
07.11_加载任意类型歌词
07.12_视频出错处理
private class VideoErrorListener implements MediaPlayer.OnErrorListener {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
//提示视频出错
AlertDialog.Builder builder = new AlertDialog.Builder(VideoPlayerActivity.this);
builder.setTitle("提示");
builder.setMessage("当前视频出错");
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
finish();
}
});
builder.setPositiveButton("完成", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.show();
return false;
}
}
07.13_vitamio打造万能播放器