多媒体


  • 多媒体
  • 计算机表示图形的几种方式
  • 缩放加载大图片
  • 创建原图的副本
  • 图形的处理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. 获取手机的宽和高
//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;
  1. 获取图片的宽和高
//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;
  1. 计算缩放比
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;
}
  1. 按照缩放比显示图片
//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. 百度音乐盒完成

  1. 获取当前歌曲进度和总时长
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秒获取歌曲的进度

}

  1. 在服务中添加一个播放进度的方法
/**
 * 设置播放音乐指定位置的方法
 * @param position  该位置由进度条拖动时提供
 */
private void seekToPosition(int position){
    mediaPlayer.seekTo(position);
}
  1. 通过handler将数据传递到Activity更新UI
  2. 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如果需要加载大图片,其实严谨的来说,应该加如图片缩放.有时候拍的照片太大了,以至于无法加载,程序报错.

  1. 首先我们决定将照片放到sd卡的应用缓存目录下,通过getExternalCacheDir()可以得到这个目录(具体的路径是/sdcard/Android/data/<package name>/cache).因为从Android6.0开始,读写SD卡被列为危险权限,如果将图片存放在SD卡的任何其他目录,都要进行运行时权限处理才行,而使用管理目录可以跳过这一步.
  2. 如果运行设备的系统版本低于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);
    }
}