程序主界面
![](https://img-blog.csdn.net/20160327163911386?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](https://img-blog.csdn.net/20160327164051496?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
1.主界面,头部是两个TextView(自定义类似指针效果),底部是ViewPager。ViewPager中每个页面对应的是一个Fragment.这样就搭起了首页。
xml文件代码:
- <?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:orientation="vertical"
- android:background="@mipmap/base_bg"
- tools:context=".activity.MainActivity">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="55dp"
- android:orientation="vertical"
- android:background="@mipmap/base_titlebar_bg"
- >
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:orientation="horizontal"
- android:gravity="center_vertical"
- >
-
- <TextView
- android:id="@+id/tv_audio"
- style="@style/mainActivity_indicator"
- android:text="@string/audio"
- />
-
- <TextView
- android:id="@+id/music"
- style="@style/mainActivity_indicator"
- android:text="@string/music" />
-
- </LinearLayout>
-
- <View
- android:id="@+id/indicator"
- android:layout_width="30dp"
- android:layout_height="3dp"
- android:background="@color/green"
- />
-
- </LinearLayout>
-
- <android.support.v4.view.ViewPager
- android:id="@+id/view_pager"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
- </LinearLayout>
2.当选中音频或者视频时,TextView的文字颜色和大小都改变。
-
-
-
-
- public void changeSelectedIndicator(boolean isAudio){
-
- mAudio.setSelected(isAudio);
- mMusic.setSelected(!isAudio);
-
- float ascale = isAudio ? 1.2f : 1.0f;
- float mscale = isAudio ? 1.0f : 1.2f;
-
-
- ViewPropertyAnimator.animate(mAudio).scaleX(ascale);
- ViewPropertyAnimator.animate(mAudio).scaleY(ascale);
-
- ViewPropertyAnimator.animate(mMusic).scaleX(mscale);
- ViewPropertyAnimator.animate(mMusic).scaleY(mscale);
- }
3.滑动指示先与ViewPager的结合。
- <span style="color:#000000;">
-
-
-
-
- protected void scrollIndicator(int position, int positionOffsetPixels) {
- int translationX = mIndicatorWidth * position + positionOffsetPixels / pageSize ;
- LogUtil.i("qd","translationX =="+translationX );
- ViewHelper.setTranslationX(mIndicator, translationX);
-
- }
-
- </span><pre name="code" class="java"><span style="color:#000000;"> mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- LogUtil.i("qd", "position===" + position + " positionOffset=" + positionOffset + " positionOffsetPixels=" + positionOffsetPixels);
- scrollIndicator(position, positionOffsetPixels);
- }
-
- @Override
- public void onPageSelected(int position) {
- changeSelectedIndicator(position == 0);
- }
-
- @Override
- public void onPageScrollStateChanged(int state) {
-
- }
- });</span>
4,给ViewPager设置FragmentStatePagerAdapter,他的构造需要FragmentManager,所以MainActivity需要继承自FragmentActivity
- fragmentAdapter.addItem(new VideoFragment());
- fragmentAdapter.addItem(new MusicFragment());
新建了BaseFragment这样一个基类。设计思想,1.视频和音频的列表页面其实大致相同,只是显示的具体数据不同,所以可以他他们共同的东西抽取出来成为一个
- public abstract class BaseFragment<T> extends Fragment implements BaseInterface
2.每个类都会涉及到,初始化view,初始化data,设置监听等。所以,把这三个方法涉及成一个Interface,由每个需要的类来进行实现。
3.每个Fragment都会执行onCreateView,只是他们的布局不同,抽取出getLayoutId(),返回每个子类具体的布局。并且在onViewCreated方法中,调用initView,initData.
这样子类只需要实现这几个方法即可。
4.如何查询手机当中的视频? 音频资源???
Android将这些的信息都封装进了本地数据库中,我们只需要按照指定的格式查询数据库即可。
android 提供了异步查询数据库的一个类,AsyncQueryHandler,他使用了内容观察者模式,每次数据库发生变动时,他返回的Cursor都会变化。
- <span style="font-size:18px;"> AsyncQueryHandler task = new AsyncQueryHandler(getActivity().getContentResolver()) {
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
- super.onQueryComplete(token, cookie, cursor);
- if(cursor != null){
- mListView.setAdapter(getAdapter(getActivity(), cursor));
- }else{
- LogUtil.i("qd","audioFragment cursor == null");
- }
- }
- };
- task.startQuery(0,null, getUri(), getProjection(),null,null,getOrderBy());
- </span>
音频和视频的区别就在于这些参数上
- <span style="font-size:18px;"> task.startQuery(0,null, getUri(), getProjection(),null,null,getOrderBy());</span>
所以,我们抽取成abstract方法,让具体的子类来实现。
音频的地址:
- <span style="font-size:18px;">MediaStore.Audio.Media.EXTERNAL_CONTENT_URI</span>
视频的地址:
- <span style="font-size:18px;">MediaStore.Video.Media.EXTERNAL_CONTENT_URI</span>
5.查询成功后,会返回一个cursor,其中包括了数据,将数据设置到adapter中,而且这个adapter需要继承自CursorAdapter
- <span style="font-size:18px;">public class VideoAdapter extends CursorAdapter {
- public VideoAdapter(Context context, Cursor c) {
- super(context, c);
- }
-
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- View view = LayoutInflater.from(context).inflate(R.layout.video_list_item, null);
- ViewHolder holder = new ViewHolder();
- holder.title = (TextView) view.findViewById(R.id.title);
- holder.duration = (TextView) view.findViewById(R.id.duration);
- holder.size = (TextView) view.findViewById(R.id.size);
- view.setTag(holder);
- return view;
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- ViewHolder holder = (ViewHolder) view.getTag();
- VideoItem videoItem = VideoItem.fromCursor(cursor);
- holder.title.setText(videoItem.getTitle());
- holder.duration.setText(TimeUtil.formatLong(videoItem.getDuration()));
- holder.size.setText(Formatter.formatFileSize(context, videoItem.getSize()));
- }
-
- class ViewHolder{
- public TextView title;
- public TextView duration;
- public TextView size;
- }
- }
- </span>
6.最后设置当点击条目的时候,页面进行跳转。将条目的位置和查询到的所有数据全都传递过去。
利用了android中的VideoView来进行播放视频。
主要涉及到了一下几个方法。
- <span style="font-size:18px;">
- mVideoView.setVideoPath(item.getPath());
- </span>
- <span style="font-size:18px;">
- mVideoView.setOnPreparedListener(preparedListener);
- </span><pre name="code" class="java"><span style="font-size:18px;">MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener(){
-
- @Override
- public void onPrepared(MediaPlayer mp) {
-
- mVideoView.start();
- if(item != null){
- mTitle.setText(item.getTitle());
- }
-
- mDuration.setText(TimeUtil.formatLong(mVideoView.getDuration()));
- mSeekBarVideo.setMax((int) </span><pre name="code" class="java"><span style="font-size:18px;">mVideoView.getDuration()</span>
); updateVideoCurrentPosition(); LogUtil.i("qd", "preparedListener selectPosition===" + selectPosition); loading.setVisibility(View.GONE); } };
4.获取视频的播放时长和当前时长
- <span style="font-size:18px;">mVideoView.getDuration()</span>
- <span style="font-size:18px;">mVideoView.getCurrentPosition()</span>
5.设置视频播放前的监听
- <span style="font-size:18px;">
- mVideoView.setOnInfoListener(onInfoListener);
- </span><pre name="code" class="java"><span style="font-size:18px;">MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener(){
-
- @Override
- public boolean onInfo(MediaPlayer mp, int what, int extra) {
- switch (what){
- case MediaPlayer.MEDIA_INFO_BUFFERING_START:
- loading.setVisibility(View.VISIBLE);
- break;
- case MediaPlayer.MEDIA_INFO_BUFFERING_END:
- loading.setVisibility(View.GONE);
- break;
- }
- return false;
- }
- };</span>
6.设置视频缓存(针对网络视频,这个方法是用来更新第二进度条的)
- <span style="font-size:18px;">
- mVideoView.setOnBufferingUpdateListener(onBufferingUpdateListener);
- </span><pre name="code" class="java"><span style="font-size:18px;">MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener(){
-
- @Override
- public void onBufferingUpdate(MediaPlayer mp, int percent) {
- float f = (float)percent / 100;
- int secondaryProgress = (int) (f * mVideoView.getDuration());
- mSeekBarVideo.setSecondaryProgress(secondaryProgress);
- }
- };</span>
7.设置视频播放完的监听
- <span style="font-size:18px;">
- mVideoView.setOnCompletionListener(completionListener);
- </span><pre name="code" class="java"><span style="font-size:18px;"> MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener(){
- @Override
- public void onCompletion(MediaPlayer mp) {
- mhandler.removeMessages(UPDATE_VIDEO_CURRENT_POSITION);
- }
- };</span>
8.当对屏幕进行手势识别的时候,用GestureDetector,在onTouch事件时,交给GestureDetector的onTouchEvent来处理。
主要用开源的Vitamio来实现。
1.首先,在清单文件中加入
- <span style="font-size:18px;"> <activity
- android:name="io.vov.vitamio.activity.InitActivity"
- android:configChanges="orientation|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
- android:launchMode="singleTop"
- android:theme="@android:style/Theme.NoTitleBar"
- android:windowSoftInputMode="stateAlwaysHidden" />
-
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.WAKE_LOCK" />
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- </span>
2.在VideoPlayerActivity.java(视频播放页面)初始化数据时,加入
- <span style="font-size:18px;">
- if (!LibsChecker.checkVitamioLibs(this))
- return;</span>
3.替换VideoView,成Vitamio的中的VideoView,在个类的路径
- <span style="font-size:18px;">io.vov.vitamio.widget.VideoView</span>
这样就可以播放基本上所有类型的视频文件了,android默认只支持mp4格式的视频。
- 音频模块(AudioPlayerActivity.java 和 AudioPlayerService.java) 播放音乐需要服务。
Activity与Service如何交互?
1.使用AIDL
2.使用Messenger 这里主要讲解下次方式。
有点像,TCP/IP通讯,需要3此握手,才能建立连接、
第一步:
- <span style="font-size:18px;">
- startService(service);
-
- bindService(service, conn, BIND_AUTO_CREATE);
- </span>
第二步:
- <span style="font-size:18px;">
- @Override
- public IBinder onBind(Intent intent) {
- return serviceMessenger.getBinder();
- }
-
//创建handler,用来处理此信使获取到的信息
- <span style="font-size:18px;">private Handler mHandler = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- }
- };</span>
第三步:
- <span style="font-size:18px;">
- ServiceConnection conn = new ServiceConnection(){
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
-
- Messenger serviceMessenger = new Messenger(service);
-
- Message message = Message.obtain(null, AudioPlayService.WHAT_UI_INTEFACE);
-
- message.obj = AudioPlayerActivity.this;
-
- message.replyTo = uiMessenger;
-
- try {
- serviceMessenger.send(message);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
-
- }
- };</span>
第四步:
- <span style="font-size:18px;">
- private Handler mHandler = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what){
- case WHAT_UI_INTEFACE:
-
- audioUI = (AudioUI) msg.obj;
-
- Messenger uiMessenger = msg.replyTo;
-
- Message message = Message.obtain(null, WHAT_PLAYSERVICE_INTERFACCE);
-
- message.obj = AudioPlayService.this;
-
- message.arg1 = flag ;
-
- try {
- uiMessenger.send(message);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- break;
- }
- super.handleMessage(msg);
- }
- };
- </span>
第五步:
service中拿到了Activity中的信使,所以用此信使传递消息。
- <span style="font-size:18px;">
- private Handler mHandler = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what){
- case AudioPlayService.WHAT_PLAYSERVICE_INTERFACCE:
-
- audioPlayService = (IPlayAudio) msg.obj;
-
- if(msg.arg1 == -1){
-
- audioPlayService.openAudio();
- }else{
-
- updateUI(audioPlayService.getCurrentMusicItem());
- }
-
- break;
- case UPDATE_PLAY_TIME:
- updatePlayTime();
- break;
- case UPDATE_LYRICS:
- updateLyrics();
- break;
- }
- super.handleMessage(msg);
- }
- };</span>
这样就可以在service,activity中拿上对方的引用,来操作对方的方法,
视频 / 音频效果:
![](https://img-blog.csdn.net/20160327181956207?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](https://img-blog.csdn.net/20160327182118927?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
我开发中遇到的坑:
1.音频,视频的开发过程,基本不怎么报错,直接奔溃,要么是ANR,所以一定要细心,在加上Debug进行调试
2.控制台报错:client not yet。。。
Connected to the target VM, address: 'localhost:8603', transport: 'socket'
解决方案:其实还是模棱两可,碰出来的。
重新设置断点,然后在进行调试。
注意查看运行时是否是app(有时候是Activity)
3.studio开发当涉及到.so文件的正确导入方式
在gradle文件中加入下面这句话:
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
4.int类型的两个数,想出得到一个float类型的数值时,需要将除数,或者被除数转换为
float类型,否在得到的除数将== 0;
5.service生命周期
1.startService --> oncreate ---> onStartCommand --->onDestroy(必须调用stopService!!!!!此服务才能结束,否则再次打开应用的时候,可能出现ANR)
2.bindService --> onCreate ---> onBind -- > unUnBind() --> onDestroy
在activity销毁的时候,必须显示的调用unBindServce来接触绑定,销毁service
3.混合启动service startService --> bindService
---> 在activity销毁的时候,必须调用stopService()来停止服务,首先会调用onUnBind,在调用onDestroy
---> 只调用onUnbind()并不会关闭服务。 startService,必须stopService才能关闭掉
---> 如果unBindService,stopService都调用的话onUnbind ,onDestroy会按顺序调用一次。
我在开发音乐播放器的时候,在activity销毁的时候没有关闭service,下次再启动应用的时候出现白屏(也就是应用不能开启,最后会报ANR)
下载地址:
https://github.com/QDqiaodong/VideoAndAudio