多媒体
- 多媒体
- 计算机表示图形的几种方式
- 缩放加载大图片
- 创建原图的副本
- 图形的处理api
- 使用MediaPlayer播放音频文件
- 百度音乐盒完成
- SurfaceView介绍
- 补充1 VideoView
- 补充2 vitamio框架
- 照相和录像
- 调用摄像头拍照并显示
- 从相册选择照片
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. 缩放加载大图片
09-14 00:59:51.813: E/AndroidRuntime(2128): Caused by: java.lang.OutOfMemoryError
09-14 00:59:51.813: E/dalvikvm-heap(2128): Out of memory on a 30720012-byte allocation.
在Android下采用ARGB(A表示透明度)来表示颜色 每个像素占4byte
所以需要考虑的问题:
- 动态获取图片的分辨率
- 动态获取手机的分辨率
实现步骤
- 获取手机的宽和高
//1.获取屏幕的分辨率 宽和高
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Point point = new Point();
wm.getDefaultDisplay().getSize(point); //获取屏幕的大小 并将信息放到point里面
int screenWidth = point.x;
int screenHeight = point.y;
- 获取图片的宽和高
//2.获取图片的分辨率 宽和高
//创建bitmap工厂的配置参数
Options option = new Options();
//这个设置为true 则不返回bitmap直接返回null 然后decodeFile()方法将图片的信息封装到Options里面
option.inJustDecodeBounds = true;
//根据路径加载图片 将图片转换成bitmap
BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"/dog.jpg",option);
int imgWidth = option.outWidth;
int imgHeight = option.outHeight;
- 计算缩放比
int scale = 1;
int scalex = imgWidth/screenWidth;
int scaley = imgHeight/screenHeight;
//挑选其中缩放比较大的来
if(scalex>scale && scalex>scaley){
scale = scalex;
}
if(scaley>scale && scaley>scalex){
scale = scaley;
}
- 按照缩放比显示图片
//4.设置图片的缩放比 ,用来节约内存
option.inSampleSize = scale;
option.inJustDecodeBounds = false; //设置这个为false 不然会返回null
Bitmap bitmap = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"/dog.jpg", option);
//5.设置图片
iv_img.setImageBitmap(bitmap);
3. 创建原图的副本
//2.创建副本 copy原图
Bitmap copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
//2.1 创建画布 以copyBitmap为模型
Canvas canvas = new Canvas(copyBitmap);
//2.2 创建画笔
Paint paint = new Paint();
//2.3 开始作画 srcBitmap参考原图作画
canvas.drawBitmap(srcBitmap, new Matrix(), paint);
4. 图形的处理api
- 旋转
// 参数 旋转角度,旋转的中点x,y
matrix.setRotate(degrees, srcBitmap.getWidth() / 2,srcBitmap.getHeight() / 2);
- 缩放
matrix.setScale(0.5f, 0.5f);
- 位移
matrix.setTranslate(80, 0);
- 倒影效果
matrix.setScale(1.0f, -1.0f); //x轴不变 y轴反转
//post是在上一次修改的基础上进行修改,set则是每次都是新的变化 会覆盖上一次的修改
matrix.postTranslate(0, srcBitmap.getHeight());
- 镜面效果
matrix.setScale(-1.0f, 1.0f);
matrix.postTranslate(srcBitmap.getWidth(), 0);
- 设置像素点透明
alterbBitmap.setPixel((int)event.getX()+i, (int)event.getY()+j, Color.TRANSPARENT);
5. 使用MediaPlayer播放音频文件
作用:这个类可用于播放音频或者视频
6. 百度音乐盒完成
- 获取当前歌曲进度和总时长
private void updateSeekBar() {
//6. 获取音乐文件的总时长 Gets the duration of the file.
final int duration = mediaPlayer.getDuration();
//7. 构造定时器
Timer timer = new Timer();
//7.1 创建任务
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
//8. 获取歌曲的当前进度
int currentPosition = mediaPlayer.getCurrentPosition();
//9. 通过handler发送歌曲的信息到Activity更新UI
//9.1 通过将数据封装到Message中
Message msg = new Message();
//9.2 封装多条数据到Message中 将那些数据封装到Bundle中,其实Bundle底层就是Map
Bundle bundle = new Bundle();
bundle.putInt("duration", duration);
bundle.putInt("currentPosition", currentPosition);
msg.setData(bundle);
//10. 发送数据
MainActivity.handler.sendMessage(msg);
}
};
//7.2 300毫秒后 每隔1秒执行一次任务
timer.schedule(timerTask, 300, 1000); //每隔1秒获取歌曲的进度
}
- 在服务中添加一个播放进度的方法
/**
* 设置播放音乐指定位置的方法
* @param position 该位置由进度条拖动时提供
*/
private void seekToPosition(int position){
mediaPlayer.seekTo(position);
}
- 通过handler将数据传递到Activity更新UI
- SeekBar处理数据
sb_seekm.setMax(duration); //设置进度条最大值
sb_seekm.setProgress(currentPosition); //设置进度条当前进度
7. SurfaceView介绍
- SurfaceView控件是一个重量级控件
- 内部维护了2个线程
- A 获取数据 负责显示
- B 负责显示 获取数据
- 它直接可以在子线程更新UI 与进度相关的控件可以直接在子线程更新Ui
//找到控件
final SurfaceView sfv = (SurfaceView) findViewById(R.id.sfv);
final SurfaceHolder surfaceHolder = sfv.getHolder();
//添加一个callback
surfaceHolder.addCallback(new Callback() {
//当surfaceview销毁的时候调用
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
System.out.println("surfaceDestroyed");
if (player!=null && player.isPlaying()) {
//获取当前视频播放的位置
currentPosition = player.getCurrentPosition();
player.stop();
}
}
//当surfaceview 初始化了
@Override
public void surfaceCreated(SurfaceHolder holder) {
//[1]初始化mediaplayer
player = new MediaPlayer();
//[2]设置要播放的资源 path 可以是本地也可是网络路径
try {
player.setDataSource("http://192.168.13.89:8080/cc.MP4");
//[2.1]设置播放视频的内容 SurfaceHolder 是用来维护视频播放的内容
player.setDisplay(surfaceHolder);
//[3]准备播放
// player.prepare();
player.prepareAsync();
//设置一个准备完成的监听
player.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
//[4]开始播放
player.start();
//[5]继续上次的位置继续播放
player.seekTo(currentPosition);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
});
补充1 VideoView
- 这个控件就是对SurfaceView和MediaPlayer进行封装
- MediaPlayer 播放视频只支持3gp mp4格式
- 如果只是播放一些游戏的片头动画,或者某个应用的视频宣传,使用VideoView还是绰绰有余的.
补充2 vitamio框架
vitamio框架是开源的,可以播放大多数视频格式的框架.
8. 照相和录像
// create Intent to take a picture and return control to the calling
// application
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File file = new File(Environment.getExternalStorageDirectory(),
"1.png");
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
// start the image capture Intent
startActivityForResult(intent, 1);
9. 调用摄像头拍照并显示
下面的demo如果需要加载大图片,其实严谨的来说,应该加如图片缩放.有时候拍的照片太大了,以至于无法加载,程序报错.
- 首先我们决定将照片放到sd卡的应用缓存目录下,通过
getExternalCacheDir()
可以得到这个目录(具体的路径是/sdcard/Android/data/<package name>/cache
).因为从Android6.0开始,读写SD卡被列为危险权限,如果将图片存放在SD卡的任何其他目录,都要进行运行时权限处理才行,而使用管理目录可以跳过这一步. - 如果运行设备的系统版本低于Android 7.0,就调用Uri.fromFile()方法将File对象转换成Uri对象,这个Uri对象标识着图片的本地真实路径.
否则,就调用FileProvider的getUriForFile()方法将File对象转换成一个封装过的Uri对象.
之所以要进行这样一层转换,是因为从Android 7.0开始,直接使用本地真实路径的Uri被认为是不安全的,会抛出FileUriExposedException异常.而FileProvider则是一种特殊的内容提供器,它使用了和内容提供器类似的机制来对数据进行保护,可以选择性的将封装过的Uri共享给外部,从而提高了应用的安全性.
既然是内容提供器,则需要到清单文件中配置,具体配置如下:
<!--
authorities参数必须和刚才FileProvider.getUriForFile()的第二个参数保持一致,可随意写
name的值是固定的
<meta-data>用来指定共享路径,并引用了一个@xml/file_paths资源
-->
<provider
android:authorities="com.xfhy.cameraalbumtest.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"
/>
</provider>
还需要在res目录下创建xml文件夹,然后新建File,file_paths.xml文件.写入如下内容:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path=""/>
</paths>
其中external-path
是用来指定Uri共享的,name属性的值可以随便填,path属性的值表示共享的具体路径,这里设置为空值表示将整个SD卡进行共享
还有一点需要注意,在Android 4.4系统之前,访问SD卡的应用关联目录也是要声明权限的,从4.4系统开始不再需要权限申明.那么我们为了能够兼容老版本的系统的手机,需要在清单文件中申明如下权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
3.下面是具体的代码实现:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final int TAKE_PHOTO = 1;
private static final String TAG = "MainActivity";
private Button bt_take_photo; //拍照按钮
private Button bt_choose_from_album;
private ImageView iv_picture; //显示图片
private Uri imageUri; //标示图片路径的uri
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt_take_photo = (Button) findViewById(R.id.bt_take_photo);
bt_choose_from_album = (Button) findViewById(R.id.bt_choose_from_album);
iv_picture = (ImageView) findViewById(R.id.iv_picture);
bt_take_photo.setOnClickListener(this);
bt_choose_from_album.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_take_photo:
takePhoto();
break;
case R.id.bt_choose_from_album:
break;
default:
break;
}
}
/**
* 调用摄像头拍照
*/
private void takePhoto() {
//1. 创建File对象,用于存储拍照后的图片
//这里的getExternalCacheDir()是应用的关联缓存目录,在SD卡下面(/sdcard/Android/data/<package name>/cache)
//这里在Android 6.0运行时不用进行运行时权限处理
File outputImage = new File(getExternalCacheDir(), "output_image.jpg");
//2. 判断文件是否存在
try {
if (outputImage.exists()) {
outputImage.delete();
outputImage.createNewFile();
}
} catch (IOException e) {
e.printStackTrace();
}
//3. 判断当前用户的设备的系统版本是否大于24(android 7.0)
if (Build.VERSION.SDK_INT >= 24) {
//4. 将File对象转化为一个封装好的Uri对象
imageUri = FileProvider.getUriForFile(MainActivity.this,
"com.xfhy.cameraalbumtest.fileprovider",outputImage);
} else {
imageUri = Uri.fromFile(outputImage);
}
//5.启动相机程序
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
startActivityForResult(intent,TAKE_PHOTO);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//请求码
switch (requestCode){
case TAKE_PHOTO:
//如果返回码是RESULT_OK,则是成功拍照了的
if(resultCode == RESULT_OK){
try {
//6. 将拍摄的照片显示出来
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
iv_picture.setImageBitmap(bitmap); //设置显示图片
Log.i(TAG, "onActivityResult: ");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
}
10. 从相册选择照片
读取相册中的照片,是需要申请读取SD卡权限.
根据系统版本是否是Android 4.4以上,有2种处理方式.之所以要这样做,是因为Android系统从4.4版本开始,选取相册中的图片不再返回图片的真实Uri了,而是一个封装过的Uri,因此如果是4.4版本以上的手机就需要对这个版本进行解析才行.
下面是demo
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
/**
* 申请码
*/
private static final int TAKE_PHOTO = 1; //照相
private static final int CHOOSE_PHOTO = 2; //打开相册
private static final String TAG = "MainActivity";
private Button bt_take_photo; //拍照按钮
private Button bt_choose_from_album;
private ImageView iv_picture; //显示图片
private Uri imageUri; //标示图片路径的uri
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt_take_photo = (Button) findViewById(R.id.bt_take_photo);
bt_choose_from_album = (Button) findViewById(R.id.bt_choose_from_album);
iv_picture = (ImageView) findViewById(R.id.iv_picture);
bt_take_photo.setOnClickListener(this);
bt_choose_from_album.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_take_photo:
takePhoto();
break;
case R.id.bt_choose_from_album:
requestPermission();
break;
default:
break;
}
}
/**
* 申请权限 需要选择相册中的图片,则需要读SD卡的权限
*/
private void requestPermission() {
//检查是否有读SD卡的权限 不相等则需要申请权限
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//申请读SD的权限
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
openAlbum();
}
}
/**
* 打开相册 进行图片选择
*/
private void openAlbum() {
//
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent, CHOOSE_PHOTO); //打开相册
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) { //根据申请码进行判断
case 1:
//判断权限是否申请成功
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openAlbum();
} else {
Toast.makeText(this, "申请读SD卡权限失败", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//请求码
switch (requestCode) {
case CHOOSE_PHOTO:
if (resultCode == RESULT_OK) {
//判断手机系统版本号
if (Build.VERSION.SDK_INT >= 19) {
//4.4及以上系统使用这个方法处理图片
handleImageOnKitKat(data);
} else {
//4.4以下系统使用这个方法处理图片
handleImageBeforeKitKat(data);
}
}
break;
default:
break;
}
}
/**
* 4.4及以上系统使用这个方式处理图片
* @param data
*/
@TargetApi(19)
private void handleImageOnKitKat(Intent data) {
String imagePath = null;
Uri uri = data.getData();
if(DocumentsContract.isDocumentUri(this,uri)){
//如果是document类型的Uri,则通过document id处理
String docId = DocumentsContract.getDocumentId(uri);
if("com.android.providers.media.documents".equals(uri.getAuthority())){
//解析出数字格式的id
String id = docId.split(":")[1];
String selection = MediaStore.Images.Media._ID+"="+id;
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
} else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
imagePath = getImagePath(contentUri,null);
}
} else if("content".equalsIgnoreCase(uri.getScheme())){
//如果是content类型的uri,则使用普通方式处理
imagePath = getImagePath(uri,null);
} else if("file".equalsIgnoreCase(uri.getScheme())){
//如果是file类型的uri,直接获取图片路径即可
imagePath = uri.getPath();
}
displayImage(imagePath);
}
/**
* 显示图片
* @param imagePath
*/
private void displayImage(String imagePath) {
if(imagePath != null){
//BitmapFactory可以将指定路径文件转换成Bitmap对象
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
iv_picture.setImageBitmap(bitmap);
} else {
Toast.makeText(this, "failed to get image", Toast.LENGTH_SHORT).show();
}
}
/**
* 获取图片路径
* @param uri
* @param selection
* @return
*/
private String getImagePath(Uri uri, String selection) {
String path = null;
//通过Uri和selection来获取图片真实的路径
Cursor cursor = getContentResolver().query(uri,null,selection,null,null);
//首先需要判断cursor是否为空
if (cursor != null) {
if(cursor.moveToFirst()){
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
//最后一定要记得关闭cursor
cursor.close();
}
return path;
}
/**
* 4.4以下系统使用这个方法处理图片
*
* @param data
*/
private void handleImageBeforeKitKat(Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(uri,null);
displayImage(imagePath);
}
}