封面来源未知,请多包涵

图形图像处理技术

画笔和画布

在安卓中,画笔用Paint类来表示,画布用Canvas类来表示。

画笔主要用于指定颜色、透明度、笔触的粗细、填充样式等。

绘图的步骤:

绘图步骤

在帧布局管理器上绘制一个橙色矩形

activity_main.xml:

1
2
3
4
<FrameLayout 
    android:id="@+id/framelayout">

</FrameLayout>

自定义MyView类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();  //定义画笔
        //安卓默认颜色是透明的 0x后紧跟的两个FF表示设置透明度为不透明
        paint.setColor(0xFFFF6600); //设置画笔颜色
        paint.setStyle(Paint.Style.FILL);//设置填充方式
        //绘制矩形
        canvas.drawRect(10,10,280,250,paint);
    }
}

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FrameLayout frameLayout = findViewById(R.id.framelayout);
        //将自定义View添加到帧布局管理器中
        frameLayout.addView(new MyView(this));
    }
}

注意:安卓默认的画笔笔触颜色是透明,我们在设置画笔笔触颜色时需要设置其透明度。

绘制几何图形

在安卓中,Canvas类提供了丰富的方法用于绘制几何图形。

绘制几何图形

实现在屏幕上绘制Android的机器人

activity_main.xml:

1
2
3
<FrameLayout 
    android:id="@+id/framelayout">
</FrameLayout>

自定义MyView类,绘制安卓机器人:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制安卓机器人
        Paint paint = new Paint();
        paint.setAntiAlias(true); //采用抗锯齿功能
        paint.setColor(0xFFA4C739); //设置画笔颜色为绿色

        /*********绘制机器人的头********/
        RectF rectF = new RectF(10,10,100,100);//定义外轮廓矩形
        rectF.offset(90,20);
        //绘制头的圆弧
        canvas.drawArc(rectF,-10,-160,false,paint);
        //绘制眼睛
        paint.setColor(0xFFFFFFFF);//设置画笔颜色
        canvas.drawCircle(165,53,4,paint);//绘制一个眼睛的圆
        canvas.drawCircle(125,53,4,paint);//绘制另一个眼睛的圆
        //绘制天线
        paint.setColor(0xFFA4C739);//设置画笔颜色
        paint.setStrokeWidth(2);//设置笔触宽度
        canvas.drawLine(110,15,125,35,paint);//绘制天线
        canvas.drawLine(180,15,165,35,paint);//绘制另一条天线

        /*********绘制机器人的身体********/
        canvas.drawRect(100,75,190,150,paint);//绘制矩形
        //绘制圆角矩形(身体)
        RectF rectF_body = new RectF(100, 140, 190, 160);
        canvas.drawRoundRect(rectF_body,10,10,paint);
        //绘制胳膊
        RectF rectF_arm = new RectF(75, 75, 95, 140);
        canvas.drawRoundRect(rectF_arm,10,10,paint);
        rectF_arm.offset(120,0);
        canvas.drawRoundRect(rectF_arm,10,10,paint);
        //绘制腿
        RectF rectF_leg = new RectF(115, 150, 135, 200);
        canvas.drawRoundRect(rectF_leg,10,10,paint);
        rectF_leg.offset(40,0);
        canvas.drawRoundRect(rectF_leg,10,10,paint);
    }
}

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FrameLayout frameLayout = findViewById(R.id.framelayout);
        frameLayout.addView(new MyView(this));
    }
}

绘制文本

我们可以使用Canvas类提供的drawText()方法来实现。

绘制对白文字

由于无背景图片,因此案例运行后只能看到两段文字,我们主要理解绘制文本的方法即可。

activity_main.xml:

1
2
3
<FrameLayout 
    android:id="@+id/framelayout">
</FrameLayout>

自定义MyView类,绘制文本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
       /********绘制对白********/
        Paint paint = new Paint();//创建画笔
        paint.setColor(0xFF000000);//设置笔触为黑色
        paint.setAntiAlias(true); //采用抗锯齿功能
        paint.setTextAlign(Paint.Align.LEFT);   //设置文字对齐方式
        paint.setTextSize(30);//设置文字大小,单位sp
        //输出第一段对白
        canvas.drawText("你想和我一起",175,160,paint);
        canvas.drawText("去嗨皮吗",175,185,paint);
        //输出第二段对白
        canvas.drawText("给爷爬!",245,45,paint);
    }
}

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FrameLayout frameLayout = findViewById(R.id.framelayout);
        //将自定义View添加到帧布局管理器中
        frameLayout.addView(new MyView(this));
    }
}

绘制图片

我们需要绘制图片就需要先创建位图对象,在安卓中位图对象用Bitmap表示。

创建位图对象可以通过BitmapFactory类或Bitmap类进行创建。

BitmapFactory类或Bitmap类创建位图方式:

BitmapFactory类 Bitmap类
1. 通过图片文件创建 1. 挖取一块图像创建
2. 通过输入流创建 2. 将源位图缩放生成
3. 使用颜色数组创建

BitmapFactory类为创建位图提供的方法:

decodeFile():通过路径创建

decodeResource():通过资源ID创建

decodeStream():通过输入流创建

使用BitmapFactory.decodeFile()创建位图

注意:本次案例是否能成功运行与SDK版本有巨大的联系!!

