android 学习笔记11-多媒体

1、图片处理-加载大图片

图片总大小 = 图片总像素 * 每个像素的大小
单色图:每个像素占用1/8个字节
16色图:每个像素占用1/2个字节
256色图:每个像素占用1个字节
24位图:每个像素占用3个字节

为什么要进行处理在显示呢?因为堆内存只有64M(不同手机不一样),加载图片会内存溢出OOM。
android解析图片ARGB  一个1.7M图片需要申请 29M左右的内存,会崩。
图片的像素比我的屏幕的像素要大,如果不缩小,多加载的不能显示没有意义

一般的步骤:
a,获取屏幕宽高
    Display dp = getWindowManager().getDefaultDisplay();
    int screenWidth = dp.getWidth();
    int screenHeight = dp.getHeight();
b,获取图片宽高
    Options opts = new Options();
    //请求图片属性但不申请内存
    opts.inJustDecodeBounds = true;
    BitmapFactory.decodeFile("sdcard/dog.jpg", opts);
    int imageWidth = opts.outWidth;
    int imageHeight = opts.outHeight;
c,图片的宽高除以屏幕宽高,算出宽和高的缩放比例,取较大值作为图片的缩放比例
    int scale = 1;
    int scaleX = imageWidth / screenWidth;
    int scaleY = imageHeight / screenHeight;
    if(scaleX >= scaleY && scaleX > 1){
        scale = scaleX;
    }
    else if(scaleY > scaleX && scaleY > 1){
        scale = scaleY;
    }
d,按缩放比例加载图片
    opts.inSampleSize = scale;//设置缩放比例
    opts.inJustDecodeBounds = false;//为图片申请内存
    Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts);
    iv.setImageBitmap(bm);

代码演示:
    public class MainActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }

        public void click(View v){
            Options opts = new Options();
            //只请求图片宽高,不解析图片像素
            opts.inJustDecodeBounds = true;
            //返回null,获取图片宽高,保存在opts对象中
            BitmapFactory.decodeFile("sdcard/test.jpg", opts);
            //获取图片宽高
            int imageWidth = opts.outWidth;
            int imageHeight = opts.outHeight;

            //获取屏幕宽高
            Display dp = getWindowManager().getDefaultDisplay();
            int screenWidth = dp.getWidth();
            int screenHeight = dp.getHeight();

            //计算缩放比例
            int scale = 1;
            int scaleWidth = imageWidth / screenWidth;
            int scaleHeight = imageHeight / screenHeight;
            //判断取哪个比例
            if(scaleWidth >= scaleHeight && scaleWidth > 1){
                scale = scaleWidth;
            }
            else if(scaleWidth < scaleHeight && scaleHeight > 1){
                scale = scaleHeight;
            }
            //设置缩小比例
            opts.inSampleSize = scale;
            opts.inJustDecodeBounds = false;
            //获取缩小后的图片的像素信息
            Bitmap bm = BitmapFactory.decodeFile("sdcard/test.jpg", opts);
            ImageView iv = (ImageView) findViewById(R.id.iv);
            iv.setImageBitmap(bm);

        }
    }

2、创建图片的副本

默认加载的图片是不能操作的,比如想旋转,平移都是不行的,所以我们需要创建一个图片的副本
    //加载原图
    Bitmap srcBm = BitmapFactory.decodeFile("sdcard/photo.jpg");
    iv_src.setImageBitmap(srcBm);

    //创建与原图大小一致的空白bitmap
    Bitmap copyBm = Bitmap.createBitmap(srcBm.getWidth(), srcBm.getHeight(), srcBm.getConfig());
    //定义画笔
    Paint paint = new Paint();
    //把纸铺在画版上
    Canvas canvas = new Canvas(copyBm);
    //把srcBm的内容绘制在copyBm上
    canvas.drawBitmap(srcBm, new Matrix(), paint);
    iv_copy.setImageBitmap(copyBm);//我们就可以操作iv_copy这个副本了

3、图片的简单特效处理

