【0050】Android基础-38-多媒体(图片、音频、视频)
【关键点】
【1】在Android中的图片显示的格式是ARGB(透明度);
【2】png 格式 Android采用的是png格式
【1】计算机表示图形的几种方式
图形的大小 = 图片的总像素 * 每个像素的大小
单色 每个像素最多可以表示2种颜色 只需要使用长度为1的二进制位来表示 那么每个像素占1/8byte
16色 每个像素最多可以表示16种颜色 0000 - 1111 那么只需要使用长度为4的二进制表示 那么每个像素占1/2个byte
256色 每个像素最多可以表示256种颜色 0000 0000 - 1111 1111 那么只需要使用长度8的二进制位表示 那么每个像素占1byte
24位 rgb
r 1byte 0-255
g 1byte 0-255
b 1byte 0-255 那么一个像素占3byte
jpg 格式
png 格式 Android采用的是png格式
【2】【实例】缩放加载大图片实例
【最终的效果】点击加载按钮,将已经准备的大图片显示出来;
此图片的分辨率是2400*3200;手机屏幕的分辨率是:320*480;
保证横竖屏都可以切换显示;


【2.1】加载图片出现问题
【出现的问题】


【问题的原因】图片加载时占用的内存过大,实际的分配的内存空间只有16MB;




【结论】手机加载图片的大小与实际的图片的大小没有关系;就比如上面的图片的大小是1.7MB;而实际手机加载申请的字节是:30720012B = 29.296875MB;
【需要计算缩放比】
实际的图片的像素大小是2400*3200,而手机的屏幕的分辨率是320*480,因此需要根据实际的手机的屏幕的大小来进行图片的缩放;
计算缩放比的时候需要动态的计算,不能只定义单独的屏幕的宽或者高,两者要兼顾;
比如:
| 宽 | 高 | |
| 图片分辨率 | 2400 | 3200 |
| 屏幕分辨率 | 320 | 480 |
| 比率 | 7(7.5取整) | 6(6.66取整) |
如果只兼顾宽:则高不合适:3200/7=457<480,图片显示的不够完整;只兼顾高是一样的道理;
如果换一个图片的加载,则比率又会不同,因此需要动态的加载;
【解决办法】使用WindowManager进行动态的计算比率;
【2.2】获得手机屏幕的宽和高

【获取手机的宽和高】
【方法1】

【方法2】

【注意】需要在api13之后的版本才可以使用;


【核心代码】

【2.3】获得图片的宽和高


【核心代码】

【2.4】计算缩放比

【2.5】按照缩放比显示图片

【2.6】遗留问题1
【说明】不同的分辨率手的分配的单个应用的大小是不同的;
在真机上是无法修改的;


【2.7】遗留问题2
当加载的图片的分辨小于手机的分辨率时是不需要进行缩放的;
【3】【实例】创建原图的拷贝
【3.1】创建原图拷贝的意义


【结论】如果选择推荐缩放到最佳尺寸,则进行柔光灯处理之后的速度会快;
【原因说明】原图是不可以被修改的;



【3.2】【实例】将原图加载,并将原图拷贝一个副本出来;
副本是可以修改的, 出现了一道红杠;

【源码】

