由于需求不同,所以选择的方案固然也不同,至于第二种调用系统相机,这里就不过多讲解了,使用Intent对象设置一个Action动作即可,跳转时使用startActivityForResult,然后在onActivityResult处理相关数据便可,关键代码:
intent.setAction(“android.media.action.STILL_IMAGE_CAMERA”);
至于使用,较常见的一般是应用中用户上传头像的时候调用,然后返回处理图像数据。
而第一种自定义相机的方式使用也十分普遍,但是要做好这个模块,相对来说还是有一定难度的,之前分享过一个Github上的开源相机的项目,项目由美国的一个团队开发,集 拍照、摄影、各种特效动画 等功能与一身,本人之前研究了下,发现功能比较全面也很强大,抠出来单独拍照那一个模块,我滴妈呀,真TM费劲!相机不管是预览还是拍摄图像都还是很清晰的,自己当时也写了一个,比较操蛋,只能怪自己对这一块的优化了解浅显吧!特别是预览的时候,聚焦完成后,焦点周边会出现很多白色的噪点,密密麻麻,特别严重,头疼的很。不过也总算解决了,灰常感谢USA的那个什么什么团队的开源相机程序。经过自己改造后的预览效果图:
下面看下这个项目的效果图,我也把地址甩底下,大伙感兴趣的自行Clone研究(或者闲的蛋疼也可以抽时间剥离开每一个模块学习,作为日后的知识储备),里面也用到了这个**Android中读取图片EXIF元数据之metadata-extractor的使用**。
GitHub:https://github.com/xplodwild/android_packages_apps_Focal
相机开发简介
==========
下面说说在Android中调用Camera来定义相机的最基本步骤:
-
打开相机 —— 调用Camera的open()方法。
-
获取拍照参数 —— 调用Camera的getParameters()方法,返回Camera.Parameters对象。
-
拍照参数设置 —— 调用Camera.Parameters对象。
-
拍照参数控制 —— 调用Camera的setParameters(),并将Camera.Parameters对象作为参数传入。注:Android2.3.3之后不用设置。
-
预览取景 —— 调用Camera的startPreview()方法,在之前注意调用Camera的setPreviewDisplay(SurfaceHolder holder)设置使用哪个SurfaceView来显示取得的图片。
-
拍照 —— 调用Camera的takePicture()
-
停止预览 —— 调用Camera的stopPreview()方法
-
资源释放 —— Camera.release()
开启和关闭预览的联系如下:Camera ---- SurfaceHolder ------ SurfaceView
关于SurfaceHolder.Callback必须实现的3个方法:
surfaceCreated() 该方法在surfaceView被Create时调用
surfaceChanged() 该方法是当surfaceView发生改变后调用
surfaceDestroyed() 这个不用说了,销毁时调用
surfaceHolder通过addCallBack()方法将响应的接口绑定
注:必要Camera权限,例如:
关于Camera下的Parameters类,其中封装了我们需要的大部分功能,下面做个简单介绍:
-
setPictureFormat() 方法用于设置相机照片的格式,其参数是一个字符型参数,位于PixelFormat类中,如:PixelFormat.JPEG。
-
setSceneMode() 方法用于设置相机场景类型,其参是是一个字符型参数,位于Parameters类中,以SCENE_MODE_开头。
-
setZoom() 方法用于设置相机焦距,其参数是一个整型的参数,该参数的范围是0到Camera.getParameters().getMaxZoom()。
-
setPictureSize() 方法用于设置相机照片的大小,参数为整型。
-
setWhiteBalance() 方法用于设置相机照片白平衡,其参数是一个字符型,位于Parameters类中,以WHITE_BALANCE开头。
-
setJpegQuality() 方法用于设置相机照片的质量,其参数是一个整型参数,取值范围为1到100。
-
setFlashMode() 方法用于设置闪光灯的类型,其参数是一个字符型参数,位于Parameters类中,以FLASH_MODE_开头。
-
setColorEffect() 方法用于设置照片颜色特效的类型,其参数是一个字符型参数,位于Parameters类中,以EFFECT_开头。
本程序模块效果图及示例
===============
下面分享本篇Blog的示例相机模块,此功能模块并非上面开源项目中的剥离出来的,看下效果图咯:
效果看着还可以吧(不点赞也太不给面子了吧 - . - ),下面个出主界面的布局代码:
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:id=“@+id/layout”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
<SurfaceView
android:id=“@+id/surfaceView”
android:layout_width=“match_parent”
android:layout_height=“match_parent” />
<RelativeLayout
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
<org.gaochun.camera.CameraGrid
android:id=“@+id/camera_grid”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_alignParentTop=“true” />
<View
android:id=“@+id/focus_index”
android:layout_width=“40dp”
android:layout_height=“40dp”
android:background=“@drawable/camera_focus”
android:visibility=“invisible” />
<ImageView
android:id=“@+id/flash_view”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentLeft=“true”
android:onClick=“onClick”
android:padding=“15dp”
android:scaleType=“centerCrop”
android:src=“@drawable/camera_flash_off” />
<ImageView
android:id=“@+id/camera_flip_view”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentRight=“true”
android:onClick=“onClick”
android:padding=“15dp”
android:scaleType=“centerCrop”
android:src=“@drawable/camera_flip” />
<RelativeLayout
android:layout_width=“fill_parent”
android:layout_height=“70dp”
android:layout_alignParentBottom=“true”
android:background=“#a0000000”
android:padding=“5dp” >
<Button
android:id=“@+id/search”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_marginLeft=“30dp”
android:background=“@null”
android:drawablePadding=“3dp”
android:drawableTop=“@drawable/ic_search_selector”
android:onClick=“onClick”
android:text=“搜图”
android:textColor=“@drawable/row_selector_text” />
<ImageView
android:id=“@+id/action_button”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerInParent=“true”
android:clickable=“true”
android:onClick=“onClick”
android:src=“@drawable/btn_shutter_photo” />
<Button
android:id=“@+id/takephoto”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentRight=“true”
android:layout_marginRight=“30dp”
android:background=“@null”
android:drawablePadding=“3dp”
android:drawableTop=“@drawable/ic_takephoto_selector”
android:onClick=“onClick”
android:text=“拍照”
android:textColor=“@drawable/row_selector_text” />
下面是核心模块 CameraPreview 类:
public class CameraPreview extends ViewGroup implements SurfaceHolder.Callback, Camera.AutoFocusCallback {
private SurfaceView mSurfaceView;
private SurfaceHolder mHolder;
private Size mPreviewSize;
private Size adapterSize;
//private List mSupportedPreviewSizes;
private Camera mCamera;
private boolean isSupportAutoFocus = false;
private Camera.Parameters parameters = null;
private Context mContext;
//private int mCurrentCameraId = 0;
private int screenWidth;
private int screenHeight;
CameraPreview(Context context, SurfaceView sv) {
super(context);
mContext = context;
mSurfaceView = sv;
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mHolder.setKeepScreenOn(true);
isSupportAutoFocus = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_CAMERA_AUTOFOCUS);
DisplayMetrics dm = new DisplayMetrics();
((Activity) mContext).getWindowManager().getDefaultDisplay().getMetrics(dm);
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
}
public void setCamera(Camera camera) {
mCamera = camera;
initCamera();
}
public void initCamera() {
if (mCamera != null) {
Camera.Parameters params = mCamera.getParameters();
//mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
requestLayout();
if (mPreviewSize == null) {
mPreviewSize = findBestPreviewResolution();
}
if (adapterSize == null) {
adapterSize = findBestPictureResolution();
}
if (adapterSize != null) {
params.setPictureSize(adapterSize.width, adapterSize.height);
}
if (mPreviewSize != null) {
params.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
}
params.setPictureFormat(PixelFormat.JPEG);
List focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
// set the focus mode
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
mCamera.setParameters(params);
}
setDispaly(params, mCamera);
//setCameraDisplayOrientation((Activity) mContext, mCurrentCameraId, mCamera);
mCamera.setParameters(params);
}
}
//控制图像的正确显示方向
private void setDispaly(Camera.Parameters parameters, Camera camera) {
if (Build.VERSION.SDK_INT >= 8) {
setDisplayOrientation(camera, 90);
} else {
parameters.setRotation(90);
}
}
//实现的图像的正确显示
private void setDisplayOrientation(Camera camera, int i) {
Method downPolymorphic;
try {
downPolymorphic = camera.getClass().getMethod(“setDisplayOrientation”,
new Class[]{int.class});
if (downPolymorphic != null) {
downPolymorphic.invoke(camera, new Object[]{i});
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void setCameraDisplayOrientation(Activity activity,
int cameraId, android.hardware.Camera camera) {
android.hardware.Camera.CameraInfo info =
new android.hardware.Camera.CameraInfo();
android.hardware.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; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
camera.setDisplayOrientation(result);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
// if (mSupportedPreviewSizes != null) {
// mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
// }
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed && getChildCount() > 0) {
final View child = getChildAt(0);
final int width = r - l;
final int height = b - t;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null) {
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
}
// Center the child SurfaceView within the parent.
if (width * previewHeight > height * previewWidth) {
final int scaledChildWidth = previewWidth * height / previewHeight;
child.layout((width - scaledChildWidth) / 2, 0,
(width + scaledChildWidth) / 2, height);
} else {
final int scaledChildHeight = previewHeight * width / previewWidth;
child.layout(0, (height - scaledChildHeight) / 2,
width, (height + scaledChildHeight) / 2);
}
}
}
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, acquire the camera and tell it where
// to draw.
try {
if (mCamera != null) {
mCamera.setPreviewDisplay(holder);
}
} catch (IOException e) {
if (null != mCamera) {
mCamera.release();
mCamera = null;
}
e.printStackTrace();
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (holder.getSurface() == null) {
return;
}
if (mCamera != null) {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
try {
mCamera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
mCamera.startPreview();
reAutoFocus();
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return, so stop the preview.
if (mCamera != null) {
mCamera.stopPreview();
}
}
/**
- 最小预览界面的分辨率
*/
private static final int MIN_PREVIEW_PIXELS = 480 * 320;
/**
- 最大宽高比差
*/
private static final double MAX_ASPECT_DISTORTION = 0.15;
/**
-
找出最适合的预览界面分辨率
-
@return
*/
private Camera.Size findBestPreviewResolution() {
Camera.Parameters cameraParameters = mCamera.getParameters();
Camera.Size defaultPreviewResolution = cameraParameters.getPreviewSize();
List<Camera.Size> rawSupportedSizes = cameraParameters.getSupportedPreviewSizes();
if (rawSupportedSizes == null) {
return defaultPreviewResolution;
}
// 按照分辨率从大到小排序
List<Camera.Size> supportedPreviewResolutions = new ArrayList<Camera.Size>(rawSupportedSizes);
Collections.sort(supportedPreviewResolutions, new Comparator() {
@Override
public int compare(Camera.Size a, Camera.Size b) {
int aPixels = a.height * a.width;
int bPixels = b.height * b.width;
if (bPixels < aPixels) {
return -1;
}
if (bPixels > aPixels) {
return 1;
}
return 0;
}
});
StringBuilder previewResolutionSb = new StringBuilder();
for (Camera.Size supportedPreviewResolution : supportedPreviewResolutions) {
previewResolutionSb.append(supportedPreviewResolution.width).append(‘x’).append(supportedPreviewResolution.height)
.append(’ ');
}
// 移除不符合条件的分辨率
double screenAspectRatio = (double) screenWidth
/ screenHeight;
Iterator it = supportedPreviewResolutions.iterator();
while (it.hasNext()) {
Camera.Size supportedPreviewResolution = it.next();
int width = supportedPreviewResolution.width;
int height = supportedPreviewResolution.height;
// 移除低于下限的分辨率,尽可能取高分辨率
if (width * height < MIN_PREVIEW_PIXELS) {
it.remove();
continue;
}
// 在camera分辨率与屏幕分辨率宽高比不相等的情况下,找出差距最小的一组分辨率
// 由于camera的分辨率是width>height,我们设置的portrait模式中,width<height
// 因此这里要先交换然preview宽高比后在比较
boolean isCandidatePortrait = width > height;
int maybeFlippedWidth = isCandidatePortrait ? height : width;
int maybeFlippedHeight = isCandidatePortrait ? width : height;
double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight;
double distortion = Math.abs(aspectRatio - screenAspectRatio);
if (distortion > MAX_ASPECT_DISTORTION) {
it.remove();
continue;
}
// 找到与屏幕分辨率完全匹配的预览界面分辨率直接返回
if (maybeFlippedWidth == screenWidth
&& maybeFlippedHeight == screenHeight) {
return supportedPreviewResolution;
}
}
// 如果没有找到合适的,并且还有候选的像素,则设置其中最大比例的,对于配置比较低的机器不太合适
if (!supportedPreviewResolutions.isEmpty()) {
Camera.Size largestPreview = supportedPreviewResolutions.get(0);
return largestPreview;
}
// 没有找到合适的,就返回默认的
return defaultPreviewResolution;
}
private Camera.Size findBestPictureResolution() {
Camera.Parameters cameraParameters = mCamera.getParameters();
List<Camera.Size> supportedPicResolutions = cameraParameters.getSupportedPictureSizes(); // 至少会返回一个值
StringBuilder picResolutionSb = new StringBuilder();
for (Camera.Size supportedPicResolution : supportedPicResolutions) {
picResolutionSb.append(supportedPicResolution.width).append(‘x’)
.append(supportedPicResolution.height).append(" ");
}
Camera.Size defaultPictureResolution = cameraParameters.getPictureSize();
// 排序
List<Camera.Size> sortedSupportedPicResolutions = new ArrayList<Camera.Size>(
supportedPicResolutions);
Collections.sort(sortedSupportedPicResolutions, new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size a, Camera.Size b) {
int aPixels = a.height * a.width;
int bPixels = b.height * b.width;
if (bPixels < aPixels) {
return -1;
}
if (bPixels > aPixels) {
return 1;
}
return 0;
}
});
// 移除不符合条件的分辨率
double screenAspectRatio = screenWidth
/ (double) screenHeight;
Iterator<Camera.Size> it = sortedSupportedPicResolutions.iterator();
while (it.hasNext()) {
Camera.Size supportedPreviewResolution = it.next();
int width = supportedPreviewResolution.width;
int height = supportedPreviewResolution.height;
// 在camera分辨率与屏幕分辨率宽高比不相等的情况下,找出差距最小的一组分辨率
// 由于camera的分辨率是width>height,我们设置的portrait模式中,width<height
// 因此这里要先交换然后在比较宽高比
boolean isCandidatePortrait = width > height;
int maybeFlippedWidth = isCandidatePortrait ? height : width;
int maybeFlippedHeight = isCandidatePortrait ? width : height;
double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight;
double distortion = Math.abs(aspectRatio - screenAspectRatio);
if (distortion > MAX_ASPECT_DISTORTION) {
it.remove();
continue;
}
}
// 如果没有找到合适的,并且还有候选的像素,对于照片,则取其中最大比例的,而不是选择与屏幕分辨率相同的
if (!sortedSupportedPicResolutions.isEmpty()) {
return sortedSupportedPicResolutions.get(0);
}
// 没有找到合适的,就返回默认的
return defaultPictureResolution;
}
private Size getOptimalPreviewSize(List sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) w / h;
if (sizes == null)
return null;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
public void reAutoFocus() {
if (isSupportAutoFocus) {
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
}
});
}
}
public List getResolutionList() {
return mCamera.getParameters().getSupportedPreviewSizes();
}
public Camera.Size getResolution() {
Camera.Parameters params = mCamera.getParameters();
Camera.Size s = params.getPreviewSize();
return s;
}
/*public void setCurrentCameraId(int current) {
mCurrentCameraId = current;
}*/
//定点对焦的代码
public void pointFocus(MotionEvent event) {
mCamera.cancelAutoFocus();
parameters = mCamera.getParameters();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
//showPoint(x, y);
focusOnTouch(event);
}
mCamera.setParameters(parameters);
autoFocus();
}
//实现自动对焦
public void autoFocus() {
new Thread() {
@Override
public void run() {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (mCamera == null) {
return;
}
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success) {
initCamera();//实现相机的参数初始化
}
}
});
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
尾声
最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
-
自行下载直达领取链接:点击这里前往GitHub
);
}
if (mCamera == null) {
return;
}
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success) {
initCamera();//实现相机的参数初始化
}
}
});
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-lSZtSmaI-1711372381342)]
[外链图片转存中…(img-JmvfSlVX-1711372381343)]
[外链图片转存中…(img-ZtHCLjWb-1711372381344)]
[外链图片转存中…(img-apOzgtih-1711372381344)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-sUe3HRxJ-1711372381344)]
尾声
最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
[外链图片转存中…(img-LABtFmUY-1711372381345)]文章来源:https://www.toymoban.com/news/detail-852573.html
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
[外链图片转存中…(img-Nf6QwByx-1711372381345)]文章来源地址https://www.toymoban.com/news/detail-852573.html
-
自行下载直达领取链接:点击这里前往GitHub
到了这里,关于Android相机开发实战,Android程序员必看的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!