我先贴出我的SDK版本,你可以前往Gradle Scripts中查看对应Module的build.gradle文件中的SDK版本:

1
2
minSdkVersion 21
targetSdkVersion 29

准备: 我们需要准备一张图片,命名为iriya.png(为什么这个名?别问,老二次元了),图片及名字可以自己定义,但是记得修改涉及到图片名的代码。准备好以后我们需要开启安卓虚拟机,然后将这张图片上传至虚拟机的SDcard中。

上传方式:

View > Tool Windows > Device File Explorer > sdcard ,右击,Upload,选择你准备的图片上传即可。

注意:一定要先启动虚拟机,不然在Device File Explorer中看不到虚拟机中的文件。

检查是否上传成功:Device File Explorer > storage > emulated > 0,在这个目录下看看有没有你上传的图片。

代码编写:

activity_main.xml:

1
2
3
<FrameLayout ...
    android:id="@+id/framelayout">
</FrameLayout>

MainActivity.java:

1
2
3
4
5
6
7
8
9
10
public class MainActivity extends AppCompatActivity {
    public static final int EXTERNAL_STORAGE_REQ_CODE = 10;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FrameLayout frameLayout = findViewById(R.id.framelayout);
        frameLayout.addView(new MyView(this));
    }
}

自定义MyView类,创建位图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //获取文件路径
        String path = Environment.getExternalStorageDirectory()+"/iriya.png";
        //创建位图对象
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        Paint paint = new Paint();
        //绘制图片
        canvas.drawBitmap(bitmap,0,0,paint);
    }
}

注意:由于需要读取虚拟机SD卡中的文件,因此我们需要设置一系列权限!!

权限设置:

  1. AndroidManifest.xml:
1
2
3
4
5
6
<manifest ...>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <application android:requestLegacyExternalStorage="true"  ...>
        ...
    </application>
</manifest>

android:requestLegacyExternalStorage="true"和我一样的SDK版本的情况下,这个属性将很关键,一定要加上。

  1. 然后我们需要到虚拟机中,找到我们对应的该Module对应的应用,并设置应用的权限:允许访问储存空间

最后,我们运行即可,查看界面是否有图片显示。


在上述权限设置无用的情况下,如果你的SDK版本与我不同,且Android Version >= 6,你可以尝试设置动态访问权限。

在MainActivity.java中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static final int EXTERNAL_STORAGE_REQ_CODE = 10;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        int permission = 
            ActivityCompat.checkSelfPermission(this,
                                               Manifest.permission.READ_EXTERNAL_STORAGE);
        if (permission != PackageManager.PERMISSION_GRANTED) {
            //请求权限
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                    EXTERNAL_STORAGE_REQ_CODE);
        }
        ...
    }

使用Bitmap类创建位图对象

通过使用BitmapFactory类我们可以创建一整张位图,那如果我们仅需要从图像上取一小块区域呢?这时我们可以通过Bitmap类创建一个对象。

Bitmap类为创建位图提供的方法:

createBitmap():根据重载形式创建对应的Bitmap对象。可用于挖去图片一小块区域。

compress():压缩Bitmap对象并保存到文件输出流

createScaledBitmap():将源位图缩放并创建新的Bitmap对象。

我们可以继续在本案例中的MyView类中的ondraw()方法中编写:

如果我们想挖取图片一小部分,那么可以使用:

1
2
3
4
5
6
7
8
protected void onDraw(Canvas canvas) {
        ...
        /*******挖去一小块区域******/
        //创建新的位图对象
        Bitmap bitmap1 = Bitmap.createBitmap(bitmap, 23, 89, 150, 168);
        //绘制图片
        canvas.drawBitmap(bitmap1,270,50,paint);
    }

由于我选取的图片太大,我想将图片缩放,那么可以:

1
2
3
4
5
6
7
8
protected void onDraw(Canvas canvas) {
        ...
        /*******缩放图片******/
        //创建新的位图对象
        Bitmap bitmap1 = Bitmap.createScaledBitmap(bitmap,500,500,true);
        //绘制图片
        canvas.drawBitmap(bitmap1,270,50,paint);
    }

绘制路径

创建路径使用Path类,在这个类中提供了一些矢量绘图的方法,通过这些方法可以创建各种路径。

Path类

路径的体现形式:

  • 绘制路径
  • 绘制绕路径文字

针对这两种体现形式,我们可以使有Canvas类的drawPath()方法或drawTextOnPath()方法来实现。

绘制路径

activity_main.xml:

1
2
3
<FrameLayout ... android:id="@+id/framelayout">

</FrameLayout>

MainActivity.java:

1
2
3
4
5
6
7
8
9
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FrameLayout frameLayout = findViewById(R.id.framelayout);
        frameLayout.addView(new MyView(this));
    }
}

自定义MyView类,绘制路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(0xFF0000FF);//设置笔触为蓝色
        paint.setStyle(Paint.Style.STROKE);//设置填充样式为描边
        Path path = new Path();
        //创建圆形路径 CW:顺时针绘制;CCW:逆时针绘制
        path.addCircle(150,100,50,Path.Direction.CW);
        //canvas.drawPath(path,paint); //绘制路径
        //绘制绕路径的文本
        paint.setTextSize(26); //设置文字大小,单位默认sp
        String string = "活着就是为了改变世界";
        canvas.drawTextOnPath(string,path,0,0,paint);
    }
}

