Android Studio 调用Camera实现拍照功能

今天写个Camera拍照的教程吧。本例子的流程为   首先通过SurfaceView将Camera的实时画面显示在屏幕上,然后通过点击拍照对当前画面进行捕捉,*后将获得的图片保存至本地。

首先创建一个SurfaceHolder实现对SurfaceView的回调,然后重写SurfaceCreate函数,实现对Camera的初始化等一系列工作:代码如下:
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.e(“TAG”,”——surfaceCreated——“);
try {
//这里我优先找后置摄像头,找不到再找前面的
int cameraIndex = findBackOrFrontCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
if (cameraIndex == -1) {
cameraIndex = findBackOrFrontCamera(Camera.CameraInfo.CAMERA_FACING_FRONT);
if (cameraIndex == -1) {
Log.e(“TAG”, “No Camera!”);
currentCameraType = CAMERA_NOTEXIST;
currentCameraIndex = -1;
return;
} else {
currentCameraType = FRONT;
}
} else {
currentCameraType = BACK;
}

//找到想要的摄像头后,就打开
if (mCamera == null) {
mCamera = openCamera(currentCameraType);
}

} catch (Exception e) {
e.printStackTrace();
}
}
本例子中,我首先找到想要打开的摄像头,这里的优先寻找后置摄像头,如果没有找到,再找前置的,代码如下:

/**
* 按要求查找摄像头
*
* @param camera_facing 按要求查找,镜头是前还是后
* @return -1表示找不到
*/
private int findBackOrFrontCamera(int camera_facing) {
int cameraCount = 0;
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
cameraCount = Camera.getNumberOfCameras();
for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
Camera.getCameraInfo(camIdx, cameraInfo);
if (cameraInfo.facing == camera_facing) {
return camIdx;
}
}
return -1;
}
当找到摄像头后,便打开Camera,其实打开Camera可以直接用open(CameraId)函数即可,但我在重新封装了一下,直接帖代码:

/**
* 按照type的类型打开相应的摄像头
*
* @param type 标志当前打开前还是后的摄像头
* @return 返回当前打开摄像机的对象
*/
private Camera openCamera(int type) {
int frontIndex = -1;
int backIndex = -1;
int cameraCount = Camera.getNumberOfCameras();

Camera.CameraInfo info = new Camera.CameraInfo();
for (int cameraIndex = 0; cameraIndex < cameraCount; cameraIndex++) {
Camera.getCameraInfo(cameraIndex, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
frontIndex = cameraIndex;
} else if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
backIndex = cameraIndex;
}
}

currentCameraType = type;
if (type == FRONT && frontIndex != -1) {
currentCameraIndex = frontIndex;
return Camera.open(frontIndex);
} else if (type == BACK && backIndex != -1) {
currentCameraIndex = backIndex;
return Camera.open(backIndex);
}
return null;
}
然后在SurfaceChange对Camera进行一系列初始化(对摄像头初始化,就是设定图片格式,图片尺寸等赋值,要打开摄像头才可以初始化,否则会报错)
/**
* 初始化摄像头
* @param holder
*/
private void initCamera(SurfaceHolder holder){
Log.e(“TAG”,”initCamera”);
if (mPreviewRunning)
mCamera.stopPreview();

Camera.Parameters parameters;
try{
//获取预览的各种分辨率
parameters = mCamera.getParameters();
}catch (Exception e){
e.printStackTrace();
return;
}
//这里我设为480*800的尺寸
parameters.setPreviewSize(480,800);
// 设置照片格式
parameters.setPictureFormat(PixelFormat.JPEG);
//设置图片预览的格式
parameters.setPreviewFormat(PixelFormat.YCbCr_420_SP);
setCameraDisplayOrientation(this,currentCameraIndex,mCamera);
try{
mCamera.setPreviewDisplay(holder);
}catch(Exception e){
if(mCamera != null){
mCamera.release();
mCamera = null;
}
e.printStackTrace();
}
mCamera.startPreview();
mPreviewRunning = true;
}

