多媒体教程

1. 计算机图片的大小

首先我们打开电脑中的画图板,截取像素800*400的区域,在图中作画: 
这里写图片描述

点击左上角另存为,有如下几种常见的图片格式: 
这里写图片描述

我们以bmp格式保存图片,保存图片的类型如下: 
这里写图片描述

分别以24位位图,单色位图,16色位图,256色位图分别保存成4个文件: 
这里写图片描述

查看以上几个图片文件,发现图片的大小不一致,那么图片的总大小是如何计算的呢?以下是计算公式:

图片的总大小 = 图片的总像素 * 每个像素的大小 
每个像素的大小取决于图片能表示的颜色的数量。

1.1. 单色图

只能表示黑白两种颜色,使用0和1就足够表示了,每个像素需要一个长度为1的二进制数字表示颜色即每个像素占用1/8个字节。根据公式,上面保存的单色的图片的大小为: 
400*800*(1/8) = 40000字节,查看单色图片的属性如1下图: 
这里写图片描述

发现图片的总大小为40062字节,为什么会比我们计算的值大呢?因为图片还要存储一些额外的信息,比如时间等等。

1.2. 16色图

只能表示16种颜色,使用16个数字(0 - 15),换成二进制为0000 - 1111,每个像素需要一个长度为4的二进制数字能表示颜色即一个像素占1/2个字节。

1.3. 256色图

只能表示256种颜色,使用256个数字(-128 ~ 127),换成二进制位0000 0000-1111 1111,一个像素占1个字节。

1.4. 24位图

24位图表示范围为24位个二进制数,我们利用计算器可以看到,24位最大可以表示16777215个数字,所以24位图能表一千六百多万种颜色。 
这里写图片描述 
每个像素占用24位,也就是3个字节,分别用RGB表示: 
R:0 - 255,使用1个字节就可以表示; 
G:同上; 
B:同上。

1.5. 32位色

每个像素占用4个字节,分别用ARGB表示: 
A:透明度,0 - 255 
如果图片要显示到界面,那么内存中需要保存图片的所有像素的颜色信息,内存中使用ARGB保存。

2. 加载大图片到内存

2.1. 内存溢出

Android系统以ARGB表示每个像素,所以每个像素占用4个字节,很容易内存溢。下面,使用ImageView加载SD卡中的一张大内存的图片,该图片大小如下图: 
这里写图片描述

界面布局:

<RelativeLayout
    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"
    tools:context=".MainActivity" >
    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

MainActivity中获取到图片的bitmap对象,利用ImageView显示:

public class MainActivity extends Activity {
    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView iv = (ImageView) findViewById(R.id.iv);
        BitmapFactory.decodeFile("/mnt/sdcard/dog.jpg");
        iv.setImageBitmap(bitmap);
    }
}

运行后,程序会崩溃,查看Logcat日志输出,发现内存溢出如下图: 
这里写图片描述

为什么会出现内存溢出呢?当创建模拟器的时候,有一个VM Heap选项,这个选项代表手机模拟器给每个应用默认分配的内存大小,当一个应用的使用内存超过了16M,那么就会报内存溢出的错误。 
这里写图片描述

继续查看日志,可以看到有这么一行日志,如下图: 
这里写图片描述

上图的日志是说创建一个30720012字节的文件时内存溢出。我们来计算下加载的图片显示到手机上的总的大小,图片的分辨率为2400*3200如下图: 
这里写图片描述 
在安卓中,是使用ARGB表示图片像素的,所以一个像素是4个byte,根据计算公式,该图片的总大小为2400*3200*4=30720000,发现和日志输出中的数值是一致的。

2.2. 图片大小缩放

1.获取屏幕宽高

//通过Context的getWindowManager()方法获取Window的管理者对象
Display dp = getWindowManager().getDefaultDisplay();
int screenWidth = dp.getWidth();
int screenHeight = dp.getHeight();

另一种获取Windowmanager对象的方法:

WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);

2.获取图片宽高

Options opts = new Options();
//inJustDecodeBounds属性值为true,表示只请求图片属性,不申请内存
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile("sdcard/dog.jpg", opts);
//获取图片的宽
int imageWidth = opts.outWidth;
//获取图片的高
int imageHeight = opts.outHeight;

3.获取缩放比例 
图片的宽高除以屏幕宽高,算出宽和高的缩放比例,取较大值作为图片的缩放比例。

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;
}