首先定义一个矩阵对象
Matrix mt = new Matrix();
代码演示:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //加载原图,放在资源文件夹下
        Bitmap bmSrc = BitmapFactory.decodeResource(getResources(), R.drawable.photo);

        //创建副本
        //1.创建与原图一模一样大小的bitmap对象,该对象中目前是没有内容的,可以比喻为创建了和原图一样大小的白纸
        Bitmap bmCopy = Bitmap.createBitmap(bmSrc.getWidth(), bmSrc.getHeight(), bmSrc.getConfig());
        //2.创建画笔对象
        Paint paint = new Paint();
        //3.创建画板,把白纸铺到画板上
        Canvas canvas = new Canvas(bmCopy);

        Matrix mt = new Matrix();

        //平移效果,指定平移距离
        //mt.setTranslate(20, 10);
        //缩放效果,指定缩放比例
        //mt.setScale(2, 0.5f, bmCopy.getWidth() / 2, bmCopy.getHeight() / 2);
        //旋转效果
        //mt.setRotate(45, bmCopy.getWidth() / 2, bmCopy.getHeight() / 2);

        //镜面效果
        //mt.setScale(-1, 1);
        //mt.postTranslate(bmCopy.getWidth(), 0);

        //倒影效果
        mt.setScale(1, -1);
        mt.postTranslate(0, bmCopy.getHeight());

        canvas.drawBitmap(bmSrc, mt, paint);

        ImageView iv_src = (ImageView) findViewById(R.id.iv_src);
        iv_src.setImageBitmap(bmSrc);
        ImageView iv_copy = (ImageView) findViewById(R.id.iv_copy);
        iv_copy.setImageBitmap(bmCopy);
    }

4、画图板

记录用户触摸事件的XY坐标,绘制直线,可以保存已经画好的图片
给ImageView设置触摸侦听,得到用户的触摸事件,并获知用户触摸ImageView的坐标
注意:必须要使用图片的副本操作,因为原图是操作不了的

在图片的View上设置侦听
代码演示:
    public class MainActivity extends Activity {
        private int startX;
        private int startY;
        private Paint paint;//画笔
        private Canvas canvas;//理解为画板
        private ImageView iv;
        private Bitmap bmCopy;//复制图
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            Bitmap bmSrc = BitmapFactory.decodeResource(getResources(), R.drawable.bg);
            bmCopy = Bitmap.createBitmap(bmSrc.getWidth(), bmSrc.getHeight(), bmSrc.getConfig());//白纸
            paint = new Paint();//笔
            canvas = new Canvas(bmCopy);//画板,将空白的bmcopy放到画板中,就可以在上面操作了
            canvas.drawBitmap(bmSrc, new Matrix(), paint);//作画
            iv = (ImageView) findViewById(R.id.iv);
            iv.setImageBitmap(bmCopy);
            iv.setOnTouchListener(new OnTouchListener() {

                //用户手指只要触摸屏幕,就会产生触摸事件
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    switch (event.getAction()) {//判断触摸事件的类型
                    case MotionEvent.ACTION_DOWN://手指触摸
                        //触摸事件中会包含手指触摸的坐标
                        startX = (int) event.getX();
                        startY = (int) event.getY();
                        break;
                    case MotionEvent.ACTION_MOVE://手指滑动
                        int newX = (int) event.getX();
                        int newY = (int) event.getY();
                        //指定线的起点和终点
                        canvas.drawLine(startX, startY, newX, newY, paint);
                        iv.setImageBitmap(bmCopy);

                        //把本此画线的终点设置为下一次画线的起点
                        startX = newX;
                        startY = newY;
                        break;
                    case MotionEvent.ACTION_UP://手指抬起
                        break;

                    }
                    //true表示告诉系统,这个触摸事件由iv处理
                    //false表示不处理该触摸事件,事件往上传递
                    return true;//这里一定要注意了,一定要设置为true
                }
            });
        }

        public void red(View v){//设置画笔的颜色
            paint.setColor(Color.RED);
        }
        public void green(View v){//设置画笔的颜色
            paint.setColor(Color.GREEN);
        }
        public void brush(View v){
            paint.setStrokeWidth(8);//改变线条粗细
        }

        //保存图片
        public void save(View v){
            File file = new File("sdcard/test.png");
            FileOutputStream fos;
            try {
                fos = new FileOutputStream(file);
                //把图片压缩到本地文件
                bmCopy.compress(CompressFormat.PNG, 100, fos);
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

            //手动发一个sd卡就绪广播
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_MEDIA_MOUNTED);
            intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory()));
            sendBroadcast(intent);
        }
    }

注意:保存图片后,在图库里面是显示不了的,因为图库读取的是sd里面的文件在数据库中的索引值,只有在收到sd就绪广播的时候才会扫描sd文件,
    将文件路径存到数据库中,所以我们要手动发一个sd卡就绪的广播,这样在图库中就可以看到我们保存的图片了。

5、游戏案例:

一个撕衣服的游戏小案例
主要的原理就是:把穿内衣和穿外衣的照片重叠显示,内衣照在下面,用户滑动屏幕时,触摸的是外衣照,把手指经过的像素都置为透明,内衣照就显示出来了。
注意在布局文件中设置Ralaytive的布局,不然重叠不到一起,而且图片的像素大小最好和屏幕保持一至,不然会出现撕不准的问题(需要计算比例处理)。

关键的代码:
iv.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_MOVE:
            int newX = (int) event.getX();
            int newY = (int) event.getY();
            copyBm.setPixel(newX, newY, Color.TRANSPARENT);//把指定的像素变成透明
            iv.setImageBitmap(copyBm);
            break;
        }
        return true;
    }
});