/**
* 设置旋转角度
* @param activity
* @param cameraId
* @param camera
*/
private void setCameraDisplayOrientation(Activity activity,int cameraId,Camera camera){
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId,info);
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
switch(rotation){
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
result = (info.orientation + degrees) % 360;
result = (360 – result) % 360;
}else{
result = (info.orientation – degrees +360) % 360;
}
camera.setDisplayOrientation(result);
}

当这些工作完成后,便可以对当前摄像头捕捉的画面进行拍照了:
/**
* 实现拍照功能
*/
public void takePhoto(){
Camera.Parameters parameters;
try{
parameters = mCamera.getParameters();
}catch(Exception e){
e.printStackTrace();
return;
}
//获取摄像头支持的各种分辨率,因为摄像头数组不确定是按降序还是升序,这里的逻辑有时不是很好找得到相应的尺寸
//可先确定是按升还是降序排列,再进对对比吧,我这里拢统地找了个,是个不精确的…
List<Camera.Size> list = parameters.getSupportedPictureSizes();
int size = 0;
for (int i =0 ;i < list.size() – 1;i++){
if (list.get(i).width >= 480){
//完美匹配
size = i;
break;
}
else{
//找不到就找个*接近的吧
size = i;
}
}
//设置照片分辨率,注意要在摄像头支持的范围内选择
parameters.setPictureSize(list.get(size).width,list.get(size).height);
//设置照相机参数
mCamera.setParameters(parameters);

//使用takePicture()方法完成拍照
mCamera.autoFocus(new Camera.AutoFocusCallback() {
//自动聚焦完成后拍照
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success && camera != null){
mCamera.takePicture(new ShutterCallback(), null, new Camera.PictureCallback() {
//拍照回调接口
@Override
public void onPictureTaken(byte[] data, Camera camera) {
savePhoto(data);
//停止预览
mCamera.stopPreview();
//重启预览
mCamera.startPreview();
}
});
}
}
});
}

/* *//**
* 快门回调接口,如果不想拍照声音,直接将new ShutterCallback()修改为null即可
*/
private class ShutterCallback implements Camera.ShutterCallback {
@Override
public void onShutter() {
MediaPlayer mPlayer = new MediaPlayer();
mPlayer = MediaPlayer.create(getApplicationContext(), R.raw.shutter);
try{
mPlayer.prepare();
}catch (IllegalStateException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
mPlayer.start();
}
}
这里要注意下,有些手机调用onAutoFocus函数,会返回失败,因为如果该手机无自动对焦,则无法执行对焦成功后的函数了。。。

当捕捉到数据后,便可以将这些数据保存至设定的地方了:

/**
* 设置照片的路径,具体路径可自定义
* @return
*/
private String setPicSaveFile(){
//创建保存的路径
File storageDir = getOwnCacheDirectory(this,”MyCamera/photos”);
//返回自定义的路径
return storageDir.getPath();
}

private File getOwnCacheDirectory(Context context, String cacheDir) {
File appCacheDir = null;
//判断SD卡正常挂载并且拥有根限的时候创建文件
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) &&
hasExternalStoragePermission(context)){
appCacheDir = new File(Environment.getExternalStorageDirectory(),cacheDir);
}
if (appCacheDir == null || !appCacheDir.exists() && !appCacheDir.mkdirs()){
appCacheDir = context.getCacheDir();
}
return appCacheDir;
}

/**
* 检查是否有权限
* @param context
* @return
*/
private boolean hasExternalStoragePermission(Context context) {
int permission = context.checkCallingOrSelfPermission(“android.permission.WRITE_EXTERNAL_STORAGE”);
//PERMISSION_GRANTED=0
return permission == 0;
}
由于调用了系统Camera和对SD卡读写,所以在AndroidManifest需要申请权限:

<!–摄像头相关权限–>
<uses-permission android:name=”android.permission.CAMERA” />
<uses-feature android:name=”android.hardware.camera” />
<uses-feature android:name=”android.hardware.camera.autofocus” />
<!– 在SDCard中创建与删除文件权限 –>
<uses-permission android:name=”android.permission.MOUNT_UNMOUNT_FILESYSTEMS”/>
<!– 往SDCard写入数据权限 –>
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/>
 

后记:google在Android5.0后推出了Camera的升级版—Camera2:

按照Android的官方说明,camera 2支持以下5点新特性,有兴趣的可以研究下:

(1)支持每秒30帧的全高清连拍。
(2)支持在每帧之间使用不同的设置。
(3)支持原生格式的图像输出。
(4)支持零延迟快门和电影速拍。
(5)支持相机在其他方面的手动控制,比如设置噪音消除的级别。

Android Studio | 直接调用系统摄像头拍照并回显、在SurfaceView中实现嵌入拍照并回显

1.直接调用系统摄像头拍照
功能实现:点击按钮,调用系统摄像头拍照之后,回显在imageviewl里面。

%title插图%num
public class register extends AppCompatActivity
{
private ImageView shotview ;//shotview定义在这里
//【warning】如果这里写成:private ImageView shotview = findViewById(R.id.imageView);会报错
//因为需要在onCreate()将类实例化之后,才可以进行初始化。
private File currentImageFile = null;
@Override
protected void onCreate(Bundle savedInstanceState)
{
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.regiester);
shotview = findViewById(R.id.imageView); //shotview的初始化在这里

Button takePhoto = findViewById(R.id.getcamera);
takePhoto.setOnClickListener(new View.OnClickListener()
{
//直接调用系统摄像头
@Override
public void onClick(View v)
{
//dir指的是directory,目录。storage directory 存储目录。

//如果你想在外存储上放公共文件你可以使用getExternalStoragePublicDirectory()
//but!如果你的api 版本低于8,那么不能使用getExternalStoragePublicDirectory(),
//而是使用Environment.getExternalStorageDirectory(),不带参数,不能自己创建一个目录,只是返回外部存储的根路径。
File dir = new File(Environment.getExternalStorageDirectory(),”pictures”);
//函数原型:File newFile=new File(directory, filename)
//创建了一个文件夹,名字是dir,路径是外部存储的根路径,名字是”pictures”。
if(dir.exists())
{
dir.mkdirs();//在根路径下建子目录,子目录名是”pictures”
}

//命名临时图片的文件名
currentImageFile = new File(dir,System.currentTimeMillis() + “.jpg”);
if(!currentImageFile.exists())
{
try
{
currentImageFile.createNewFile();
}
catch (IOException e)
{
e.printStackTrace();
}
}

Intent it = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//通过intent调用照相机照相
it.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(currentImageFile));
//存到外部存储的临时文件currentImageFile的路径下
startActivityForResult(it, Activity.DEFAULT_KEYS_DIALER);
//如果想在Activity中得到新打开Activity 关闭后返回的数据,
// 需要使用系统提供的startActivityForResult(Intent intent, int requestCode)方法
//打开新的Activity,新的Activity 关闭后会向前面的Activity传回数据,为了得到传回的数据,
//必须在前面的Activity中重写onActivityResult(int requestCode, int resultCode, Intent data)方法。
}
});
}
//重写onActivityResult(int requestCode, int resultCode, Intent data)方法
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == Activity.DEFAULT_KEYS_DIALER)
{
shotview.setImageURI(Uri.fromFile(currentImageFile));
}
}
}

关于内部存储与外部存储:

1、写一个相册程序,图片肯定是放在外部存储中;而如果要保存一个应用的一些设置数据,是放在内部存储的data目录下。因此其实在安卓文件管理中,我们都是在操作*对路径。