4.按缩放比例加载图片

//设置缩放比例
opts.inSampleSize = scale;
//inJustDecodeBounds属性为false,表示为图片申请内存
opts.inJustDecodeBounds = false;
//从文件中获取Bitmap,参数1表示文件路径,参数2表示图片参数。
Bitmap bm = BitmapFactory.decodeFile("sdcard/dog.jpg", opts);
iv.setImageBitmap(bm);

运行效果: 
这里写图片描述

3. 创建图片副本

我们在使用美图秀秀对图片进行编辑操作后,保存图片的时候,是保存为另外一张图片。其实其内部原理是先创建一张原图的副本,然后再副本图片上进行用户编辑操作,所以最后保存的时候是新的一张图片。 
此外,直接加载的bitmap对象是只读的,无法修改,要修改图片只能在内存中创建出一个一模一样的bitmap副本,然后修改副本。 
首先,创建布局,有两个ImageView:

<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"
    tools:context=".MainActivity" >

    <ScrollView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            <ImageView
                android:id="@+id/iv_src"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <ImageView
                android:id="@+id/iv_copy"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>

    </ScrollView>

</LinearLayout>

Activity中,创建图片副本,修改副本图片,并显示到另一个ImageView上。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView iv_copy = (ImageView) findViewById(R.id.iv_copy);  
        ImageView iv_src = (ImageView) findViewById(R.id.iv_src);  
        Bitmap srcBitmap = BitmapFactory.decodeFile("mnt/sdcard/meinv.png");
        iv_src.setImageBitmap(srcBitmap);
        //创建一个和原图大小一样,并且图片参数一样的空白bitmap
        Bitmap copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(),srcBitmap.getHeight(), srcBitmap.getConfig());
        //创建画布对象
        Canvas canvas = new Canvas(copyBitmap);
        //创建画笔对象
        Paint paint = new Paint();
        //调用画布对象Canvas的drawBitmap()方法将原图的Bitmap画到画布上,参数1表示原图的bitmap,参数2表示矩阵,参数3表示画笔
        canvas.drawBitmap(srcBitmap, new Matrix(), paint);
        //将拷贝图片的bitmap对象上坐标(20,30)点设置成红色
        copyBitmap.setPixel(20,30, Color.RED); 
        //将创建的拷贝的bitmap对象设置到另一个ImageView上
        iv_copy.setImageBitmap(copyBitmap);
    }
}

运行效果: 
这里写图片描述

4. 对图片进行特效处理

4.1. 旋转效果

Matrix matrix = new Matrix();
//让矩阵旋转30度
matrix.setRotate(30);
//参数1表示旋转的角度,参数2和参数3是旋转的中心点
matrix.setRotate(30, copyBitmap.getWidth()/2, copyBitmap.getHeight()/2);
//调用画布对象Canvas的drawBitmap()方法将原图的Bitmap画到画布上,参数1表示原图的bitmap,参数2表示矩阵,参数3表示画笔
canvas.drawBitmap(srcBitmap, matrix, paint);
iv_copy.setImageBitmap(copyBitmap);

运行效果: 
这里写图片描述

4.2. 平移效果

Matrix matrix = new Matrix();
matrix.setTranslate(20, 0); 
//调用画布对象Canvas的drawBitmap()方法将原图的Bitmap画到画布上,参数1表示原图的bitmap,参数2表示矩阵,参数3表示画笔
canvas.drawBitmap(srcBitmap, matrix, paint);
iv_copy.setImageBitmap(copyBitmap);

运行效果: 
这里写图片描述

4.3. 缩放效果

Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
//调用画布对象Canvas的drawBitmap()方法将原图的Bitmap画到画布上,参数1表示原图的bitmap,参数2表示矩阵,参数3表示画笔
canvas.drawBitmap(srcBitmap, matrix, paint);
iv_copy.setImageBitmap(copyBitmap);

运行效果:这里写图片描述

4.4. 镜面

Matrix matrix = new Matrix();
//设置缩放
matrix.setScale(-1.0f, 1.0f);
//设置位移
matrix.postTranslate(copyBitmap.getWidth(), 0);
//调用画布对象Canvas的drawBitmap()方法将原图的Bitmap画到画布上,参数1表示原图的bitmap,参数2表示矩阵,参数3表示画笔
canvas.drawBitmap(srcBitmap, matrix, paint);
iv_copy.setImageBitmap(copyBitmap);