总结:

路径绘制

逐帧动画

创建逐帧动画的基本步骤:

创建逐帧动画

单击屏幕播放或停止逐帧动画

准备: 我们首先需要准备逐帧动画资源图片文件,可以在网上执行搜索或联系本文作者获取。我们准备共6张逐帧动画资源图片文件,分别命名为image1~image6,将它们放置于res/drawable目录下。

res/drawable目录下创建动画资源文件fairy.xml:

1
2
3
4
5
6
7
8
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/image1" android:duration="60"/>
    <item android:drawable="@drawable/image2" android:duration="60"/>
    <item android:drawable="@drawable/image3" android:duration="60"/>
    <item android:drawable="@drawable/image4" android:duration="60"/>
    <item android:drawable="@drawable/image5" android:duration="60"/>
    <item android:drawable="@drawable/image6" android:duration="60"/>
</animation-list>

在布局资源文件activity_main.xml文件中使用动画资源文件:

1
2
3
4
5
6
<LinearLayout ...
    android:orientation="vertical"
    android:id="@+id/linearlayout"
    android:background="@drawable/fairy"
    tools:context=".MainActivity">
</LinearLayout>

在MainActivity.java中编写播放动画的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MainActivity extends AppCompatActivity {
    private boolean flag = true;//记录播放状态
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout linearLayout = findViewById(R.id.linearlayout);
        //获取动画Drawable资源
        final AnimationDrawable animationDrawable = (AnimationDrawable) linearLayout.getBackground();
        linearLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (flag){
                    animationDrawable.start();  //播放动画
                    flag = false;
                }else {
                    animationDrawable.stop();
                    flag = true;
                }
            }
        });
    }
}

补间动画

定义: 补间动画指的是做FLASH动画时,在两个关键帧中间需要做“补间动画”,才能实现图画的运动;插入补间动画后两个关键帧之间的插补帧是由计算机自动运算而得到的。

安卓为我们提供了4种补间动画:

  1. 透明度渐变动画<alpha>
  2. 旋转动画<rotate>
  3. 缩放动画<scale>
  4. 平移动画<translate>

实现4中补间动画的基本使用

准备: 需要一张图片资源文件,命名为image1,放置于res/drawable目录下。

在res目录下创建anim目录,并编写4种补间动画文件:

alpha.xml:

1
2
3
4
5
6
7
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!--从完全不透明到透明-->
    <alpha
        android:duration="2000"
        android:fromAlpha="0"
        android:toAlpha="1" />
</set>

rotate.xml:

1
2
3
4
5
6
7
8
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <rotate
        android:duration="2000"
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toDegrees="360" />
</set>

scale.xml:

1
2
3
4
5
6
7
8
9
10
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
        android:duration="2000"
        android:fromXScale="1"
        android:fromYScale="1"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="2"
        android:toYScale="2" />
</set>

translate.xml: 编写参考链接

1
2
3
4
5
6
7
8
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="2000"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="300"
        android:toYDelta="300" />
</set>

在布局资源文件activity_main.xml中设置一个图像视图组件用于显示图片:

1
2
3
4
5
6
7
8
9
10
<RelativeLayout ...>
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/image"
        android:src="@drawable/image1"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"/>
</RelativeLayout>

在MainActivity.java文件中创建动画对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final ImageView imageView = findViewById(R.id.image);
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //创建透明度渐变动画对象
                //Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.alpha);
                //创建旋转动画对象
                //Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.rotate);
                //创建缩放动画对象
                //Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.scale);
                //创建平移动画对象
                Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.translate);
                imageView.startAnimation(animation); //开启动画
            }
        });
    }
}

实现淡入、淡出补间动画

准备: 需要4张图片资源文件,分别命名为image1~image4,放置于res/drawable目录下。

在res目录下创建anim目录,并编写补间动画文件:

anim_alpha_in.xml:

1
2
3
4
5
6
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:fromAlpha="0"
        android:toAlpha="1"
        android:duration="2000"/>
</set>

anim_alpha_out.xml:

1
2
3
4
5
6
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:fromAlpha="1"
        android:toAlpha="0"
        android:duration="2000"/>
</set>

在布局资源文件activity_main.xml中设置一个ViewFlipper组件用于显示切换图片:

1
2
3
4
5
<ViewFlipper
             android:id="@+id/flipper"
             android:layout_width="match_parent"
             android:layout_height="match_parent">
</ViewFlipper>