2、如今的设备,很多中高端机器都将自己的机身存储扩展到了8G以上,他们将存储在概念上分成了”内部internal” 和”外部external” 两部分,但其实都在手机内部。所以不管安卓手机是否有可移动的sdcard,他们总是有外部存储和内部存储。
3、你把手机连接电脑,能被电脑识别的部分就一定是外部存储。
4、公共文件Public files:文件是可以被自由访问,且文件的数据对其他应用或者用户来说都是由意义的,当应用被卸载之后,其卸载前创建的文件仍然保留。
私有文件Private files:外部存储上,应用私有文件的价值在于卸载之后,这些文件也会被删除。
5、如果你想在外存储上放公共文件你可以使用getExternalStoragePublicDirectory()。but!如果你的api 版本低于8,那么不能使用getExternalStoragePublicDirectory(),而是使用Environment.getExternalStorageDirectory(),它不带参数,不能自己创建一个目录,只是返回外部存储的根路径。
【引用总结自:个人认为的一个关于内部存储和外部存储的很好的总结】

2.将拍照界面通过SurfaceView嵌入自己写的界面,存储拍下的照片,照片回调显示

%title插图%num

其中关于surfaceView的设置:

<SurfaceView
android:id=”@+id/sfv_preview”
android:layout_width=”350dp”
android:layout_height=”393dp”
android:text=”@string/photo_preview”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintHorizontal_bias=”0.529″
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toTopOf=”parent”
app:layout_constraintVertical_bias=”0.135″ />

register.java中的代码如下:

public class register extends AppCompatActivity
{
private SurfaceView sfv_preview;
private Button btn_take_photo;
private Camera camera = null;

//surfaceHolder是surface的监听器,提供访问和控制SurfaceView背后的Surface 相关的方法
private SurfaceHolder.Callback cpHolderCallback = new SurfaceHolder.Callback()
{
@Override
public void surfaceCreated(SurfaceHolder holder)
//当surface对象创建后,该方法就会被立即调用。
{
startPreview();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
//当surface发生任何结构性的变化时(格式或者大小),该方法就会被立即调用
{
}

@Override
public void surfaceDestroyed(SurfaceHolder holder)
//当surface对象在将要销毁前,该方法会被立即调用。
{
stopPreview();
}
};

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.regiester);
bindViews();
}

private void bindViews()
{
sfv_preview = (SurfaceView) findViewById(R.id.sfv_preview);
btn_take_photo = (Button) findViewById(R.id.getcamera);
sfv_preview.getHolder().addCallback(cpHolderCallback);
//获得sfv_preview的surfaceview,
//再由 addCallback为SurfaceHolder添加一个SurfaceHolder.Callback回调接口。

btn_take_photo.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)//按下“开始拍照”按钮
{
camera.takePicture(null, null, new Camera.PictureCallback()
//拍照函数
{
@Override
public void onPictureTaken(byte[] data, Camera camera)
{
String path = “”;//定义路径
if ((path = saveFile(data)) != null)//如果路径存在
{
Intent it = new Intent(register.this, show_registered_photo.class);
it.putExtra(“path”, path);
startActivity(it);
//用intent实现从当前界面跳转到预览照片的界面
}
else
{
Toast.makeText(register.this, “保存照片失败”, Toast.LENGTH_SHORT).show();
}
}
});
}
});
}

//保存临时文件的方法
private String saveFile(byte[] bytes){
try {
File file = File.createTempFile(“img”,””);
FileOutputStream fos = new FileOutputStream(file);
fos.write(bytes);
fos.flush();
fos.close();
return file.getAbsolutePath();
}
catch (IOException e)
{
e.printStackTrace();
}
return “”;
}

//开始预览
private void startPreview()
{
camera = Camera.open();
try
{
camera.setPreviewDisplay(sfv_preview.getHolder());
camera.setDisplayOrientation(90); //让相机旋转90度
camera.startPreview();
}
catch (IOException e)
{
e.printStackTrace();
}
}

//停止预览
private void stopPreview() {
camera.stopPreview();
camera.release();
camera = null;
}
}

3.1遇到的小插曲(1):
代码没有错的情况下,和camera有关的方法都报错。如下图所示:

%title插图%num