每次只设置一个像素点太慢,以触摸的像素为圆心,半径为5画圆,圆内的像素全部置为透明
    for (int i = -10; i < 10; i++) {
        for (int j = -10; j < 10; j++) {
            if(Math.sqrt(i * i + j * j) <= 10)
                copyBm.setPixel(newX + i, newY + j, Color.TRANSPARENT);
        }
    }

6、音乐播放器-本地:

我们查看下API里面的说明就可以大概知道用法了,里面有它使用的一些状态图介绍。
主要的框架代码和服务差不多,使用的是MediaPlayer
播放音频的代码应该运行在服务中,定义一个播放服务MusicService
学习简单的播放,暂停,继续播放,seek等,然后界面显示进度,也可以拖动进度。使用的是SeekBar这个功能

代码演示:
MainActivity.java:
public class MainActivity extends Activity {
    ControllerInterface ci;//定义一个中间人的接口,后面获取服务的对象使用
    private static SeekBar sb;

    public static Handler handler = new Handler(){//注意这里定义static,后面在服务中就直接可以使用这个handler更新界面了
        public void handleMessage(android.os.Message msg) {
            Bundle bundle = msg.getData();
            int duration = bundle.getInt("duration");
            int currentPosition = bundle.getInt("currentPosition");

            //设置进度条显示进度
            sb.setMax(duration);
            sb.setProgress(currentPosition);
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sb = (SeekBar) findViewById(R.id.sb);
        //设置滑动侦听
        sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
            //seekBar:触发该方法执行的seekBar对象
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                //获取sb的当前进度,然后设置给音乐服务的播放进度
                ci.seekTo(seekBar.getProgress());
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress,
                    boolean fromUser) {
            }
        });

        Intent intent = new Intent(this, MusicService.class);
        startService(intent);//把进程变成服务进程
        bindService(intent, new ServiceConnection() {//绑定服务获取中间人对象
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                ci = (ControllerInterface) service;//获取到服务中的中间人对象
            }
        }, BIND_AUTO_CREATE);
    }

    public void play(View v){//调用服务中间人对象里面的方法,这个中间人方法有会去调用服务中的具体实现的代码方法
        ci.play();
    }
    public void pause(View v){//这样activity就可以调用服务中的方法了
        ci.pause();
    }
    public void continuePlay(View v){
        ci.continuePlay();
    }
}

ControllerInterface.java:中间人的接口定义
public interface ControllerInterface {
    void play();
    void pause();
    void continuePlay();
    void seekTo(int progress);
}

MusicService.java:定义服务进程
public class MusicService extends Service {

    MediaPlayer player;
    private Timer timer;
    @Override
    public IBinder onBind(Intent intent) {
        return new MusicController();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        player = new MediaPlayer();//创建服务的时候就把播放器准备好
    }

    @Override
    public void onDestroy() {//服务销毁的时候,播放器也销毁掉
        super.onDestroy();
        //销毁player
        player.stop();
        player.release();//释放资源
        if(timer != null){
            timer.cancel();
            timer = null;
        }
    }

    class MusicController extends Binder implements ControllerInterface{
        @Override
        public void play() {
            MusicService.this.play();//调用服务中的play方法,就是具体的代码实现
        }
        @Override
        public void pause() {
            MusicService.this.pause();
        }
        @Override
        public void continuePlay() {
            MusicService.this.continuePlay();
        }

        @Override
        public void seekTo(int progress) {
            MusicService.this.seekTo(progress);
        }
    }