注意:setXXX方法每次修改都是最新的的操作,会覆盖上一次操作,post是在上一次修改的基础上继续修改。

运行效果: 
这里写图片描述

4.5. 倒影

Matrix matrix = new Matrix();
matrix.setScale(1.0f, -1.0f);
matrix.postTranslate(0, copyBitmap.getHeight());
//调用画布对象Canvas的drawBitmap()方法将原图的Bitmap画到画布上,参数1表示原图的bitmap,参数2表示矩阵,参数3表示画笔
canvas.drawBitmap(srcBitmap, matrix, paint);
iv_copy.setImageBitmap(copyBitmap);

运行效果: 
这里写图片描述

5. 美图秀秀案例

本案例模拟实现安卓版的美图秀秀中的功能,拖动SeekBar,图片的颜色值随着变化。 
界面效果: 
这里写图片描述

<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"
    tools:context=".MainActivity" >
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="青------红" />
    <SeekBar
        android:id="@+id/sb_red"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="紫------绿" /> 
    <SeekBar
        android:id="@+id/sb_green"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="黄------蓝" />
    <SeekBar
        android:id="@+id/sb_blue"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

MainActivity中初始化控件,给SeekBar设置监听并且创建原图的一个副本:

public class MainActivity extends Activity implements OnSeekBarChangeListener {
    private Paint paint;
    private Canvas canvas;
    private Bitmap srcBitmap;
    private ImageView iv;
    private Bitmap copyBitmap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv = (ImageView) findViewById(R.id.iv);
        SeekBar sb_blue = (SeekBar) findViewById(R.id.sb_blue);
        SeekBar sb_green = (SeekBar) findViewById(R.id.sb_green);
        SeekBar sb_red = (SeekBar) findViewById(R.id.sb_red);
        sb_blue.setOnSeekBarChangeListener(this);
        sb_green.setOnSeekBarChangeListener(this);
        sb_red.setOnSeekBarChangeListener(this);
        srcBitmap = BitmapFactory.decodeFile("mnt/sdcard/meinv.png");
        copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
        canvas = new Canvas(copyBitmap);
        paint = new Paint();
        canvas.drawBitmap(srcBitmap, new Matrix(), paint);
        iv.setImageBitmap(copyBitmap);
    }
}

实现SeekBar监听:

//当进度改变的时候调用
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
 boolean fromUser) {
}
//当开始拖动的时候调用
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
//当停止拖动的时候调用
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
    int id = seekBar.getId();
    //获取当前SeekBar的进度
    int progress = seekBar.getProgress();
    //创建一个颜色矩阵对象
    ColorMatrix cm = new ColorMatrix();
    float rf = 0;
    float gf = 0;
    float bf = 0;
    switch (id) {
    case R.id.sb_red: 
        rf = progress / 128.0f;
        break;
    case R.id.sb_green:
        gf = progress / 128.0f;
        break;
    case R.id.sb_blue:
        bf = progress / 128.0f;
        break;
    }
    /**设置颜色矩阵,颜色矩阵的计算公式如下
        1 0 0 0 0
        0 1 0 0 0
        0 0 1 0 0
        0 0 0 1 0
        New Red Value = 1*128 + 0*128 + 0*128 + 0*0 + 0
        New Blue Value = 0*128 + 1*128 + 0*128 + 0*0 + 0
        New Green Value = 0*128 + 0*128 + 1*128 + 0*0 + 0
        New Alpha Value = 0*128 + 0*128 + 0*128 + 1*0 + 0
    */
    cm.set(new float[] { rf, 0 , 0 , 0, 0,
                          0, gf, 0 , 0, 0, 
                          0, 0 , bf, 0, 0,
                          0, 0 , 0 , 1, 0 });
    //给Paint对象设置颜色过滤
    paint.setColorFilter(new ColorMatrixColorFilter(cm));
    canvas.drawBitmap(srcBitmap, new Matrix(), paint);
    iv.setImageBitmap(copyBitmap);
}

运行效果: 
这里写图片描述

6. 画画板案例

本案例实现在一块背景上可以画画的功能,类似画画板的功能。 
布局界面:

<RelativeLayout 
    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"
    tools:context=".MainActivity" >
    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

在MainActivity中,创建一个背景的图片副本,让ImageView显示:

public class MainActivity extends Activity {
    private Paint paint;
    private Canvas canvas;
    private ImageView iv;
    private Bitmap copyBitmap;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv = (ImageView) findViewById(R.id.iv);
        Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.bg);
        copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(),srcBitmap.getHeight(), srcBitmap.getConfig());
        canvas = new Canvas(copyBitmap);
        paint = new Paint();
        canvas.drawBitmap(srcBitmap, new Matrix(), paint);
        //画一条线,参数1是起点x坐标,参数2是起点y坐标,参数3是终点x坐标,参数4是终点y坐标,参数5是画笔对象。
        canvas.drawLine(10, 10, 40, 100, paint);
        iv.setImageBitmap(copyBitmap);
    }
}

运行效果: 
这里写图片描述

接下来,我们给ImageView设置触摸事件,随着触摸事件在ImageView上画画:

iv.setOnTouchListener(new OnTouchListener() {
    int startX = 0;
    int startY = 0;
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //调用event.getAction()获取触摸事件类型
        int action = event.getAction();
        switch (action) {
        //MotionEvent.ACTION_DOWN表示按下事件
        case MotionEvent.ACTION_DOWN:
            //获取当前手指触摸的x和y坐标
            startX = (int) event.getX();
            startY = (int) event.getY();
            break;
        //MotionEvent.ACTION_MOVE表示手指移动事件
        case MotionEvent.ACTION_MOVE:
            //获取当前手指触摸的x和y坐标
            int newX = (int) event.getX();
            int newY = (int) event.getY();
            //实时更改画线的起点坐标
            canvas.drawLine(startX, startY, newX, newY, paint);
            startX = newX;
            startY = newY;
            iv.setImageBitmap(copyBitmap);
            break;
        //MotionEvent.ACTION_UP表示抬起事件
        case MotionEvent.ACTION_UP:
            break;
        default:
            break;
        }
        return true;
    }
});

运行效果: 
这里写图片描述

接下来,实现刷子效果,可以改变画笔颜色:

public void click1(View view) {
    //设置画笔粗细
    paint.setStrokeWidth(20);
}
public void click2(View view) {
    //设置画笔颜色为绿色
    paint.setColor(Color.GREEN);
}
public void click3(View view) {
    paint.setColor(Color.RED);
}

运行效果: 
这里写图片描述

6.2. 保存图片

将图片保存到SD卡:

public void click4(View view){
    File file = new File(Environment.getExternalStorageDirectory().getPath(), ”haha.png”);
        FileOutputStream fos;
        try {
            fos = new FileOutputStream(file);
            //调用Bitmap对象的compress()方法保存图片,参数1是图片的格式,参数2是图片压缩的质量,参数3是输出流。
            copyBitmap.compress(CompressFormat.PNG, 100, fos);
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

加入权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

运行效果: 
这里写图片描述

这里写图片描述

导出到电脑,用图片查看器查看: 
这里写图片描述

6.3. 在图库中显示图片

系统每次收到SD卡就绪广播时,都会去遍历SD卡的所有文件和文件夹,把遍历到的所有多媒体文件都在MediaStore数据库保存一个索引,这个索引包含多媒体文件的文件名、路径、大小。 
图库每次打开时,并不会去遍历SD卡获取图片,而是通过内容提供者从MediaStore数据库中获取图片的信息,然后读取该图片。 
系统开机或者点击挂载SD卡按钮时,系统会发送sd卡就绪广播,我们也可以手动发送挂载SD卡就绪广播。

Intent intent = new Intent();
intent.setAction(Intent.ACTION_MEDIA_MOUNTED);
intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory()));
sendBroadcast(intent);

运行效果: 
这里写图片描述

7. 撕衣服案例

本案例实现撕衣服的效果,手指在图片上滑动,划过的地方将会把衣服去掉。效果图如下: 
这里写图片描述

手指在图片上滑动: 
这里写图片描述

实现原理:在屏幕上面有两个ImageView,下面的ImageView是没有穿衣服的图片,上面的是穿衣服的。然后创建一个穿衣服的图片的Bitmap副本,设置给上方的ImageView,给上方的ImageView设置触摸监听,当手指移动的时候在副本Bitmap上画透明像素,这样下方的ImageView就显示出来了。

1.布局代码:

<RelativeLayout 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"
    tools:context=".MainActivity" >
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/after19" />
    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