%title插图%num
后来发现是因为import了错误的包,关于Camera有两个不同的包,在快捷import的时候有两个选项,如下图(画×的是我之前调用的包,画√的是导入了之后就不再报错的包),导入的时候要多注意:

%title插图%num
3.2 遇到的小插曲(2)
拍照界面跳出来之后,按下btn_take_photo按钮后闪退回登录界面。
报错原因:是因为show_registered_photo类没有注册,Alt+Enter快捷键,讲其写入 AndroidManifest.xml 即可。

两个surfaceView切换的demo

需求:视频通话界面,两个surfaceView一个显示本端的视图,另一个显示对端的视图,由于显示比例的问题总会存在一个覆盖另一个的问题,为保证用户体验,规定小的覆盖大的视图上面,且点击小的视图可切花为大图视图居中,达到两个视图切花的功能。简单写一个demo完成功能的测试需求,为了较少文章的篇幅,视图的内容用回执矩形代替(实际开发中显示的是本地照相采集的数据和对端经过opgl处理的数据)

一,简单的布局

<?xml version=”1.0″ encoding=”utf-8″?>
<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” >

<RelativeLayout
android:id=”@+id/remote_rl”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”

>

<SurfaceView
android:id=”@+id/remote_view”
android:layout_width=”match_parent”
android:layout_height=”match_parent”

</RelativeLayout> android:layout_gravity=”center” />

</RelativeLayout>

<RelativeLayout
android:id=”@+id/local_rl”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”

>

<SurfaceView
android:id=”@+id/local_view”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</RelativeLayout>

具体的demo实现

public class MainActivity extends Activity implements View.OnClickListener {
public static final String TAG = “sssss”;
//远端的视图
private SurfaceView remote_sv;
// 本地的视图
private SurfaceView local_sv;
private SurfaceHolder remote_holder;
private SurfaceHolder local_holder;
private RelativeLayout remote_rl;
private RelativeLayout local_rl;

private int screenWidth;
private int screenHeight;

private int beforRemoteweith;
private int beforLocalweith;
private int beforRemoteheigth;
private int beforLocalheigth;
private int StateAB = 0;
private int StateBA = 1;
private int mSate;
private int defaultLocalHeight=200;
private int defaultLocalwidth=400;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DisplayMetrics dm = getResources().getDisplayMetrics();
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels – 500;
remote_sv = (SurfaceView) findViewById(R.id.remote_view);
remote_rl = (RelativeLayout) findViewById(R.id.remote_rl);
local_rl = (RelativeLayout) findViewById(R.id.local_rl);
remote_sv.setOnClickListener(this);

LayoutParams params = new LayoutParams(screenWidth, screenHeight);
remote_sv.setLayoutParams(params);
remote_holder = remote_sv.getHolder();
// 对 surfaceView 进行操作
remote_holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas c = remote_holder.lockCanvas();
// 2.开画
Paint p = new Paint();
p.setColor(Color.RED);
Rect aa = new Rect(0, 0, holder.getSurfaceFrame().width(),
holder.getSurfaceFrame().height());
c.drawRect(aa, p);
// 3. 解锁画布 更新提交屏幕显示内容
remote_holder.unlockCanvasAndPost(c);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {/*
Log.d(TAG,”remote_holder surfaceChanged width”+ width+”height”+height);
Canvas c = remote_holder.lockCanvas();
// 2.开画
Paint p = new Paint();
p.setColor(Color.RED);
Rect aa = new Rect(0, 0, holder.getSurfaceFrame().width(),
holder.getSurfaceFrame().height());
c.drawRect(aa, p);
// 3. 解锁画布 更新提交屏幕显示内容
remote_holder.unlockCanvasAndPost(c);
*/}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {

}
});// 自动运行surfaceCreated以及surfaceChanged