    public void play(){//播放本地的时候可以不用异步准备
        player.reset();
        try {
            player.setDataSource("sdcard/test.mp3");
            //player.setDataSource("http://localhost:8080/test.mp3");
            //player.prepare();//同步准备
            player.prepareAsync();//异步准备
            player.setOnPreparedListener(new OnPreparedListener() {
                //准备完毕时调用
                @Override
                public void onPrepared(MediaPlayer mp) {
                    player.start();
                    addTimer();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void pause(){
        player.pause();
    }
    public void continuePlay(){
        player.start();
    }
    public void seekTo(int progress){
        player.seekTo(progress);
    }
    public void addTimer(){
        if(timer == null){//注意,如果多次点击播放,就会创建很多的timer,这里判断下,为null才创建,就可以解决了
            timer = new Timer();
            //设置计时任务
            timer.schedule(new TimerTask() {
                //这个run方法也是在子线程执行
                @Override
                public void run() {
                    int duration = player.getDuration();//获取播放总时长
                    int currentPosition = player.getCurrentPosition(); //获取当前播放进度

                    Message msg = MainActivity.handler.obtainMessage();
                    //把数据封装至消息对象
                    Bundle data = new Bundle();
                    data.putInt("duration", duration);
                    data.putInt("currentPosition", currentPosition);
                    msg.setData(data);

                    MainActivity.handler.sendMessage(msg);
                }
                //计时任务开始5毫秒后,run方法执行,每500毫秒执行一次
            }, 5, 500);
        }
    }

}

7、音乐播放器-在线:

在线只需要注意 使用异步准备,因为你不知道网络的状态,所以需要侦听什么时候准备好
player.setDataSource("http://localhost:8080/test.mp3");
player.setOnPreparedListener(new OnPreparedListener() {
    //准备完毕时调用
    @Override
    public void onPrepared(MediaPlayer mp) {
        player.start();
    }
});

其它的代码可以复用上面的。

8、视频播放器:

使用SurfaceView
对画面的实时更新要求较高
双缓冲技术:内存中有两个画布,A画布显示至屏幕,B画布在内存中绘制下一帧画面,绘制完毕后B显示至屏幕,A在内存中继续绘制下一帧画面
关键的代码:自带的播放器支持的格式少(3gp mp4)
    SurfaceView sv = (SurfaceView) findViewById(R.id.sv);
    SurfaceHolder sh = sv.getHolder();

    MediaPlayer player = new MediaPlayer();
    player.reset();
    try {
        player.setDataSource("sdcard/test.3gp");
        player.setDisplay(sh);
        player.prepare();
    } catch (Exception e) {
        e.printStackTrace();
    }
    player.start();
SurfaceView是重量级组件,可见时才会创建
SurfaceView一旦不可见,就会被销毁,一旦可见,就会被创建,销毁时停止播放,再次创建时再开始播放

代码演示:
public class MainActivity extends Activity {
    private MediaPlayer player;
    int progress = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SurfaceView sv = (SurfaceView) findViewById(R.id.sv);
        //获取surfaceview的控制器,我们是操作holder
        final SurfaceHolder holder = sv.getHolder();
        holder.addCallback(new Callback() {//设置一个回调

            //surfaceview销毁时会调用
            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                if(player != null){
                    //停止之前先保存播放进度,比如你按home或者back键退出了
                    progress = player.getCurrentPosition();//设置这个全局变量,不然再进的时候progress就变了
                    player.stop();
                    player.release();//释放播放器资源
                    player = null;//将播放器置空
                }
            }
            //surfaceview创建时会调用
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                if(player == null){
                    player = new MediaPlayer();//创建surfaceView的时候就创建播放器
                    player.reset();
                    try {
                        player.setDataSource("sdcard/test.3gp");
                        player.setDisplay(holder);//指定视频播放在哪个surfaceview
                        player.prepare();

                        player.seekTo(progress);//跳转到上一次停止的地方继续播放
                        player.start();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

            //surfaceview结构改变时会调用,暂时没有用到
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width,
                    int height) {
            }
        });
    }
}

温馨提示:我们再网上可以下载开源免费的资源
    FFMPEG:开源免费音视频编解码器
    vitamio 多媒体开发框架

9、播放视频也可以用VideoView

VideoView是SurfaceView的一个子类,里面的实现细节我们可以不用管,使用起来比上面的要简单很多
三行代码搞定:
VideoView vv = (VideoView) findViewById(R.id.vv);
vv.setVideoPath("sdcard/test.3gp");
vv.start();

10、摄像头

我们可以调用系统的摄像头的activity去拍照也可以我们自己去实现一个拍照的功能
启动系统提供的拍照程序:
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//隐式启动系统提供的拍照Activity
    File file = new File(Environment.getExternalStorageDirectory(), "test.jpg"); //设置照片的保存路径
    intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); 
    startActivityForResult(intent, 0);

启动系统提供的摄像程序:
    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
    File file = new File(Environment.getExternalStorageDirectory(), "test.3gp"); 
    intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); 
    intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);//设置保存视频文件的质量
    startActivityForResult(intent, 0);

代码演示:
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void click1(View v){
        //启动拍照的Activity
        Intent intent = new Intent();
        intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File("sdcard/haha.jpg")));
        startActivityForResult(intent, 10);//设置一个整数判断是否成功调用,如果成功调起来我们可以做一个逻辑判断
    }
    public void click2(View v){
        //启动拍照的Activity
        Intent intent = new Intent();
        intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File("sdcard/haha2.3gp")));
        intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
        startActivityForResult(intent, 20);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == 10){
            Toast.makeText(this, "拍照完毕", 0).show();
        }
        else if(requestCode == 20){
            Toast.makeText(this, "摄像完毕", 0).show();
        }
    }
}



备注:我们也可以自己写一个activity去实现拍照的功能,甚至我们不用让界面显示出来就可以在后台进行拍照功能
具体的实现方法可以查阅API文档,里面有详细的介绍。按照步骤照抄就可以了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值