在MainActivity.java文件中创建动画对象,并设置切换手势:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class MainActivity extends AppCompatActivity implements GestureDetector.OnGestureListener{
    ViewFlipper viewFlipper;
    GestureDetector detector;//定义手势检测器
    final int distance = 50;//定义手势动作两点之间最小距离
    //定义图片数组
    private int[] images = new int[]{R.drawable.image1, R.drawable.image2, R.drawable.image3, R.drawable.image4};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        detector = new GestureDetector(MainActivity.this,this);
        viewFlipper = findViewById(R.id.flipper);
        for (int i = 0;i<images.length;i++){
            ImageView imageView = new ImageView(this);
            imageView.setImageResource(images[i]);
            viewFlipper.addView(imageView);
        }
        //定义并初始化动画数组
        Animation[] animations = new Animation[2];
        animations[0] = AnimationUtils.loadAnimation(this,R.anim.anim_alpha_in);
        animations[1] = AnimationUtils.loadAnimation(this,R.anim.anim_alpha_out);
        //指定切换的动画效果
        viewFlipper.setInAnimation(animations[0]);
        viewFlipper.setOutAnimation(animations[1]);
    }


    @Override
    public boolean onDown(MotionEvent e) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {

    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        if (e1.getX() - e2.getX() > distance){
            viewFlipper.showPrevious();
            return true;
        }else if (e2.getX() - e1.getX() > distance){
            viewFlipper.showNext();
            return true;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return detector.onTouchEvent(event);    //这里重写后一定要记得修改
    }
}

多媒体应用开发

音频——使用MediaPlayer播放

使用MediaPlayer播放的音频格式:mp3(常用)、ogg、3gp、wav

什么是MediaPlayer?

Android提供的用来控制音频/视频文件或流播放的

使用MediaPlayer类播放音频的步骤:

MediaPlayer使用步骤

使用MediaPlayer播放音频文件

准备: 需要一段mp3音频文件,命名为music.mp3。在res下创建raw目录,将准备的音频文件放置于raw目录下。

在activity_main.xml中设计播放、暂停、停止按钮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<LinearLayout ...
    android:orientation="horizontal"
    android:gravity="center_horizontal">
    <Button
        android:id="@+id/btn_play"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="播放"/>
    <Button
        android:id="@+id/btn_stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="停止"/>
    <Button
        android:id="@+id/btn_pause"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="暂停"/>
</LinearLayout>

在MainActivity.java中创建MediaPlayer对象,并装载音频文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //创建MediaPlayer对象,并装载了播放的音频
        final MediaPlayer mediaPlayer = MediaPlayer.create(this,R.raw.music);
        Button button_paly = findViewById(R.id.btn_play);
        Button button_stop = findViewById(R.id.btn_stop);
        Button button_pause = findViewById(R.id.btn_pause);
        button_paly.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mediaPlayer.start();//开始播放音频
            }
        });
        button_stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mediaPlayer.stop();//停止播放
            }
        });
        button_pause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mediaPlayer.pause();//暂停播放
            }
        });
    }
}

总结

MediaPlayerSummary

制作简易音乐播放器

在本次案例中,我们将使用MediaPlayer制作一个简易音乐播放器。我们需要从模拟机SD卡中读取音乐文件,然后点击播放按钮进行播放。

**准备:**我们需要三张表示播放、暂停、停止的图标文件资源,分别命名为play、pause和stop,将它们放置于res/drawable目录下。我们还需要准备一段音频文件,命名为music.mp3,将这段音频文件上传至模拟器SD卡上。

由于我们需要上传文件至SD卡并读取SD卡的文件,所以我们需要设置权限。文件上传至模拟器和权限设置参考 图形图像处理技术中的绘制图片 中的上传和配置。

在布局资源文件activity_main.xml中设置按钮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:gravity="center_horizontal"
    tools:context=".MainActivity">
    <ImageButton
        android:id="@+id/btn_play"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="30dp"
        android:src="@drawable/play"/>
    <ImageButton
        android:id="@+id/btn_stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/stop"/>

</LinearLayout>