local_sv = (SurfaceView) findViewById(R.id.local_view);
local_sv.setOnClickListener(this);
local_sv.setOnClickListener(this);
// sv.setZOrderOnTop(false);
local_sv.setZOrderOnTop(true);
// 这两个方法差不多,设置了就会浮现到顶部,但是,后面的看不见,要像下面设置为透明
// local_sv.setZOrderOnTop(true);
// local_sv.setZOrderMediaOverlay(true);

local_holder = local_sv.getHolder();

remote_holder.setFormat(PixelFormat.TRANSPARENT);
local_holder.setFormat(PixelFormat.TRANSPARENT);
LayoutParams params1 = new LayoutParams(defaultLocalHeight, defaultLocalwidth);
local_sv.setLayoutParams(params1);
remote_holder = remote_sv.getHolder();
local_holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas c = holder.lockCanvas();
// 2.开画
Paint p = new Paint();
p.setColor(Color.YELLOW);
Rect aa = new Rect(0, 0, holder.getSurfaceFrame().width(),
holder.getSurfaceFrame().height());
c.drawRect(aa, p);
// 3. 解锁画布 更新提交屏幕显示内容
holder.unlockCanvasAndPost(c);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {/*
Log.d(TAG,”local_holder surfaceChanged width”+ width+”height”+height);
Canvas c = holder.lockCanvas();
// 2.开画
Paint p = new Paint();
p.setColor(Color.YELLOW);
Rect aa = new Rect(0, 0, holder.getSurfaceFrame().width()-50,
holder.getSurfaceFrame().height()-50);
c.drawRect(aa, p);
// 3. 解锁画布 更新提交屏幕显示内容
holder.unlockCanvasAndPost(c);

*/}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {

}
});
zoomOpera(local_rl, local_sv, remote_sv, remote_rl, defaultLocalwidth,
defaultLocalHeight, RelativeLayout.CENTER_IN_PARENT);
}

@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.local_view:
Log.d(TAG, ” onClick local_view” + mSate);
if (mSate == StateAB) {
zoomlocalViewout(beforRemoteweith, beforRemoteheigth, local_sv,
remote_sv);
zoomRemoteViewint(beforLocalweith, beforLocalheigth);
mSate = StateBA;
}

break;
case R.id.remote_view:
Log.d(TAG, ” onClick emote_view” + mSate);
if (mSate == StateBA) {

zoomRemoteout(beforRemoteweith, beforRemoteheigth, local_sv,
remote_sv);
zoomlocalViewint(beforLocalweith, beforLocalheigth);

mSate = StateAB;
}

break;
default:
break;
}

}
//放大远端的视图
private void zoomRemoteout(int weith2, int heigth2, SurfaceView localView,
SurfaceView remoteView) {

beforLocalheigth = localView.getMeasuredHeight();
beforLocalweith = localView.getMeasuredWidth();
beforRemoteheigth = remoteView.getMeasuredHeight();
beforRemoteweith = remoteView.getMeasuredWidth();
Log.d(TAG, “zoomRemoteout beforLocalheigth” + beforLocalheigth
+ “beforLocalweith” + beforLocalweith + “beforRemoteheigth”
+ beforRemoteheigth + “beforRemoteweith” + beforLocalweith);
zoomOpera(local_rl, local_sv, remote_sv, remote_rl, screenWidth,
beforLocalheigth, RelativeLayout.CENTER_IN_PARENT);

}
//具体的视图操作
private void zoomOpera(View sourcView, SurfaceView beforeview,
SurfaceView afterview, View detView, int beforLocalweith,
int beforLocalHeigth, int rule) {

LayoutParams params1 = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);