2.具体代码逻辑:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //上方的ImageView
        final ImageView iv = (ImageView) findViewById(R.id.iv);
        //通过BitmapFactory.decodeResource()方法加载图片
        Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pre19);
        //创建图片副本
        final Bitmap alterBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
        Paint paint = new Paint();
        Canvas canvas = new Canvas(alterBitmap);
        canvas.drawBitmap(srcBitmap, new Matrix(), paint);
        //给上方ImageView设置图片副本背景
        iv.setImageBitmap(alterBitmap);
        //给ImageView设置触摸事件
        iv.setOnTouchListener(new OnTouchListener() {
            //当手指滑动的时候调用
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    for (int i = -7; i < 7; i++) {
                        for (int j = -7; j < 7; j++) {
                            if (Math.sqrt(i * i + j * j) < 7) {
                                try {
                                    //在图片副本上画透明像素
                                    alterBitmap.setPixel((int) event.getX() + i, (int) event.getY() + j, Color.TRANSPARENT);
                                } catch (Exception e) {
                                }
                            }

                        }
                    }
                    //给ImageView设置修改后的背景,实时刷新
                    iv.setImageBitmap(alterBitmap);
                    break;

                }
                return true;
            }
        });
    }
}

8. 音乐播放器案例

本案例实现点击按钮实现播放音频的功能。点击按钮播放SD卡上的音频文件。播放音频需要使用到MediaPlayer这个api,下图是MediaPlayer的状态图解: 
这里写图片描述

8.1. 播放音频文件

将xiaopingguo.mp3音频文件导入到SD卡中,如下图: 
这里写图片描述

