封面来源未知,请多包涵
图形图像处理技术
画笔和画布
在安卓中,画笔用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(); 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); 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); 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); 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卡中的文件,因此我们需要设置一系列权限!!
权限设置:
- 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版本的情况下,这个属性将很关键,一定要加上。
- 然后我们需要到虚拟机中,找到我们对应的该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类,在这个类中提供了一些矢量绘图的方法,通过这些方法可以创建各种路径。
路径的体现形式:
针对这两种体现形式,我们可以使有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(); path.addCircle(150,100,50,Path.Direction.CW); paint.setTextSize(26); 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); 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种补间动画:
- 透明度渐变动画
<alpha>
- 旋转动画
<rotate>
- 缩放动画
<scale>
- 平移动画
<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.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播放的音频格式:mp3(常用)、ogg、3gp、wav
什么是MediaPlayer?
Android提供的用来控制音频/视频文件或流播放的类。
使用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); 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(); } }); } }
|
总结
制作简易音乐播放器
在本次案例中,我们将使用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.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播放音频步骤:
- 创建SoundPool对象;
- 使用
load()
加载音频;
- 使用
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); 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) .build(); 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播放视频的步骤:
但一般情况下,我们使用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.requestFocus(); videoView.start(); videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { Toast.makeText(MainActivity.this, "视频播放完毕", Toast.LENGTH_SHORT).show(); } }); } }
|
播放视频的方法:VideoView和MediaPlayer、SurfaceView。
使用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 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.setDisplay(surfaceHolder); 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);
if (!android.os.Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { Toast.makeText(MainActivity.this, "请安装SD卡", Toast.LENGTH_SHORT).show(); } SurfaceView surfaceView = findViewById(R.id.surfaceView); final SurfaceHolder holder = surfaceView.getHolder(); 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); Camera.Parameters parameters = camera.getParameters();
parameters.setPictureFormat(PixelFormat.JPEG); 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; 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); 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 ...> <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版本或不同测试设备在部分步骤上可能存在些许差异,请按情况自行处理。善用搜索引擎!
- 首先我们需要用数据线将我们的手机与电脑连接起来。
- 在Android Studio中进行如下操作:
- 完成第二步后我们可以看到在AS界面的右边出现一个名为“Troubleshoot device connection issues”的窗口,你只需要按照步骤完成即可。看不懂英文?好吧,我给你简述一下。
- 首先,AS会检测你的电脑是否已与手机连接成功,如果连接成功,点击窗口右下角的“Next”,进入第二步;
- 在第二步中你需要打开手机的“开发者选项”(不同的手机打开方式可能存在差异,可以按照窗口英文导航进行操作,也可以使用搜索引擎查询),然后进入“开发者选项”,打开“USB调试”、“USB安装”、“USB调试(安全设置)”等选项,然后点击窗口右下角的“Next”,进入最后一步;
- 如果你成功完成前两步,在这一步中基本不需要配置。到此,你已经成功连接了AS与你的手机,可以开始进行真机测试了。
- 运行案例,在手机上完成安装,进入测试。
声明:不同的手机连接的方式不同,本笔记仅做参考,遇上无法解决的问题,请善用各种搜索引擎。
录制视频
控制摄像头录制视频步骤:
- 创建MediaRecorder对象,使用
setAudioSource()
设置声音来源;
- 使用
setOutputFormat()
设置输出文件的格式;
- 设置音频和视频的参数;
- 使用
setOutputFile()
设置保存位置;
- 使用
setPreviewDisplay()
设置显示预览的SurfaceView;
- 使用
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
| 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);
if (!android.os.Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { Toast.makeText(MainActivity.this, "请安装SD卡", Toast.LENGTH_SHORT).show(); } surfaceView = findViewById(R.id.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.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP); mediaRecorder.setVideoEncodingBitRate(1920*1080); mediaRecorder.setOutputFile(videoFile.getAbsolutePath()); 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() { super.onResume(); camera = Camera.open(); }
@Override protected void onPause() { 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图像处理和多媒体完