Log.w(TAG, “beforLocalheigth = ” + beforLocalheigth
+ “; beforLocalweith = ” + beforLocalweith);
params1.addRule(rule, RelativeLayout.TRUE);
afterview.setLayoutParams(params1);
afterview.setBackgroundResource(android.R.color.transparent);
params1 = new LayoutParams(beforLocalweith, beforLocalHeigth);
params1.addRule(rule, RelativeLayout.TRUE);
detView.setLayoutParams(params1);

}
//缩小远端的视图
private void zoomRemoteViewint(int weith2, int heigth2) {
RelativeLayout paretview = (RelativeLayout) local_rl.getParent();
paretview.removeView(remote_rl);
paretview.removeView(local_rl);
zoomOpera(local_rl, local_sv, remote_sv, remote_rl, beforLocalweith,
beforLocalheigth, RelativeLayout.ALIGN_PARENT_TOP);
Log.d(TAG, “paretview” + paretview.getChildCount());
paretview.addView(local_rl);
paretview.addView(remote_rl);
remote_sv.setZOrderOnTop(true);

}
//放大本端的视图
private void zoomlocalViewout(int weith2, int heigth2,
SurfaceView localView, SurfaceView remoteView) {
beforLocalheigth = localView.getMeasuredHeight();
beforLocalweith = localView.getMeasuredWidth();
beforRemoteheigth = remoteView.getMeasuredHeight();
beforRemoteweith = remoteView.getMeasuredWidth();
Log.d(TAG, “zoomlocalViewout beforLocalheigth” + beforLocalheigth
+ “beforLocalweith” + beforLocalweith + “beforRemoteheigth”
+ beforRemoteheigth + “beforRemoteweith” + beforRemoteweith);
zoomOpera(remote_rl, remote_sv, local_sv, local_rl, beforRemoteweith,
beforRemoteheigth, RelativeLayout.CENTER_IN_PARENT);

}
//减小本端的视图
private void zoomlocalViewint(int weith2, int heigth2) {
RelativeLayout paretview = (RelativeLayout) local_rl.getParent();
paretview.removeView(remote_rl);
paretview.removeView(local_rl);
zoomOpera(remote_rl, remote_sv, local_sv, local_rl, beforRemoteweith,
beforRemoteheigth, RelativeLayout.ALIGN_PARENT_TOP);
paretview.addView(remote_rl);
paretview.addView(local_rl);
local_sv.setZOrderOnTop(true);

}
}

仿微信视频通话大小视图切换(SurfaceView实现)

前言

前一段时间做了一个即时通讯的项目,在项目中遇到很多坑,有时间一一做个总结,项目消息发送基于XMPP+Tigase,语言视频通话基于PJSIP+FreeSWITCH,项目UI仿微信。做到视频通话时,遇到本地视图与远程视图切换,网上搜了一篇相关的博客,根据大神思路写了这个Demo,其中用的是第三直播源可能有点不稳定,切换过程可能存在黑屏和无响应的情况,但是用的Pjsip中切换还是很流程的;