在MainActivity.java中编写音乐播放器逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class MainActivity extends AppCompatActivity {
    private MediaPlayer mediaPlayer;
    private File file;
    private boolean isPause = false;//定义是否暂停
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().setFlags(WindowManager.
                             LayoutParams.FLAG_FULLSCREEN, 
                             WindowManager.
                             LayoutParams.FLAG_FULLSCREEN);
        //获取要播放的音频文件
        file = new File("/sdcard/music.mp3");
        if (file.exists()){
            //创建MediaPlayer对象,并装载音频文件
            mediaPlayer = MediaPlayer.create(this, Uri.parse(file.getAbsolutePath()));
        }else {
            Toast.makeText(MainActivity.this, 
                           "要播放的音频文件不存在!", Toast.LENGTH_SHORT).show();
            return;
        }
        final ImageButton btn_play = findViewById(R.id.btn_play);
        final ImageButton btn_stop = findViewById(R.id.btn_stop);

        btn_play.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mediaPlayer.isPlaying() && !isPause){
                    mediaPlayer.pause();
                    isPause = true;
                    ((ImageButton)v).setImageDrawable(getResources()
                                                      .getDrawable(R.drawable.play,null));
                }else {
                    mediaPlayer.start();//继续播放
                    isPause = false;
                    ((ImageButton)v).setImageDrawable(getResources()
                                                      .getDrawable(R.drawable.pause,null));
                }
            }
        });

        btn_stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mediaPlayer.stop();
                btn_play.setImageDrawable(getResources().getDrawable(R.drawable.play,null));
            }
        });
        //事件完成监听器
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                play();
            }
        });
    }

    private void play(){
        mediaPlayer.reset();//重置播放器
        try {
            mediaPlayer.setDataSource(file.getAbsolutePath());
            mediaPlayer.prepare();  //预加载音频
            mediaPlayer.start(); //播放音频
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //释放资源
    @Override
    protected void onDestroy() {
        if (mediaPlayer.isPlaying()){
            mediaPlayer.stop();
        }
        mediaPlayer.release();
        super.onDestroy();
    }
}

音频——使用SoundPool播放

MediaPlayer和SoundPool区别:

MediaPlayer SoundPool
延迟长,且占用资源多 延迟短,且占用资源少
不支持同时播放多个音频 支持多个音频同时播放

但SoundPool只能播放短促的音频,否则可能无法播放完毕。

SoundPool(音效池):管理多个短促的音频。主要用于播放按键音,消息提示音。


使用SoundPool播放音频步骤:

  1. 创建SoundPool对象;
  2. 使用load()加载音频;
  3. 使用play()播放音频。

点击列表项播放铃声

准备: 我们需要准备5段铃声,时间控制在10s内,分别命名为bell_1~bell_5,然后在res目录下新建raw目录,将这些铃声放置于raw目录下。

在res/values目录下创建bellname.xml数组资源文件:

1
2
3
4
5
6
7
8
9
<resources>
    <array name="bellname">
        <item>铃声1</item>
        <item>铃声2</item>
        <item>铃声3</item>
        <item>铃声4</item>
        <item>铃声5</item>
    </array>
</resources>

在布局资源文件activity_main.xml中编写列表视图:

1
2
3
4
5
6
7
<RelativeLayout ...>
    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:entries="@array/bellname"/>
</RelativeLayout>

在MainActivity.java中加载并播放音频:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ListView listView = findViewById(R.id.listView);
        /*****创建SoundPool对象,并且设置音频属性******/
        AudioAttributes audioAttributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)  //设置音效使用场景
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) //设置音效类型
                .build();
        final SoundPool soundPool = new SoundPool.Builder()
                .setAudioAttributes(audioAttributes)    //设置音效池属性
                .setMaxStreams(10)  //设置最多可容纳10个音频流
                .build();
        /*******将要播放的音频保存到HashMap对象中********/
        final HashMap<Integer,Integer> soundMap = new HashMap<Integer,Integer>();
        soundMap.put(0,soundPool.load(this,R.raw.bell_1,1));
        soundMap.put(1,soundPool.load(this,R.raw.bell_2,1));
        soundMap.put(2,soundPool.load(this,R.raw.bell_3,1));
        soundMap.put(3,soundPool.load(this,R.raw.bell_4,1));
        soundMap.put(4,soundPool.load(this,R.raw.bell_5,1));
        /*******************播放音频********************/
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                //单击选项播放音频
                soundPool.play(soundMap.get(position),1,1,0,0,1);
            }
        });
    }
}

视频——使用VideoView播放

Android中支持的常用视频格式:mp4、3gp

使用VideoView播放视频的步骤:

VideoView播放视频的步骤

但一般情况下,我们使用Android给我们提供的MediaController类,使用这个类可以给我们的视频设置一个播放控制条,这样我们就可以不用编写控制视频进度、暂停、停止、快进的代码了。

使用VideoView播放模拟器SD卡中的视频

准备: 我们需要准备一段时间长度适宜的视频文件,我将其命名为video_ice.mp4(你可以自由命名,但是记得修改与视频文件名相关的代码),然后开启模拟器,让其上传到模拟器的SD卡上。

由于我们需要上传文件至SD卡并读取SD卡的文件,所以我们需要设置权限。文件上传至模拟器和权限设置参考 图形图像处理技术中的绘制图片 中的上传和配置。

由于本案例中的AndroidManifest.xml权限配置与以前的权限配置存在些许差异,因此在此给出此文件内容。

在AndroidManifest.xml文件中设置权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission
        android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
        tools:ignore="ProtectedPermissions" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:requestLegacyExternalStorage="true"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="fullSensor">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

在布局资源文件activity_main.xml中使用VideoView组件:

1
2
3
4
5
6
<RelativeLayout ...>
    <VideoView
        android:id="@+id/video"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