【MainActivity.java】
1 package com.itheima.copybitmap; 2 3 import android.os.Bundle; 4 import android.app.Activity; 5 import android.graphics.Bitmap; 6 import android.graphics.BitmapFactory; 7 import android.graphics.Canvas; 8 import android.graphics.Color; 9 import android.graphics.Matrix; 10 import android.graphics.Paint; 11 import android.view.Menu; 12 import android.view.View; 13 import android.widget.ImageView; 14 15 public class MainActivity extends Activity { 16 17 @Override 18 protected void onCreate(Bundle savedInstanceState) { 19 super.onCreate(savedInstanceState); 20 setContentView(R.layout.activity_main); 21 22 // [1]找到我们关心的控件 23 ImageView iv_src = (ImageView) findViewById(R.id.iv_src); 24 ImageView iv_copy = (ImageView) findViewById(R.id.iv_copy); 25 26 // [2] 把tomcat.png 转换成bitmap 然后显示到iv_src 27 Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), 28 R.drawable.tomcat); 29 30 // [2.1 test] 修改原图 原图不可以被修改 31 // srcBitmap.setPixel(20, 30, Color.RED); 32 33 iv_src.setImageBitmap(srcBitmap); 34 35 // [3]拷贝原图 36 37 // [3.1]创建模板 38 Bitmap copybitmap = Bitmap.createBitmap(srcBitmap.getWidth(), 39 srcBitmap.getHeight(), srcBitmap.getConfig()); 40 // [3.2]想作画 需要一个画布 以copybitmap为模板 41 Canvas canvas = new Canvas(copybitmap); 42 // [3.3]创建一个画笔 43 Paint paint = new Paint(); 44 // [3.4]开始作画 srcBitmap参考原图去画 45 canvas.drawBitmap(srcBitmap, new Matrix(), paint); 46 47 for (int i = 0; i < 10; i++) { 48 // [一次修改一个像素] 49 copybitmap.setPixel(20 + i, 30, Color.RED); 50 } 51 52 // [4]把copybitmap显示到iv_copy上 53 iv_copy.setImageBitmap(copybitmap); 54 55 } 56 57 }
【activity_main.xml】源码
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" 6 android:gravity="center_horizontal" 7 tools:context=".MainActivity" > 8 9 <ImageView 10 android:id="@+id/iv_src" 11 android:layout_width="wrap_content" 12 android:layout_height="wrap_content" /> 13 14 <ImageView 15 android:id="@+id/iv_copy" 16 android:layout_width="wrap_content" 17 android:layout_height="wrap_content" /> 18 19 </LinearLayout>
【4】图形处理的api :比如图片的反转等等;
【4.1】图片的旋转

【参数】第一个:倾斜多少度;第二个/第三个:旋转的中心点

【修正】老虎的腿没了,因为创建的画布的大小与原图的大小一致,因此需要进行进行修改副本的画布的大小;

【4.2】图片随着时间不停的在旋转

【注意】不能在主线程中进行ui的更新,需要开辟子线程;
1 package com.itheima.copybitmap; 2 3 import android.os.Bundle; 4 import android.os.SystemClock; 5 import android.app.Activity; 6 import android.graphics.Bitmap; 7 import android.graphics.BitmapFactory; 8 import android.graphics.Canvas; 9 import android.graphics.Color; 10 import android.graphics.Matrix; 11 import android.graphics.Paint; 12 import android.view.Menu; 13 import android.view.View; 14 import android.widget.ImageView; 15 16 public class MainActivity extends Activity { 17 18 private float degrees;// 图片旋转的角度 19 20 @Override 21 protected void onCreate(Bundle savedInstanceState) { 22 super.onCreate(savedInstanceState); 23 setContentView(R.layout.activity_main); 24 25 // [1]找到我们关心的控件 26 ImageView iv_src = (ImageView) findViewById(R.id.iv_src); 27 final ImageView iv_copy = (ImageView) findViewById(R.id.iv_copy); 28 29 // [2] 把tomcat.png 转换成bitmap 然后显示到iv_src 30 final Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), 31 R.drawable.tomcat); 32 33 // [2.1 test] 修改原图 原图不可以被修改 34 // srcBitmap.setPixel(20, 30, Color.RED); 35 36 iv_src.setImageBitmap(srcBitmap); 37 38 new Thread() { 39 public void run() { 40 41 for (int i = 0; i < 100; i++) { 42 43 degrees += 5; 44 45 // [3]拷贝原图 46 47 // [3.1]创建模板 48 final Bitmap copybitmap = Bitmap.createBitmap( 49 srcBitmap.getWidth(), srcBitmap.getHeight(), 50 srcBitmap.getConfig()); 51 // [3.2]想作画 需要一个画布 以copybitmap为模板 52 Canvas canvas = new Canvas(copybitmap); 53 // [3.3]创建一个画笔 54 Paint paint = new Paint(); 55 // [3.4]开始作画 srcBitmap参考原图去画 56 Matrix matrix = new Matrix(); 57 58 // [3.5]对图片进行旋转 59 matrix.setRotate(degrees, srcBitmap.getWidth() / 2, 60 srcBitmap.getHeight() / 2); 61 canvas.drawBitmap(srcBitmap, matrix, paint); 62 63 // ☆ 注意不能在子线程更新ui 64 runOnUiThread(new Runnable() { 65 public void run() { 66 // 这个方法里面的逻辑一定是在主线程执行 67 // [4]把copybitmap显示到iv_copy上 68 iv_copy.setImageBitmap(copybitmap); 69 } 70 }); 71 72 SystemClock.sleep(1000); 73 74 } 75 76 }; 77 }.start(); 78 79 } 80 81 }
【4.3】图片的缩放

【注意】如果是放大的话,对应的画板也要相应的放大;
【源码】
1 package com.itheima.copybitmap; 2 3 import android.os.Bundle; 4 import android.os.SystemClock; 5 import android.app.Activity; 6 import android.graphics.Bitmap; 7 import android.graphics.BitmapFactory; 8 import android.graphics.Canvas; 9 import android.graphics.Color; 10 import android.graphics.Matrix; 11 import android.graphics.Paint; 12 import android.view.Menu; 13 import android.view.View; 14 import android.widget.ImageView; 15 16 public class MainActivity extends Activity { 17 18 @Override 19 protected void onCreate(Bundle savedInstanceState) { 20 super.onCreate(savedInstanceState); 21 setContentView(R.layout.activity_main); 22 23 // [1]找到我们关心的控件 24 ImageView iv_src = (ImageView) findViewById(R.id.iv_src); 25 final ImageView iv_copy = (ImageView) findViewById(R.id.iv_copy); 26 27 // [2] 把tomcat.png 转换成bitmap 然后显示到iv_src 28 final Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), 29 R.drawable.tomcat); 30 31 // [2.1 test] 修改原图 原图不可以被修改 32 // srcBitmap.setPixel(20, 30, Color.RED); 33 34 iv_src.setImageBitmap(srcBitmap); 35 36 // [3]拷贝原图 37 // [3.1]创建模板 38 final Bitmap copybitmap = Bitmap.createBitmap(srcBitmap.getWidth(), 39 srcBitmap.getHeight(), srcBitmap.getConfig()); 40 // [3.2]想作画 需要一个画布 以copybitmap为模板 41 Canvas canvas = new Canvas(copybitmap); 42 // [3.3]创建一个画笔 43 Paint paint = new Paint(); 44 // [3.4]开始作画 srcBitmap参考原图去画 45 Matrix matrix = new Matrix(); 46 47 // [3.5]对图片缩放处理 48 matrix.setScale(1.0f, -1.0f); 49 50 canvas.drawBitmap(srcBitmap, matrix, paint); 51 52 // [4]把copybitmap显示到iv_copy上 53 iv_copy.setImageBitmap(copybitmap); 54 55 } 56 57 }
【4.4】位移

【4.5】倒影的效果


【4.6】镜面效果


【5】【实例】画画板实例
例如实现此功能:

【5.1】注意细节
【注意1】


【注意 2】
在图像画完之后需要更新UI

【注意3】出现下面的情况是因为起点的坐标没有更新;
也就是说:画笔的起点一直在开始定义的点,没有动态的跟随鼠标的抬起和落下更新;


【5.2】实现画画的功能
【功能演示】

【源码】
1 package com.itheima.paint; 2 3 import java.io.File; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 7 import android.net.Uri; 8 import android.os.Bundle; 9 import android.os.Environment; 10 import android.app.Activity; 11 import android.content.Intent; 12 import android.graphics.Bitmap; 13 import android.graphics.Bitmap.CompressFormat; 14 import android.graphics.BitmapFactory; 15 import android.graphics.Canvas; 16 import android.graphics.Color; 17 import android.graphics.Matrix; 18 import android.graphics.Paint; 19 import android.view.Menu; 20 import android.view.MotionEvent; 21 import android.view.View; 22 import android.view.View.OnTouchListener; 23 import android.widget.ImageView; 24 25 public class MainActivity extends Activity { 26 27 private Bitmap srcBitmap; 28 private ImageView iv; 29 private Bitmap copyBitmap; 30 private Canvas canvas; 31 private Paint paint; 32 33 @Override 34 protected void onCreate(Bundle savedInstanceState) { 35 super.onCreate(savedInstanceState); 36 setContentView(R.layout.activity_main); 37 //[1]找到imageview 显示我们画的内容 38 iv = (ImageView) findViewById(R.id.iv); 39 40 //[2]把bg转换成bitmap 41 srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg); 42 //[2.1]创建模板 43 copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig()); 44 //[2.2]以copybitmap为模板 创建一个画布 45 canvas = new Canvas(copyBitmap); 46 //[2.3]创建一个画笔 47 paint = new Paint(); 48 //[2.4]开始作画 49 canvas.drawBitmap(srcBitmap, new Matrix(), paint); 50 51 // canvas.drawLine(20, 20, 30, 50, paint); 52 53 //[3]把copybitmap显示到iv上 54 iv.setImageBitmap(copyBitmap); 55 56 //[4]给iv设置一个触摸事件 57 iv.setOnTouchListener(new OnTouchListener() { 58 59 int startX = 0; 60 int startY = 0; 61 @Override 62 public boolean onTouch(View v, MotionEvent event) { 63 //[5]获取手指触摸的事件类型 64 int action = event.getAction(); 65 //[6]具体判断一下是什么事件类型 66 switch (action) { 67 case MotionEvent.ACTION_DOWN: //按下 68 //[7]获取手指按下坐标 69 startX = (int) event.getX(); 70 startY= (int) event.getY(); 71 System.out.println("按下:"+startX+"---"+startY); 72 break; 73 74 case MotionEvent.ACTION_MOVE://移动 75 //[8]获取停止的坐标 76 int stopX = (int) event.getX(); 77 int stopY = (int) event.getY(); 78 79 System.out.println("移动:"+stopX+"---"+stopY); 80 81 //[9]画线 82 canvas.drawLine(startX, startY, stopX, stopY, paint); 83 84 //[9.1]更新一下起点坐标 85 startX = stopX; 86 startY = stopY; 87 88 //[10]记得更新ui 89 iv.setImageBitmap(copyBitmap); 90 91 92 break; 93 94 case MotionEvent.ACTION_UP: //抬起; 95 96 break; 97 } 98 return true; 99 } 100 }); 101 102 103 104 } 105 }
【5.3】增加画笔颜色的修改和画笔的变粗功能
1 //点击按钮让画笔的颜色 变成红色 2 public void click1(View v) { 3 //设置画笔颜色 4 paint.setColor(Color.RED); 5 } 6 7 8 //让画笔颜色变粗 9 public void click2(View v) { 10 //设置画笔的宽度 11 paint.setStrokeWidth(15); 12 13 }
【5.4】保存画出来图片

1 //保存大作 2 public void click3(View v) { 3 /** 4 * format 保存图片的格式 5 * 6 * quality 保存照片的质量 7 */ 8 try { 9 File file = new File(Environment.getExternalStorageDirectory().getPath(),"dazuo.png"); 10 FileOutputStream fos = new FileOutputStream(file); 11 copyBitmap.compress(CompressFormat.PNG, 100, fos); 12 fos.close(); //关闭流 13 } catch (Exception e) { 14 e.printStackTrace(); 15 } 16 17 }

【5.5】通知图册加载已经绘画保存的图片
【源码】
1 //保存大作 2 public void click3(View v) { 3 /** 4 * format 保存图片的格式 5 * 6 * quality 保存照片的质量 7 */ 8 try { 9 File file = new File(Environment.getExternalStorageDirectory().getPath(),"dazuo.png"); 10 FileOutputStream fos = new FileOutputStream(file); 11 copyBitmap.compress(CompressFormat.PNG, 100, fos); 12 13 //发送一条sd卡挂载上来的广播 欺骗一下系统图库应用 说sd卡被挂载了 你去加载图片吧 14 15 Intent intent = new Intent(); 16 //设置action 17 intent.setAction(Intent.ACTION_MEDIA_MOUNTED); 18 //设置data 19 intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory())); 20 21 //发送无序广播 22 sendBroadcast(intent); 23 24 fos.close(); //关闭流 25 26 27 } catch (Exception e) { 28 e.printStackTrace(); 29 } 30 31 32 }
【6】【实例】撕衣服小案例
【原理】让穿衣服的图片覆盖不穿衣服的图片,然后使用透明画笔将上面穿衣服的照片的抹掉;

【实际的效果】

【源码】
1 package com.itheima.syf; 2 3 import android.os.Bundle; 4 import android.app.Activity; 5 import android.graphics.Bitmap; 6 import android.graphics.BitmapFactory; 7 import android.graphics.Canvas; 8 import android.graphics.Color; 9 import android.graphics.Matrix; 10 import android.graphics.Paint; 11 import android.view.Menu; 12 import android.view.MotionEvent; 13 import android.view.View; 14 import android.view.View.OnTouchListener; 15 import android.widget.ImageView; 16 17 public class MainActivity extends Activity { 18 19 @Override 20 protected void onCreate(Bundle savedInstanceState) { 21 super.onCreate(savedInstanceState); 22 setContentView(R.layout.activity_main); 23 24 // [1]找到iv 显示我们操作的图片 25 final ImageView iv = (ImageView) findViewById(R.id.iv); 26 27 // [2]把我们要操作的图片转换成bitmap 28 Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), 29 R.drawable.pre19); 30 31 // [3]创建原图的副本 32 33 // [3.1]创建模板 34 final Bitmap alterbBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), 35 srcBitmap.getHeight(), srcBitmap.getConfig()); 36 // [3.2]以alterbBitmap bitmap为模板创建一个画布 37 Canvas canvas = new Canvas(alterbBitmap); 38 // [3.3]创建一个画笔 39 Paint paint = new Paint(); 40 // [3.4]开始作画 41 canvas.drawBitmap(srcBitmap, new Matrix(), paint); 42 43 // [4]把alterbitmap显示到iv上 44 iv.setImageBitmap(alterbBitmap); 45 46 // [5]给iv设置一个触摸事件 47 iv.setOnTouchListener(new OnTouchListener() { 48 49 @Override 50 public boolean onTouch(View v, MotionEvent event) { 51 // [6]获取触摸事件的类型 52 int action = event.getAction(); 53 switch (action) { 54 case MotionEvent.ACTION_MOVE: // 移动 55 for (int i = -7; i < 7; i++) { // 改变x 56 for (int j = -7; j < 7; j++) { 57 alterbBitmap.setPixel((int) event.getX()+i, (int) event.getY()+j,Color.TRANSPARENT); 58 } 59 60 } 61 // 一定要记得更新iv 62 iv.setImageBitmap(alterbBitmap); 63 break; 64 } 65 return true; 66 } 67 }); 68 } 69 }
【改进】将每次擦去的图片改为是圆
1 if (Math.sqrt(i * i + j * j) < 7) { 2 // 一次修改一个像素 3 try { 4 alterbBitmap.setPixel((int) event.getX()+i, (int) event.getY()+j,Color.TRANSPARENT); 5 } catch (Exception e) { 6 //e.printStackTrace();不要报异常,原因是图片在撕得时候可能会触碰发到负的坐标然后挂掉; 7 } 8 }

【7】【实例】百度音乐盒完成
【说明】会涉及到一个新的类;此类可以播放音频和视频


【7.1】在之前的百度音乐盒(需要运行在服务中)中添加播放的功能

【7.1.1】异常的处理

【解决办法】


【源码】
1 //专门用来播放音乐的 2 public void playMusic(){ 3 System.out.println("音乐播放了"); 4 5 //[2]设置要播放的资源 path 可以是本地也可是网络路径 6 try { 7 player.reset(); 8 9 player.setDataSource("/mnt/sdcard/xpg.mp3"); 10 11 //[3]准备播放 12 player.prepare(); 13 14 //[4]开始播放 15 player.start(); 16 17 } catch (Exception e) { 18 e.printStackTrace(); 19 } 20 21 22 } 23 24 25 // 音乐暂停了 26 public void pauseMusic() { 27 System.out.println("音乐暂停了"); 28 // 暂停 29 player.pause(); 30 31 } 32 33 // 音乐继续播放的方法 34 public void rePlayMusic() { 35 System.out.println("音乐继续播放了"); 36 37 player.start(); 38 39 }
【7.3】增加播放的进度条
【说明】原来的progressBar只会显示,没有进度条拖拽的功能;
现在使用的SeekBar可以进行拖拽;


【7.3.1】更新当前歌曲的播放进度

【说明】不使用while(true)循环的方法,学习使用java中的另外一种方法:Timer类;
【Timer的实例】






【执行的任务的取消】

1 @Override 2 protected void onDestroy() { 3 //当Activity销毁的时候取消timer 4 timer.cancel(); 5 task.cancel(); 6 super.onDestroy(); 7 }

【7.3.2】在更新进度条的时候需要将获取的参数传递seekBar对象(此时的SeekBar对象在MainActivity中),需要使用到handler;
传递的参数多于1个(此处是2个);


【参数的接收设置】
1 public class MainActivity extends Activity { 2 3 private Iservice iservice; // 这个就是我们定义的中间人对象 4 private MyConn conn; 5 private static SeekBar sbar; 6 public static Handler handler = new Handler(){ 7 //当 接收到消息该方法执行 8 public void handleMessage(android.os.Message msg) { 9 //[1]获取msg 携带的数据 10 Bundle data = msg.getData(); 11 //[2]获取当前进度和总进度 12 int duration = data.getInt("duration"); 13 int currentPosition = data.getInt("currentPosition"); 14 15 //[3]设置seekbar的最大进度和当前进度 16 sbar.setMax(duration); //设置进度条的最大值 17 sbar.setProgress(currentPosition);//设置当前进度 18 19 }; 20 };
【7.3.3】进度条的功能:需要将Service中的方法给MainActivity调用;使用接口暴露;


【服务中的代码的添加】
1 package com.itheima.baidumusic; 2 3 import java.util.Timer; 4 import java.util.TimerTask; 5 6 import android.app.Service; 7 import android.content.Intent; 8 import android.media.MediaPlayer; 9 import android.media.MediaPlayer.OnCompletionListener; 10 import android.os.Binder; 11 import android.os.Bundle; 12 import android.os.IBinder; 13 import android.os.Message; 14 15 //音乐播放服务 16 public class MusicService extends Service { 17 18 private MediaPlayer player; 19 20 // [2]把我们定义的中间人对象 返回 21 @Override 22 public IBinder onBind(Intent intent) { 23 return new MyBinder(); 24 } 25 26 // 服务第一次开启的是调用 27 @Override 28 public void onCreate() { 29 30 // [1]初始化mediaplayer 31 player = new MediaPlayer(); 32 33 super.onCreate(); 34 } 35 36 // 当服务销毁的时候调用 37 @Override 38 public void onDestroy() { 39 super.onDestroy(); 40 } 41 42 // 设置播放音乐指定位置的方法 43 public void seekToPosition(int position) { 44 player.seekTo(position); 45 } 46 47 // 专门用来播放音乐的 48 public void playMusic() { 49 System.out.println("音乐播放了"); 50 51 // [2]设置要播放的资源 path 可以是本地也可是网络路径 52 try { 53 player.reset(); 54 55 player.setDataSource("/mnt/sdcard/xpg.mp3"); 56 57 // [3]准备播放 58 player.prepare(); 59 60 // [4]开始播放 61 player.start(); 62 63 // [5]更新进度条 64 updateSeekBar(); 65 66 } catch (Exception e) { 67 e.printStackTrace(); 68 } 69 70 } 71 72 // 更新进度条的方法 73 private void updateSeekBar() { 74 // [1]获取当前歌曲总时长 75 final int duration = player.getDuration(); 76 // [2]一秒钟获取一次当前进度 77 final Timer timer = new Timer(); 78 final TimerTask task = new TimerTask() { 79 80 @Override 81 public void run() { 82 // [3]获取当前歌曲的进度 83 int currentPosition = player.getCurrentPosition(); 84 85 // [4]创建message对象 86 Message msg = Message.obtain(); 87 // [5]使用msg携带多个数据 88 Bundle bundle = new Bundle(); 89 bundle.putInt("duration", duration); 90 bundle.putInt("currentPosition", currentPosition); 91 msg.setData(bundle); 92 // 发送消息 MainActivity的handlemessage方法会执行 93 MainActivity.handler.sendMessage(msg); 94 95 } 96 }; 97 // 300毫秒后 每隔1秒钟获取一次当前歌曲的进度 98 timer.schedule(task, 300, 1000); 99 // [3]当歌曲播放完成的时候 把timer 和task 取消 100 player.setOnCompletionListener(new OnCompletionListener() { 101 102 // 当歌曲播放完成的回调 103 @Override 104 public void onCompletion(MediaPlayer mp) { 105 System.out.println("歌曲播放完成了 "); 106 107 timer.cancel(); 108 task.cancel(); 109 110 } 111 }); 112 113 } 114 115 // 音乐暂停了 116 public void pauseMusic() { 117 System.out.println("音乐暂停了"); 118 // 暂停 119 player.pause(); 120 121 } 122 123 // 音乐继续播放的方法 124 public void rePlayMusic() { 125 System.out.println("音乐继续播放了"); 126 127 player.start(); 128 129 } 130 131 // [1]定义一个中间人对象(IBinder) 132 private class MyBinder extends Binder implements Iservice { 133 134 // 调用播放音乐的方法 135 @Override 136 public void callPlayMusic() { 137 138 playMusic(); 139 } 140 141 // 调用暂停音乐的方法 142 @Override 143 public void callPauseMusic() { 144 145 pauseMusic(); 146 } 147 148 // 调用继续播放的方法 149 @Override 150 public void callrePlayMusic() { 151 152 rePlayMusic(); 153 } 154 155 // 调用设置播放指定位置的方法 156 @Override 157 public void callSeekToPosition(int position) { 158 159 seekToPosition(position); 160 } 161 162 } 163 164 }
【Mainactivity中的接收】
1 package com.itheima.baidumusic; 2 3 import android.os.Bundle; 4 import android.os.Handler; 5 import android.os.IBinder; 6 import android.app.Activity; 7 import android.content.ComponentName; 8 import android.content.Intent; 9 import android.content.ServiceConnection; 10 import android.view.Menu; 11 import android.view.View; 12 import android.view.ViewGroup; 13 import android.widget.BaseAdapter; 14 import android.widget.SeekBar; 15 import android.widget.SeekBar.OnSeekBarChangeListener; 16 17 public class MainActivity extends Activity { 18 19 private Iservice iservice; // 这个就是我们定义的中间人对象 20 private MyConn conn; 21 private static SeekBar sbar; 22 public static Handler handler = new Handler(){ 23 //当 接收到消息该方法执行 24 public void handleMessage(android.os.Message msg) { 25 //[1]获取msg 携带的数据 26 Bundle data = msg.getData(); 27 //[2]获取当前进度和总进度 28 int duration = data.getInt("duration"); 29 int currentPosition = data.getInt("currentPosition"); 30 31 //[3]设置seekbar的最大进度和当前进度 32 sbar.setMax(duration); //设置进度条的最大值 33 sbar.setProgress(currentPosition);//设置当前进度 34 35 }; 36 }; 37 38 39 40 @Override 41 protected void onCreate(Bundle savedInstanceState) { 42 super.onCreate(savedInstanceState); 43 setContentView(R.layout.activity_main); 44 45 sbar = (SeekBar) findViewById(R.id.seekBar1); 46 47 48 //[0]先调用startservice 方法开启服务 保证服务在后台长期运行 49 Intent intent = new Intent(this, MusicService.class); 50 startService(intent); 51 52 // [1]调用bindservice 目的是为了获取我们定义的中间人对象 53 conn = new MyConn(); 54 // 连接MusicService 服务 获取我们定义的中间人对象 55 bindService(intent, conn, BIND_AUTO_CREATE); 56 57 //[2]给seekbar 设置监听 58 59 sbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { 60 //当停止拖动执行 61 @Override 62 public void onStopTrackingTouch(SeekBar seekBar) { 63 64 //设置播放的位置 65 iservice.callSeekToPosition(seekBar.getProgress()); 66 } 67 //开始拖动 68 @Override 69 public void onStartTrackingTouch(SeekBar seekBar) { 70 71 } 72 73 @Override 74 public void onProgressChanged(SeekBar seekBar, int progress, 75 boolean fromUser) { 76 77 } 78 }); 79 80 } 81 82 // 点击按钮 进行 音乐播放 83 public void click1(View v) { 84 85 // 调用播放音乐的方法 86 iservice.callPlayMusic(); 87 } 88 89 // 暂停音乐 90 public void click2(View v) { 91 92 // 调用暂停音乐的方法 93 iservice.callPauseMusic(); 94 } 95 96 // 继续播放 97 public void click3(View v) { 98 99 // 调用继续播放 100 iservice.callrePlayMusic(); 101 } 102 103 // 当Activity销毁的时候调用 104 @Override 105 protected void onDestroy() { 106 // 在Activity销毁的时候 取消绑定服务 107 unbindService(conn); 108 109 super.onDestroy(); 110 } 111 112 private class MyConn implements ServiceConnection { 113 114 // 当连接成功时候调用 115 @Override 116 public void onServiceConnected(ComponentName name, IBinder service) { 117 // 获取我们定义的中间人对象 118 iservice = (Iservice) service; 119 120 } 121 122 @Override 123 public void onServiceDisconnected(ComponentName name) { 124 125 } 126 127 } 128 129 }
【7.4】播放网络音乐


【同步播放存在的问题】因为网络的资源需要加载,因此在点击播放音乐之后会卡一下才会播放音乐;
改进的方法就使用异步准备;

【解决办法】

【7.5】音乐播放结束之后要完成回调


【8】MediaPlayer的生命周期
【8.1】MediaPlayer的生命周期的讲解

【8.2】在网络播放音乐的时候没有开子线程

【原因】查看源码:在调用下层的代码时候的已经开了子线程;


【9】【实例】视频的播放
【9.1】【说明】需要设置播放视频的内容播放显示的位置

【新的控件SurfaceView】将播放的视频在sfView中显示
surfaceview介绍
[1]surfaceview 控件是一个重量级控件
[2]内部维护了2个线程
A 获取数据 负责显示
B 负责显示 获取数据
[3]他可以直接在子线程更新ui 与进度相关的控件可以直接在子线程更新ui

1 // 找到控件 2 final SurfaceView sfv = (SurfaceView) findViewById(R.id.sfv); 3 4 final SurfaceHolder surfaceHolder = sfv.getHolder(); 5 ------------------------------------- 6 // [2.1]设置播放视频的内容 SurfaceHolder 是用来维护视频播放的内容 7 player.setDisplay(surfaceHolder);
【注意】
【1】播放网络视频需要增加网络的权限
【9.2】【2】没有播放出来

【解决方法1】
开辟子线程,并且睡眠200ms;

【解决方法2】引入surfaceHolder.addCallback;详细的代码见源码
【9.3】视频播放记录位置,在下次进入的时候可以继续上次的播放
【1】在sfView销毁的时候拿到当前视频播放的位置

【2】再再次开始播放的时候读取上次的位置

【源码】
1 package com.itheima.playmusic; 2 3 import java.io.IOException; 4 5 import android.media.MediaPlayer; 6 import android.media.MediaPlayer.OnPreparedListener; 7 import android.os.Bundle; 8 import android.os.SystemClock; 9 import android.app.Activity; 10 import android.view.Menu; 11 import android.view.SurfaceHolder; 12 import android.view.SurfaceHolder.Callback; 13 import android.view.SurfaceView; 14 import android.view.View; 15 import com.itheima.playvide.R; 16 17 public class MainActivity extends Activity { 18 19 private MediaPlayer player; 20 private int currentPosition; // 当前视频播放的位置 21 22 @Override 23 protected void onCreate(Bundle savedInstanceState) { 24 super.onCreate(savedInstanceState); 25 setContentView(R.layout.activity_main); 26 27 // 找到控件 28 final SurfaceView sfv = (SurfaceView) findViewById(R.id.sfv); 29 30 final SurfaceHolder surfaceHolder = sfv.getHolder(); 31 32 // 添加一个callback 33 surfaceHolder.addCallback(new Callback() { 34 // 当surfaceview销毁的时候调用 35 @Override 36 public void surfaceDestroyed(SurfaceHolder holder) { 37 System.out.println("surfaceDestroyed"); 38 if (player != null && player.isPlaying()) { 39 // 获取当前视频播放的位置 40 currentPosition = player.getCurrentPosition(); 41 player.stop(); 42 } 43 } 44 45 // 当surfaceview 初始化了 46 @Override 47 public void surfaceCreated(SurfaceHolder holder) { 48 // [1]初始化mediaplayer 49 player = new MediaPlayer(); 50 51 // [2]设置要播放的资源 path 可以是本地也可是网络路径 52 try { 53 player.setDataSource("http://192.168.13.89:8080/cc.MP4"); 54 55 // [2.1]设置播放视频的内容 SurfaceHolder 是用来维护视频播放的内容 56 player.setDisplay(surfaceHolder); 57 58 // [3]准备播放 59 // player.prepare(); 60 player.prepareAsync(); 61 // 设置一个准备完成的监听 62 player.setOnPreparedListener(new OnPreparedListener() { 63 64 @Override 65 public void onPrepared(MediaPlayer mp) { 66 // [4]开始播放 67 player.start(); 68 // [5]继续上次的位置继续播放 69 player.seekTo(currentPosition); 70 71 } 72 }); 73 74 } catch (Exception e) { 75 e.printStackTrace(); 76 } 77 } 78 79 @Override 80 public void surfaceChanged(SurfaceHolder holder, int format, 81 int width, int height) { 82 83 } 84 }); 85 86 } 87 88 }
【10】VedioView控件
VideoView
[1]这个控件就是对surfaceview 和 meidiaplayer进行封装
[2]meidiaplayer 播放视频他只支持 3gp MP4格式 不支持avi rmvb格式的视频;
如果播放avi rmvb格式的视频可以使用下一节的框架进行操作;





【11】vitamio框架:是一个开源的工程 有专门的官方网站
【11.1】介绍认识
是一个开源的Android项目,也可以下载到jar包,如果使用jar包的方式,就将jar包放入到Android自己的工程的lib目录下;
库的好处是可以看到源码,jar无法看到源码(此处是lib库)


【11.2】如何引用该lib包

【11.3】使用该框架的步骤
1 引入vitamio框架 以library、
2 在布局中定义VideoView
<io.vov.vitamio.widget.VideoView
android:id="@+id/vv"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
3 mainactivity代码
插件vitamio框架检查是否可用
if (!LibsChecker.checkVitamioLibs(this)) {
return;
}
final VideoView vv = (VideoView) findViewById(R.id.vv);
vv.setVideoPath("http://192.168.1.2:8080/haha.avi");
vv.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
vv.start();
}
});
//设置video的控制器
vv.setMediaController(new MediaController(this));
4 一定要在清单文件初始化InitActivity
<activity android:name="io.vov.vitamio.activity.InitActivity"></activity>
【11.4】实例的书写



1 package com.itheima.videoview; 2 3 import io.vov.vitamio.LibsChecker; 4 import io.vov.vitamio.MediaPlayer; 5 import io.vov.vitamio.MediaPlayer.OnPreparedListener; 6 import io.vov.vitamio.widget.MediaController; 7 import io.vov.vitamio.widget.VideoView; 8 import android.os.Bundle; 9 import android.app.Activity; 10 11 public class MainActivity extends Activity { 12 13 @Override 14 protected void onCreate(Bundle savedInstanceState) { 15 super.onCreate(savedInstanceState); 16 setContentView(R.layout.activity_main); 17 // [1]插件vitamio框架检查是否可用 18 if (!LibsChecker.checkVitamioLibs(this)) { 19 return; 20 } 21 22 final VideoView vv = (VideoView) findViewById(R.id.vv); 23 // 设置播放的路径 24 vv.setVideoPath("http://192.168.13.89:8080/aa.avi"); 25 vv.setOnPreparedListener(new OnPreparedListener() { 26 27 @Override 28 public void onPrepared(MediaPlayer mp) { 29 vv.start(); 30 31 } 32 }); 33 // 设置video的控制器 设置一个进度条 34 vv.setMediaController(new MediaController(this)); 35 36 } 37 38 }
【运行程序报错的解决】

【演示效果】

meidiaplayer
videoview
ffmpeg 是由好几十个C大神写的
不重复造轮子 谷歌
【as中导库出现的bug】-非常重要

【如何导库】参考:Android studio使用
【本例没有完成】导入第三方包之后存在问题,报错
1 11-21 02:22:27.444 9346-9346/? E/dalvikvm: Could not find class 'android.graphics.drawable.RippleDrawable', referenced from method android.support.v7.widget.AppCompatImageHelper.hasOverlappingRendering 2 11-21 02:22:27.454 9346-9346/? E/AndroidRuntime: FATAL EXCEPTION: main 3 java.lang.UnsatisfiedLinkError: Couldn't load vinit from loader dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/it.oztaking.com.demo-1.apk"],nativeLibraryDirectories=[/data/app-lib/it.oztaking.com.demo-1, /vendor/lib, /system/lib]]]: findLibrary returned null 4 at java.lang.Runtime.loadLibrary(Runtime.java:355) 5 at java.lang.System.loadLibrary(System.java:525) 6 at io.vov.vitamio.Vitamio.<clinit>(Vitamio.java:258) 7 at io.vov.vitamio.LibsChecker.checkVitamioLibs(LibsChecker.java:40) 8 at it.oztaking.com.demo.MainActivity.onCreate(MainActivity.java:19) 9 at android.app.Activity.performCreate(Activity.java:5133) 10 at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087) 11 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2175) 12 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2261) 13 at android.app.ActivityThread.access$600(ActivityThread.java:141) 14 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1256) 15 at android.os.Handler.dispatchMessage(Handler.java:99) 16 at android.os.Looper.loop(Looper.java:137) 17 at android.app.ActivityThread.main(ActivityThread.java:5103) 18 at java.lang.reflect.Method.invokeNative(Native Method) 19 at java.lang.reflect.Method.invoke(Method.java:525) 20 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737) 21 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553) 22 at dalvik.system.NativeStart.main(Native Method)
【12】照相录像:直接使用隐式意图调用第三方的APP;
【说明】不自己写的原因是市面上的手机型号太多,使用谷歌的API可能无法调用起来相机的功能;





【源码】
1 package com.itheima.carmea; 2 3 import java.io.File; 4 5 import android.net.Uri; 6 import android.os.Bundle; 7 import android.os.Environment; 8 import android.provider.MediaStore; 9 import android.app.Activity; 10 import android.content.Intent; 11 import android.view.Menu; 12 import android.view.View; 13 14 public class MainActivity extends Activity { 15 16 @Override 17 protected void onCreate(Bundle savedInstanceState) { 18 super.onCreate(savedInstanceState); 19 setContentView(R.layout.activity_main); 20 } 21 22 // 点击按钮 进行照相 23 public void click1(View v) { 24 25 // 照相 创建意图 26 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 27 // 存放图片的路径 28 File file = new File(Environment.getExternalStorageDirectory() 29 .getPath(), "haha.png"); 30 intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); // set the 31 // image 32 // file 33 // name 34 // 开启一个activity 并获取结果 35 startActivityForResult(intent, 1); 36 37 } 38 39 // 点击按钮 进行录像 40 public void click2(View v) { 41 42 // 照相 创建意图 43 Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); 44 // 存放图片的路径 45 File file = new File(Environment.getExternalStorageDirectory() 46 .getPath(), "haha.3gp"); 47 intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); // set the 48 // image 49 // file 50 // name 51 // 开启一个activity 并获取结果 52 startActivityForResult(intent, 2); 53 54 } 55 56 // 当开启的这个Activity页面的关闭的时候调用 57 @Override 58 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 59 60 System.out.println("哈哈 方法调用了"); 61 super.onActivityResult(requestCode, resultCode, data); 62 } 63 64 }
浙公网安备 33010602011771号