布局

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <FrameLayout
  3. xmlns:android=“http://schemas.android.com/apk/res/android”
  4. xmlns:tools=“http://schemas.android.com/tools”
  5. android:layout_width=“match_parent”
  6. android:layout_height=“match_parent”
  7. tools:context=“com.demo.surfaceviewdemo.MainActivity”>
  8. <RelativeLayout
  9. android:layout_width=“match_parent”
  10. android:layout_height=“match_parent”>
  11. <RelativeLayout
  12. android:id=“@+id/rl_remote”
  13. android:layout_width=“match_parent”
  14. android:layout_height=“wrap_content”>
  15. <SurfaceView
  16. android:id=“@+id/surfaceview_remote”
  17. android:layout_width=“match_parent”
  18. android:layout_height=“match_parent”/>
  19. </RelativeLayout>
  20. <RelativeLayout
  21. android:id=“@+id/rl_local”
  22. android:layout_width=“wrap_content”
  23. android:layout_height=“wrap_content”
  24. android:layout_alignParentRight=“true”>
  25. <SurfaceView
  26. android:id=“@+id/surfaceview_local”
  27. android:layout_width=“wrap_content”
  28. android:layout_height=“wrap_content”/>
  29. </RelativeLayout>
  30. </RelativeLayout>
  31. <!–通话时显示的–>
  32. <LinearLayout
  33. android:id=“@+id/ll_call_container”
  34. android:layout_width=“match_parent”
  35. android:layout_height=“wrap_content”
  36. android:layout_gravity=“bottom”
  37. android:layout_marginBottom=“25dp”
  38. android:gravity=“center_horizontal”
  39. android:orientation=“horizontal”>
  40. <TextView
  41. android:id=“@+id/tv_call_quiet”
  42. android:layout_width=“wrap_content”
  43. android:layout_height=“wrap_content”
  44. android:layout_weight=“1”
  45. android:drawablePadding=“10dp”
  46. android:drawableTop=“@mipmap/chat_video_change_voice_img”
  47. android:gravity=“center_horizontal”
  48. android:text=“切到语音聊天”
  49. android:textColor=“#ffffff”
  50. android:textSize=“12sp”/>
  51. <TextView
  52. android:id=“@+id/tv_handup_call”
  53. android:layout_width=“wrap_content”
  54. android:layout_height=“wrap_content”
  55. android:layout_weight=“1”
  56. android:drawablePadding=“10dp”
  57. android:drawableTop=“@mipmap/chat_video_guaduan_img_normal”
  58. android:gravity=“center_horizontal”
  59. android:text=“挂断”
  60. android:textColor=“#ffffff”
  61. android:textSize=“12sp”/>
  62. <TextView
  63. android:id=“@+id/tv_change_camera”
  64. android:layout_width=“wrap_content”
  65. android:layout_height=“wrap_content”
  66. android:layout_weight=“1”
  67. android:drawablePadding=“10dp”
  68. android:drawableTop=“@mipmap/chat_video_change_camera_img”
  69. android:gravity=“center_horizontal”
  70. android:text=“转换摄像头”
  71. android:textColor=“#ffffff”
  72. android:textSize=“12sp”/>
  73. </LinearLayout>
  74. </FrameLayout>

代码

为了实现跟微信一样的效果,普通屏幕全屏显示,为了不让视频内容挤到刘海屏中,添加一下代码:

  1. //如果判断有刘海屏不让填充到状态栏
  2. if (DisplayUtil.hasNotchScreen(this)) {
  3. getWindow().addFlags(
  4. WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  5. | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
  6. } else {
  7. getWindow().addFlags(
  8. WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
  9. | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
  10. | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
  11. }

大小视图切换代码:

  1. /**
  2. * 大小视图切换 (小视图在前面、大视图在后面)
  3. *
  4. * @param sourcView 之前相对布局大小
  5. * @param beforeview 之前surfaceview
  6. * @param detView 之后相对布局大小
  7. * @param afterview 之后surfaceview
  8. */
  9. private void zoomOpera(View sourcView, SurfaceView beforeview,
  10. View detView, SurfaceView afterview) {
  11. RelativeLayout paretview = (RelativeLayout) sourcView.getParent();
  12. paretview.removeView(detView);
  13. paretview.removeView(sourcView);
  14. //设置远程大视图RelativeLayout 的属性
  15. RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,
  16. RelativeLayout.LayoutParams.MATCH_PARENT);
  17. params1.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
  18. beforeview.setZOrderMediaOverlay(true);
  19. beforeview.getHolder().setFormat(PixelFormat.TRANSPARENT);
  20. sourcView.setLayoutParams(params1);
  21. //设置本地小视图RelativeLayout 的属性
  22. params1 = new RelativeLayout.LayoutParams(defaultLocalwidth, defaultLocalHeight);
  23. params1.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
  24. params1.setMargins(0, defaultLocalMargin, defaultLocalMargin, 0);
  25. //在调用setZOrderOnTop(true)之后调用了setZOrderMediaOverlay(true) 遮挡问题
  26. afterview.setZOrderOnTop(true);
  27. afterview.setZOrderMediaOverlay(true);
  28. afterview.getHolder().setFormat(PixelFormat.TRANSPARENT);
  29. detView.setLayoutParams(params1);
  30. paretview.addView(sourcView);
  31. paretview.addView(detView);
  32. }

%title插图%num

效果图一

%title插图%num

效果图二