在MainActivity.java文件中加载并播放视频:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        VideoView videoView = findViewById(R.id.video);
        /*************加载要播放的视频***************/
        //获取文件对象
        File file = new File(Environment.getExternalStorageDirectory()+"/video_ice.mp4");
        if (file.exists()){
            videoView.setVideoPath(file.getAbsolutePath());
        }else {
            Toast.makeText(MainActivity.this, "要播放的视频不存在!", Toast.LENGTH_SHORT).show();
        }
        /****************控制视频的播放****************/
        android.widget.MediaController mc 
            = new android.widget.MediaController(MainActivity.this);
        videoView.setMediaController(mc);//让VideoView和MediaController关联
        videoView.requestFocus();//让VideoView获得焦点
        videoView.start();//开始播放视频
        videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                Toast.makeText(MainActivity.this, "视频播放完毕", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

视频——使用MediaPlayer和SurfaceView播放

播放视频的方法:VideoView和MediaPlayer、SurfaceView。

使用MediaPlayer类播放视频:

使用MediaPlayer类播放视频

使用MediaPlayer和SurfaceView制作简易视频播放器

案例描述: 使用MediaPlayer和SurfaceView制作简易视频播放器,然后播放模拟器SD卡内的视频。

准备: 我们需要准备一段时间长度适宜的视频文件,我将其命名为video_ice.mp4(你可以自由命名,但是记得修改与视频文件名相关的代码),然后开启模拟器,让其上传到模拟器的SD卡上。除了必要的视频文件之外,我们还需要准备三张图标资源文件,用于设置播放、暂停和停止的图片按钮,将这三张图片命名为video_play、video_pause和video_stop,然后将它们放置于res/drawable目录下。

同样,由于需要读取SD卡内的视频,我们需要上传视频和进行权限设置。具体步骤可以参考 多媒体应用开发中的 使用VideoView播放 的设置。两者的权限设置是一样的,在此不再累述。

在布局资源文件activity_main.xml中使用VideoView组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_weight="10"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal">
        <!--播放按钮-->
        <ImageButton
            android:id="@+id/play"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:src="@drawable/video_play"/>
        <!--暂停按钮-->
        <ImageButton
            android:id="@+id/pause"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:src="@drawable/video_pause"/>
        <!--停止按钮-->
        <ImageButton
            android:id="@+id/stop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:src="@drawable/video_stop"/>
    </LinearLayout>
</LinearLayout>

在MainActivity.java文件中加载并播放视频:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class MainActivity extends AppCompatActivity {

    private ImageButton play, pause, stop;
    private boolean noplay = true;//定义播放状态 当前表示未播放
    MediaPlayer mediaPlayer;
    SurfaceHolder surfaceHolder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                             WindowManager.LayoutParams.FLAG_FULLSCREEN);
        /************初始化SurfaceView和MediaPlayer********************/
        SurfaceView surfaceView = findViewById(R.id.surfaceView);
        surfaceHolder = surfaceView.getHolder();
        mediaPlayer = new MediaPlayer();
        //设置多媒体类型
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                Toast.makeText(MainActivity.this, "视频播放完毕", Toast.LENGTH_SHORT).show();
            }
        });
        /****************控制视频的播放、暂停和停止*********************/
        play = findViewById(R.id.play);
        pause = findViewById(R.id.pause);
        stop = findViewById(R.id.stop);

        play.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (noplay){
                    play(); //播放视频
                    noplay = false;//表示视频处于播放状态
                }else {
                    mediaPlayer.start();//继续播放视频
                }
            }
        });

        pause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mediaPlayer.isPlaying()){
                    mediaPlayer.pause();    //暂停播放
                }
            }
        });

        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mediaPlayer.isPlaying()){
                    mediaPlayer.stop(); //停止播放
                    noplay = true;  //表示视频处于未播放状态
                }
            }
        });
    }

    //播放视频的方法
    public void play(){
        mediaPlayer.reset();//重置MediaPlayer对象
        mediaPlayer.setDisplay(surfaceHolder);//把视频画面输出到SurfaceView
        try {
            mediaPlayer
                .setDataSource(Environment.getExternalStorageDirectory()+"/video_ice.mp4");
            mediaPlayer.prepare();//预加载
        } catch (IOException e) {
            e.printStackTrace();
        }
        mediaPlayer.start();//播放视频
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mediaPlayer != null){
            mediaPlayer.isPlaying();
            mediaPlayer.stop(); //停止播放
        }
        mediaPlayer.release(); //释放资源
    }
}

控制摄像头拍照

在Android中提供了一个名为Camera的类,这个类值专门用来处理摄像头相关事件。

控制摄像头拍照的步骤:

控制摄像头拍照步骤

我们将通过一个案例来进行步骤讲解。

实现控制摄像头拍照

准备: 我们需要准备两张图标资源来表示手机拍照和预览,将它们分别命名为take_photos和preview,然后我们需要将它们放置于res/drawable目录下。

**案例描述:**我们在真机上启动安装后,运行应用,然后点击“预览”图片按钮,打开手机摄像头,选择需要拍摄的物体后,点击“拍照”图片按钮实现拍照。

本案例仅仅实现了基本的拍照逻辑,使用了大量的过时的类与方法,因此本案例仅供参考,但本案例已经过测试,能够在Android 10的手机上(作者使用的是 MIX 2S 进行测试)进行运行。

==注意:本次案例需要用到真机测试和权限设置。==我会在案例中一步步给出。

在布局资源文件activity_main.xml中完成基本界面布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<FrameLayout ...>
   <SurfaceView
       android:id="@+id/surfaceView"
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>
    <ImageButton
        android:id="@+id/takephoto"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right|top"
        android:layout_marginTop="100dp"
        android:src="@drawable/take_photos"/>
    <ImageButton
        android:id="@+id/preview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right|bottom"
        android:layout_marginBottom="100dp"
        android:src="@drawable/preview"/>
</FrameLayout>