Activity中点击按钮播放音乐:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void click(View v) {
        //创建MediaPlayer对象
        final MediaPlayer player = new MediaPlayer();
        //设置播放的类型
        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        try {   
            //设置播放的数据源
            player.setDataSource("/mnt/sdcard/xiaopingguo.mp3");
             //player.setDataSource("http://192.168.116.132:8080/xiaopingguo.mp3");
            //准备播放
            player.prepare();
            //如果播放的是网络资源,那么使用prepareAsync()方法来准备播放,因为prepare()方法是阻塞线程的
            // player.prepareAsync();
            //设置准备监听,当播放准备好后回调onPrepared()方法
            player.setOnPreparedListener(new OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    //开始播放
                    player.start();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

8.2. 完善音乐播放器

在Activity中播放音频,当按返回键回到Home界面后,由于应用的进程变成了空进程,所以很容易被系统回收。那么怎么解决这个问题呢?可以在Service中操作播放音频,这样就不容易被系统回收。

1.创建布局

这里写图片描述

<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"
    tools:context=".MainActivity" >
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click1"
        android:text="播放" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click2"
        android:text="暂停" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click3"
        android:text="继续播放" />
    <SeekBar
        android:id="@+id/sb_control"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

2.创建播放音频Service

定义IService接口提供服务中的方法:

public interface Iservice {
    public void callPlay();
    public void callPause();
    public void callReplay();
    public void callSeekTo(int position);
}

编写Service类,并在Service中定义操作音频的方法并创建Binder对象:

public class MusicService extends Service {
    private MediaPlayer player;
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }
    @Override
    public void onCreate() {
        player = new MediaPlayer(); 
        super.onCreate();
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
    public void play(){
        //player.reset()方法用来重置MediaPlayer
        player.reset();
        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        try {
            player.setDataSource("/mnt/sdcard/xiaopingguo.mp3");
            player.prepare();
            player.setOnPreparedListener(new OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                   player.start();              
                   //当开始播放时开始更新进度
                   updateSeekBar();
                 }
            });             
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //定义暂停播放的方法
    public void pause(){
        player.pause();
    }
    //定义继续播放的方法
    public void rePlay(){
        player.start();
    }
    //定义指定从某个地方开始播放
    public void seekTo(int position){
        player.seekTo(position);
    }
    private class MyBinder extends Binder implements Iservice{
        @Override
        public void callPlay() {
            play();
        }
        @Override
        public void callPause() {
            pause();
        }
        @Override
        public void callReplay() {
            rePlay();
        }
        @Override
        public void callSeekTo(int position) {
            seekTo(position);
        }
    }
}

更新进度条:

private void updateSeekBar() {  
    //歌曲的总的时长
    final int duration = player.getDuration();  
    //创建Timer定时任务对象
    Timer timer = new Timer();
    //创建任务对象
    TimerTask task = new TimerTask() {
        @Override
        public void run() {
            //getCurrentPosition()获取当前播放的位置
            int currentPosition = player.getCurrentPosition(); 
            //创建Message消息对象,将歌曲总长度和当前的位置作为数据存入Message
            Message msg = Message.obtain();
            Bundle bundle = new Bundle();
            bundle.putInt("duration", duration);
            bundle.putInt("currentPosition", currentPosition);
            msg.setData(bundle);
            //发送消息
            MainActivity.handler.sendMessage(msg);
        }
    };
    //执行定时任务,参数1为任务对象,参数2为多久开始执行任务,参数3为任务执行的间隔时间
    timer.schedule(task, 50, 1000);
}

3.Activity中实现播放音频,暂停播放,继续播放和处理更新进度

public class MainActivity extends Activity {
    private Myconn myconn;
    private Iservice iservice;
    private static SeekBar sb_contrller;
    //定义Handler,处理进度更新操作
    public static Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            Bundle data = msg.getData();
            int duration = data.getInt("duration"); 
            int currentPosition = data.getInt("currentPosition");
            //设置SeekBar的总进度
            sb_contrller.setMax(duration); 
            //设置SeekBar的当前位置
            sb_contrller.setProgress(currentPosition);
        };
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sb_contrller = (SeekBar) findViewById(R.id.sb_control);
        //设置SeekBar改变监听
        sb_contrller.setOnSeekBarChangeListener(new 
          OnSeekBarChangeListener() {
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                int position = seekBar.getProgress();
                //调用服务中的方法,从SeekBar的拖动到的位置开始播放
                iservice.callSeekTo(position);
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {
            }
        });
        Intent intent = new Intent(this, MusicService.class);
        //先调用startService()让服务能一直运行,再调用bindService()方法使Activity可以调用服务中的方法
        startService(intent);
        myconn = new Myconn();
        bindService(intent, myconn, BIND_AUTO_CREATE);
    }
    public void click1(View v) {
        iservice.callPlay();
    }
    public void click2(View v) {

        iservice.callPause();
    }
    public void click3(View v) {
        iservice.callReplay();
    }
    private class Myconn implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
         {
            iservice = (Iservice) service;
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }
    @Override
    protected void onDestroy() {
        unbindService(myconn);
        super.onDestroy();
    }
}

运行效果: 
这里写图片描述

9. 视频播放器

本案例实现播放SD卡中的一个mp4文件的视频。实现该功能需要用到MediaPlayer中的api,此外还需要SurfaceView来显示播放的视频。

9.1. SurfaceView

SurfaceView是用来播放视频的控件,使用了双缓冲技术:内存中有两个画布,A画布显示至屏幕,B画布在内存中绘制下一帧画面,绘制完毕后B显示至屏幕,A在内存中继续绘制下一帧画面。 
首先创建布局,布局中使用SurfaceView:

<RelativeLayout 
    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"
    tools:context=".MainActivity" >    
    <SurfaceView
        android:id="@+id/sv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

在MainActivity中实现播放:

public class MainActivity extends Activity {
    private SurfaceView sfv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sfv = (SurfaceView) findViewById(R.id.sfv);
    }
    public void click(View v) {
        new Thread() {
            public void run() {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                final MediaPlayer player = new MediaPlayer();
                try {
                            player.setDataSource("http://192.168.116.132:8080/test.mp4");
                    player.prepareAsync();
                    //获取SurfaceHolder对象
                    SurfaceHolder holder = sfv.getHolder();
                    //给MediaPlayer设置holder对象
                    player.setDisplay(holder);
                    //设置准备好的监听,当准备好了之后调开始播放
                    player.setOnPreparedListener(new OnPreparedListener(){
                        @Override
                        public void onPrepared(MediaPlayer mp) {
                            //开始播放
                            player.start();
                        }
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }
}

运行效果,发现播放不了,如下图: 
这里写图片描述

这是由于SurfaceView是重量级组件,对画面的实时更新要求较高。我们查看SurfaceView的API文档,如下图: 
这里写图片描述

文档需要我们实现两个回调方法,给SurfaceHolder设置CallBack,通过回调可以知道SurfaceView的状态,SurfaceView一旦不可见,就会被销毁,一旦可见,就会被创建,销毁时停止播放,再次创建时再开始播放。

SurfaceHolder holder = sv.getHolder();
holder.addCallback(new Callback() {
    //当SurfaceView销毁时调用
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //记录SurfaceView不可见的时候播放的位置
        lastPosition = player.getCurrentPosition();
        //判断player是否为空或者是否正在播放,如果不为空并且正在播放,需要将player暂停
        if (player != null && player.isPlaying()) {
            player.pause();
        }
    }
    //当SurfaceView创建的时候调用,可以在这个方法中,创建MediaPlayer对象和准备工作等操作
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            player = new MediaPlayer();
            player.setDataSource("http://192.168.116.132:8080/test.mp4");
            player.setDisplay(holder);
            player.prepareAsync(); 
            player.setOnPreparedListener(new OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    player.start();
                    //调用MediaPlayer的seekTo()方法从指定位置播放,比如当我们按Home键,下次进入的时候需要从之前的位置播放
                    player.seekTo(lastPosition);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //当SurfaceView改变的时候调用
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }
});

运行结果: 
这里写图片描述

9.2. VideoView

VideoView也是播放视频的一个控件,这个类继承了SurfaceView。 
界面布局:

<RelativeLayout 
    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"
    tools:context=".MainActivity" >
    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

Activity中利用VideoView播放视频:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        VideoView vv = (VideoView) findViewById(R.id.videoView);
        vv.setVideoPath("http://192.168.116.132:8080/test.mp4");
        vv.start();     
    }
}

运行结果: 
这里写图片描述

9.3. Vitamio框架

由于系统原生的SurfaceView和VideoView对视频播放功能的不完整,所以现实开发中,开发人员很少使用这两个控件。开发中一般都会使用开源框架来实现播放视频的功能,因为开源框架支持播放视频的格式比较多,而且功能比较强大。Vitamio是一款Android与iOS平台上的全能多媒体开发框架,它的官网地址是https://www.vitamio.org。下图是Vitamio的官网首页: 
这里写图片描述

下面使用Vitamio框架播放视频。Vitamio开源框架是以类库的方式提供给开发者的,实际上就是一个安卓工程,我们将Vitamio类库导入到Eclipse,右击查看项目属性可以发现它是一个类库,如下图: 
这里写图片描述

那么我们的项目如何引入Vitamio类库呢?右击项目选择Properties,如下图: 
这里写图片描述

选择Android,然后点击Add按钮,如下图: 
这里写图片描述

选择vitamio_lib,然后点击OK按钮,如下图: 
这里写图片描述

这时候,类库就引入到了我们自己的项目中,如下图: 
这里写图片描述

这时候项目中就可以使用vitamio中的api了。 
首先我们在布局中使用vitamio提供的播放视频的控件:

<RelativeLayout 
    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"
    tools:context=".MainActivity" >
    <io.vov.vitamio.widget.VideoView
        android:id="@+id/vv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

在MainActivity中使用VideoView播放视频:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //检查类库
        if (!LibsChecker.checkVitamioLibs(this)) {
            return;
        }
        final VideoView vv = (VideoView) findViewById(R.id.vv);
        //设置播放文件资源路径
        vv.setVideoPath("http://192.168.116.132:8080/test.mp4"); 
        vv.setOnPreparedListener(new OnPreparedListener() {
            //设置准备监听,当准备好了之后才可以播放
            @Override
            public void onPrepared(MediaPlayer mp) {
                //开始播放
                vv.start(); 
            }
        });
        //设置控制器
        vv.setMediaController(new MediaController(getApplicationContext()));
    }
}

注意,最后还需要在清单文件中配置一个Activity:

<activity android:name="io.vov.vitamio.activity.InitActivity"></activity>

运行结果: 
这里写图片描述

10. 调用系统相机

10.1. 照相

我们可以隐式的开启系统提供的照相Activity,通过系统照相功能进行拍照。

//创建意图对象
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//创建照片保存的File对象
File file = new File(Environment.getExternalStorageDirectory(),"paizhao.png"); 
//设置文件保存的Uri
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); 
//开启Activity
startActivityForResult(intent, 0);

10.2. 录像

Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
File file = new File(Environment.getExternalStorageDirectory(),"luxiang.3gp"); 
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); 
//设置视频的质量
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
startActivityForResult(intent, 0);

转载于:https://my.oschina.net/u/2884845/blog/751477

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值