在MainActivity.java文件中实现拍照逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
public class MainActivity extends AppCompatActivity {
    private Camera camera; //定义一个摄像头对象
    private boolean isPreview = false;//是否为预览状态,当前表示非预览状态

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                             WindowManager.LayoutParams.FLAG_FULLSCREEN);

        //判断手机是否安装SD卡
        if (!android.os.Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            //提示安装SD卡
            Toast.makeText(MainActivity.this, "请安装SD卡",
                           Toast.LENGTH_SHORT).show();
        }
        /*****************打开摄像头并预览****************/
        SurfaceView surfaceView = findViewById(R.id.surfaceView);
        final SurfaceHolder holder = surfaceView.getHolder(); //获取SurfaceHolder
        //设置SurfaceView自己不维护缓冲
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);    
        ImageButton preview = findViewById(R.id.preview);
        preview.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!isPreview) {
                    camera = Camera.open();
                    isPreview = true;
                }
                try {
                    camera.setPreviewDisplay(holder);   //设置用于显示预览的SurfaceView
                    Camera.Parameters parameters = camera.getParameters();//获取摄像头参数

                    parameters.setPictureFormat(PixelFormat.JPEG);  //设置图片为JPG图片
                    parameters.set("jpeg-quality", 80); //设置图片质量

                    camera.setParameters(parameters); //重新设置摄像头参数
                    camera.startPreview();  //开始预览
                    camera.autoFocus(null); //设置自动对焦
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

        ImageButton takePhotos = findViewById(R.id.takephoto);
        takePhotos.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //实现拍照功能
                if (camera != null) {
                    camera.takePicture(null, null, jpeg); //进行拍照
                }
            }
        });
    }

    final Camera.PictureCallback jpeg = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            //根据拍照所得数据创建预览
            Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
            camera.stopPreview();   //停止预览
            isPreview = false;  //设置为非预览状态
            //获取SD卡中相片的位置
            File appDir = new File(Environment.getExternalStorageDirectory(), "/DCIM/Camera");
            if (!appDir.exists()) {
                appDir.mkdir(); //如果目录不存在就创建目录
            }
            //设置照片名称
            String fileName = System.currentTimeMillis() + ".jpg";
            File file = new File(appDir, fileName);  //创建文件对象

            try {//保存拍到的图片
                //创建文件输出流对象
                FileOutputStream fos = new FileOutputStream(file);
                //将图片内容压缩为JPEG格式输出
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                //将缓冲区中的数据全部写出到输出流中
                fos.flush();
                //关闭文件输出流对象
                fos.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {   //将照片插入到系统图库
                MediaStore.Images.Media.insertImage(MainActivity.this.getContentResolver(),
                        file.getAbsolutePath(), fileName, null);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }

            //最后通知图库更新
            MainActivity.this.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
                    Uri.parse("file://" + "")));
            Toast.makeText(MainActivity.this, "照片保存至"+file, Toast.LENGTH_SHORT).show();
            resetCamera();//重新预览
        }
    };

    private void resetCamera(){
        if (!isPreview){
            camera.startPreview();  //开启预览
            isPreview = true;
        }
    }
    @Override
    protected void onPause() {
        super.onPause();
        //停止预览并释放摄像头资源
        if (camera != null){
            camera.stopPreview();   //停止预览
            camera.release();   //释放资源
        }
    }
}

由于我们需要打开摄像头进行拍照还需要存放图片文件,因此我们需要进行权限设置。

在AndroidManifest.xml进行权限设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<manifest ...>
    <!--授予程序可以向SD卡中保存文件的权限-->
    <uses-permission
        android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
        tools:ignore="ProtectedPermissions" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!--授予程序使用摄像头的权限-->
    <uses-permission android:name="android.permission.CAMERA" />

    <uses-feature android:name="android.hardware.camera.autofocus" />
    <uses-feature android:name="android.hardware.camera" />

    <application
        android:requestLegacyExternalStorage="true">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="fullSensor">
               ...
        </activity>
    </application>
</manifest>

除了在文件中进行权限设置外,我们在进行真机测试时打开本应用的相关权限(相机、读写手机存储等)。

不同的手机进行应用权限设置的方式不同,在此不再累述。

什么?你说该怎么真机测试?那你接着看吧。

真机测试

作者使用的Android Studio版本为3.6.3,真机测试选用MIX 2S作为测试设备。不同AS版本或不同测试设备在部分步骤上可能存在些许差异,请按情况自行处理。善用搜索引擎!

  1. 首先我们需要用数据线将我们的手机与电脑连接起来。
  2. 在Android Studio中进行如下操作:

真机测试1

  1. 完成第二步后我们可以看到在AS界面的右边出现一个名为“Troubleshoot device connection issues”的窗口,你只需要按照步骤完成即可。看不懂英文?好吧,我给你简述一下。
    • 首先,AS会检测你的电脑是否已与手机连接成功,如果连接成功,点击窗口右下角的“Next”,进入第二步;
    • 在第二步中你需要打开手机的“开发者选项”(不同的手机打开方式可能存在差异,可以按照窗口英文导航进行操作,也可以使用搜索引擎查询),然后进入“开发者选项”,打开“USB调试”、“USB安装”、“USB调试(安全设置)”等选项,然后点击窗口右下角的“Next”,进入最后一步;
    • 如果你成功完成前两步,在这一步中基本不需要配置。到此,你已经成功连接了AS与你的手机,可以开始进行真机测试了。
  2. 运行案例,在手机上完成安装,进入测试。

声明:不同的手机连接的方式不同,本笔记仅做参考,遇上无法解决的问题,请善用各种搜索引擎。

录制视频

控制摄像头录制视频步骤:

  1. 创建MediaRecorder对象,使用setAudioSource()设置声音来源;
  2. 使用setOutputFormat()设置输出文件的格式;
  3. 设置音频和视频的参数;
  4. 使用setOutputFile()设置保存位置;
  5. 使用setPreviewDisplay()设置显示预览的SurfaceView;
  6. 使用prepare()start()开始录制视频,调用stop()方法停止录制,调用release()方法释放资源。

我们将通过一个案例来进行步骤讲解。

实现控制摄像头录制视频

准备: 我们需要准备两张图标资源来表示开始录像和结束录像,将它们分别命名为record和stop,然后我们需要将它们放置于res/drawable目录下。

**案例描述:**我们在真机上启动安装后,运行应用,然后点击“开始录像”图片按钮,打开手机摄像头,进行录像,录像完成后,点击“结束录像”保存录像文件。

本案例仅仅实现了基本的录像逻辑,使用了大量的过时的类与方法,因此本案例仅供参考,但本案例已经过测试,能够在Android 10的手机上(作者使用的是 MIX 2S 进行测试)进行运行。

==注意:本次案例需要用到真机测试和权限设置。==我会在案例中一步步给出。

在布局资源文件activity_main.xml中完成基本界面布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<RelativeLayout  ...>
    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true">
        <ImageButton
            android:id="@+id/record"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="20dp"
            android:src="@drawable/record"/>
        <ImageButton
            android:id="@+id/stop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/stop"/>
    </LinearLayout>
</RelativeLayout>

在MainActivity.java文件中实现录像逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//继承Activity,而不是AppCompatActivity
public class MainActivity extends Activity {
    private ImageButton stop, record;
    private SurfaceView  surfaceView;
    private File videoFile; //视频文件
    MediaRecorder mediaRecorder;
    private android.hardware.Camera camera;
    private Boolean isRecord = false;   //记录录像状态
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        //判断手机是否安装SD卡
        if (!android.os.Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            //提示安装SD卡
            Toast.makeText(MainActivity.this, "请安装SD卡",
                    Toast.LENGTH_SHORT).show();
        }
        /********************设置摄像头参数并开始录制视频***********************/
        surfaceView = findViewById(R.id.surfaceView);
        //设置SurfaceView的分辨率
        surfaceView.getHolder().setFixedSize(1920,1080);
        record = findViewById(R.id.record);
        stop = findViewById(R.id.stop);
        record.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                record();
            }
        });

        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //停止录制,并提示视频保存位置
                if (isRecord){
                    mediaRecorder.stop();   //停止录制
                    mediaRecorder.release();    //释放资源
                    isRecord = false;
                    Toast.makeText(MainActivity.this, "录像保存在"+videoFile, 
                                   Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
    //录制视频的方法
    private void record(){
        //视频保存的文件夹
        File path = new File(Environment.getExternalStorageDirectory()+"/Myvideo/");
        if (!path.exists()){
            path.mkdir();   //文件夹不存在时,创建文件夹
        }
        String fileName = "video.mp4";//视频文件名称
        videoFile = new File(path,fileName); //视频文件
        mediaRecorder = new MediaRecorder();
        camera.setDisplayOrientation(90);   //调整摄像头角度
        camera.unlock();    //解锁摄像头
        mediaRecorder.setCamera(camera);    //使用摄像头
        mediaRecorder.reset();  //重置MediaRecorder
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//使用麦克风获取声音
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//使用摄像头获取图像
        //设置视频输出格式为MP4
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        //设置声音编码格式
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
        //设置视频编码格式
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
        mediaRecorder.setVideoEncodingBitRate(1920*1080);//设置清晰度
        //mediaRecorder.setVideoSize(1920,1080);  //设置视频尺寸
        //mediaRecorder.setVideoFrameRate(10);  //设置每秒10帧
        mediaRecorder.setOutputFile(videoFile.getAbsolutePath());   //设置视频的输出路径
        //设置使用的SurfaceView
        mediaRecorder.setPreviewDisplay(surfaceView.getHolder().getSurface());
        mediaRecorder.setOrientationHint(90);   //调整播放视频的角度
        try {
            mediaRecorder.prepare();    //准备录像
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            mediaRecorder.start();  //开始录像
        } catch (IllegalStateException e) {
            e.printStackTrace();
        }
        Toast.makeText(MainActivity.this, "开始录制", Toast.LENGTH_SHORT).show();
        isRecord = true; //正在录制
    }

    @Override
    protected void onResume() {//当Activity获取焦点时
        super.onResume();
        camera = Camera.open(); //开启摄像头
    }

    @Override
    protected void onPause() {//当Activity失去焦点时
        super.onPause();
        camera.stopPreview();   //停止预览
        camera.release();
    }
}

由于我们需要打开摄像头进行录像还需要存放mp4文件,因此我们需要进行权限设置。

在AndroidManifest.xml进行权限设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<manifest ...>
    <!--授予程序录制声音的权限-->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <!--授予程序使用摄像头的权限-->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission
        android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
        tools:ignore="ProtectedPermissions" />
    <!--授予使用外部储存器的权限-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application ...
        android:requestLegacyExternalStorage="true">
        <activity android:name=".MainActivity">
            ...
        </activity>
    </application>
</manifest>

除了在文件中进行权限设置外,我们在进行真机测试时打开本应用的相关权限(相机、读写手机存储等)。

不同的手机进行应用权限设置的方式不同,在此不再累述。

至于真机测试方法,参考 多媒体应用开发 中的 控制摄像头拍照 ——真机测试


Android图像处理和多媒体完