├── .gitignore ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── interheart │ │ └── com │ │ └── circlecamera │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── dong │ │ │ └── circlecamera │ │ │ ├── MainActivity.java │ │ │ └── view │ │ │ ├── CameraListener.java │ │ │ ├── CameraPreview.java │ │ │ ├── CircleCameraLayout.java │ │ │ ├── CircleView.java │ │ │ ├── CircleView2.java │ │ │ └── Util.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── interheart │ └── com │ └── circlecamera │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image └── 20180525184119.png ├── readme.md └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | defaultConfig { 6 | applicationId "com.dong.circlecamera" 7 | minSdkVersion 15 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:26.1.0' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.0' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | } 29 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/interheart/com/circlecamera/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package interheart.com.circlecamera; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("interheart.com.circlecamera", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/dong/circlecamera/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.dong.circlecamera; 2 | 3 | import android.Manifest; 4 | import android.content.DialogInterface; 5 | import android.content.pm.PackageManager; 6 | import android.graphics.Bitmap; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.v4.app.ActivityCompat; 10 | import android.support.v7.app.AlertDialog; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.view.View; 13 | import android.widget.ImageView; 14 | import android.widget.Toast; 15 | 16 | import com.dong.circlecamera.view.CameraListener; 17 | import com.dong.circlecamera.view.CameraPreview; 18 | import com.dong.circlecamera.view.CircleCameraLayout; 19 | import com.dong.circlecamera.view.Util; 20 | 21 | /** 22 | * @author dong 23 | * @create 2018/5/25 24 | * @Describe 自定义圆形拍照、解决非全屏(竖屏)下预览相机拉伸问题。 25 | */ 26 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { 27 | 28 | private static final int PERMISSION_REQUEST_CODE = 10; 29 | private String[] mPermissions = {Manifest.permission.CAMERA}; 30 | 31 | private CircleCameraLayout rootLayout; 32 | private ImageView imageView; 33 | private CameraPreview cameraPreview; 34 | private boolean hasPermissions; 35 | private boolean resume = false;//解决home键黑屏问题 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.activity_main); 41 | 42 | findViewById(R.id.bt_take_photo).setOnClickListener(this); 43 | findViewById(R.id.bt_re_take_photo).setOnClickListener(this); 44 | rootLayout = findViewById(R.id.rootLayout); 45 | imageView = findViewById(R.id.image); 46 | 47 | //权限检查 48 | if (Util.checkPermissionAllGranted(this, mPermissions)) { 49 | hasPermissions = true; 50 | } else { 51 | ActivityCompat.requestPermissions(this, mPermissions, PERMISSION_REQUEST_CODE); 52 | } 53 | 54 | 55 | } 56 | 57 | @Override 58 | protected void onResume() { 59 | super.onResume(); 60 | if (hasPermissions) { 61 | startCamera(); 62 | resume = true; 63 | } 64 | } 65 | 66 | private void startCamera() { 67 | if (null != cameraPreview) cameraPreview.releaseCamera(); 68 | cameraPreview = new CameraPreview(this); 69 | rootLayout.removeAllViews(); 70 | rootLayout.setCameraPreview(cameraPreview); 71 | if (!hasPermissions || resume) { 72 | rootLayout.startView(); 73 | } 74 | cameraPreview.setCameraListener(new CameraListener() { 75 | @Override 76 | public void onCaptured(Bitmap bitmap) { 77 | if (null != bitmap) { 78 | imageView.setImageBitmap(bitmap); 79 | Toast.makeText(MainActivity.this, "拍照成功", Toast.LENGTH_SHORT).show(); 80 | } else { 81 | Toast.makeText(MainActivity.this, "拍照失败", Toast.LENGTH_SHORT).show(); 82 | } 83 | } 84 | }); 85 | } 86 | 87 | @Override 88 | public void onClick(View v) { 89 | if (null == cameraPreview) return; 90 | switch (v.getId()) { 91 | case R.id.bt_take_photo: 92 | cameraPreview.captureImage();//抓取照片 93 | break; 94 | case R.id.bt_re_take_photo: 95 | cameraPreview.startPreview(); 96 | break; 97 | } 98 | } 99 | 100 | @Override 101 | protected void onDestroy() { 102 | super.onDestroy(); 103 | if (null != cameraPreview) { 104 | cameraPreview.releaseCamera(); 105 | } 106 | rootLayout.release(); 107 | } 108 | 109 | /** 110 | * 申请权限结果返回处理 111 | */ 112 | @Override 113 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 114 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 115 | if (requestCode == PERMISSION_REQUEST_CODE) { 116 | boolean isAllGranted = true; 117 | for (int grant : grantResults) { // 判断是否所有的权限都已经授予了 118 | if (grant != PackageManager.PERMISSION_GRANTED) { 119 | isAllGranted = false; 120 | break; 121 | } 122 | } 123 | if (isAllGranted) { // 所有的权限都授予了 124 | startCamera(); 125 | } else {// 提示需要权限的原因 126 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 127 | builder.setMessage("拍照需要允许权限, 是否再次开启?") 128 | .setTitle("提示") 129 | .setPositiveButton("确认", new DialogInterface.OnClickListener() { 130 | @Override 131 | public void onClick(DialogInterface dialog, int which) { 132 | ActivityCompat.requestPermissions(MainActivity.this, mPermissions, PERMISSION_REQUEST_CODE); 133 | } 134 | }) 135 | .setNegativeButton("取消", new DialogInterface.OnClickListener() { 136 | @Override 137 | public void onClick(DialogInterface dialog, int which) { 138 | dialog.dismiss(); 139 | finish(); 140 | } 141 | }); 142 | builder.create().show(); 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/com/dong/circlecamera/view/CameraListener.java: -------------------------------------------------------------------------------- 1 | package com.dong.circlecamera.view; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | /** 6 | * Created by dong on 2018/5/23. 7 | */ 8 | 9 | public interface CameraListener { 10 | 11 | void onCaptured(Bitmap bitmap); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/dong/circlecamera/view/CameraPreview.java: -------------------------------------------------------------------------------- 1 | package com.dong.circlecamera.view; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Matrix; 7 | import android.hardware.Camera; 8 | import android.util.DisplayMetrics; 9 | import android.util.Log; 10 | import android.view.MotionEvent; 11 | import android.view.Surface; 12 | import android.view.SurfaceHolder; 13 | import android.view.SurfaceView; 14 | 15 | import java.io.IOException; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by dong on 2018/5/23. 20 | */ 21 | 22 | public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { 23 | private static final String TAG = "CameraPreview"; 24 | 25 | private Camera mCamera; 26 | private SurfaceHolder mHolder; 27 | private Activity mContext; 28 | private CameraListener listener; 29 | private int cameraId = Camera.CameraInfo.CAMERA_FACING_BACK; 30 | private int displayDegree = 90; 31 | 32 | public CameraPreview(Activity context) { 33 | super(context); 34 | mContext = context; 35 | mCamera = Camera.open(cameraId); 36 | mHolder = getHolder(); 37 | mHolder.addCallback(this); 38 | mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 39 | } 40 | 41 | public void setCameraListener(CameraListener listener) { 42 | this.listener = listener; 43 | } 44 | 45 | /** 46 | * 拍照获取bitmap 47 | */ 48 | public void captureImage() { 49 | try { 50 | mCamera.takePicture(null, null, new Camera.PictureCallback() { 51 | @Override 52 | public void onPictureTaken(byte[] data, Camera camera) { 53 | if (null != listener) { 54 | Bitmap bitmap = rotateBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), 55 | displayDegree); 56 | listener.onCaptured(bitmap); 57 | } 58 | } 59 | }); 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | if (null != listener) { 63 | listener.onCaptured(null); 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * 预览拍照 70 | */ 71 | public void startPreview() { 72 | mCamera.startPreview(); 73 | } 74 | 75 | @Override 76 | public boolean onTouchEvent(MotionEvent event) { 77 | if (null != mCamera) { 78 | mCamera.autoFocus(null); 79 | } 80 | return super.onTouchEvent(event); 81 | } 82 | 83 | @Override 84 | public void surfaceCreated(SurfaceHolder holder) { 85 | try { 86 | startCamera(holder); 87 | } catch (IOException e) { 88 | e.printStackTrace(); 89 | } 90 | } 91 | 92 | @Override 93 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 94 | if (mHolder.getSurface() == null) { 95 | return; 96 | } 97 | try { 98 | mCamera.stopPreview(); 99 | } catch (Exception e) { 100 | e.printStackTrace(); 101 | } 102 | try { 103 | startCamera(mHolder); 104 | } catch (Exception e) { 105 | Log.e(TAG, e.toString()); 106 | } 107 | } 108 | 109 | private void startCamera(SurfaceHolder holder) throws IOException { 110 | mCamera.setPreviewDisplay(holder); 111 | setCameraDisplayOrientation(mContext, cameraId, mCamera); 112 | 113 | Camera.Size preSize = getCameraSize(); 114 | 115 | Camera.Parameters parameters = mCamera.getParameters(); 116 | parameters.setPreviewSize(preSize.width, preSize.height); 117 | parameters.setPictureSize(preSize.width, preSize.height); 118 | parameters.setJpegQuality(100); 119 | mCamera.setParameters(parameters); 120 | mCamera.startPreview(); 121 | } 122 | 123 | public Camera.Size getCameraSize() { 124 | if (null != mCamera) { 125 | Camera.Parameters parameters = mCamera.getParameters(); 126 | DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); 127 | Camera.Size preSize = Util.getCloselyPreSize(true, metrics.widthPixels, metrics.heightPixels, 128 | parameters.getSupportedPreviewSizes()); 129 | return preSize; 130 | } 131 | return null; 132 | } 133 | 134 | @Override 135 | public void surfaceDestroyed(SurfaceHolder holder) { 136 | releaseCamera(); 137 | } 138 | 139 | 140 | /** 141 | * Android API: Display Orientation Setting 142 | * Just change screen display orientation, 143 | * the rawFrame data never be changed. 144 | */ 145 | private void setCameraDisplayOrientation(Activity activity, int cameraId, Camera camera) { 146 | Camera.CameraInfo info = new Camera.CameraInfo(); 147 | Camera.getCameraInfo(cameraId, info); 148 | int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 149 | int degrees = 0; 150 | switch (rotation) { 151 | case Surface.ROTATION_0: 152 | degrees = 0; 153 | break; 154 | case Surface.ROTATION_90: 155 | degrees = 90; 156 | break; 157 | case Surface.ROTATION_180: 158 | degrees = 180; 159 | break; 160 | case Surface.ROTATION_270: 161 | degrees = 270; 162 | break; 163 | } 164 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 165 | displayDegree = (info.orientation + degrees) % 360; 166 | displayDegree = (360 - displayDegree) % 360; // compensate the mirror 167 | } else { 168 | displayDegree = (info.orientation - degrees + 360) % 360; 169 | } 170 | camera.setDisplayOrientation(displayDegree); 171 | } 172 | 173 | 174 | /** 175 | * 将图片按照某个角度进行旋转 176 | * 177 | * @param bm 需要旋转的图片 178 | * @param degree 旋转角度 179 | * @return 旋转后的图片 180 | */ 181 | private Bitmap rotateBitmap(Bitmap bm, int degree) { 182 | Bitmap returnBm = null; 183 | 184 | // 根据旋转角度,生成旋转矩阵 185 | Matrix matrix = new Matrix(); 186 | matrix.postRotate(degree); 187 | try { 188 | // 将原始图片按照旋转矩阵进行旋转,并得到新的图片 189 | returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), 190 | bm.getHeight(), matrix, true); 191 | } catch (OutOfMemoryError e) { 192 | e.printStackTrace(); 193 | } 194 | if (returnBm == null) { 195 | returnBm = bm; 196 | } 197 | if (bm != returnBm) { 198 | bm.recycle(); 199 | } 200 | return returnBm; 201 | } 202 | 203 | /** 204 | * 释放资源 205 | */ 206 | public synchronized void releaseCamera() { 207 | try { 208 | if (null != mCamera) { 209 | mCamera.setPreviewCallback(null); 210 | mCamera.stopPreview();//停止预览 211 | mCamera.release(); // 释放相机资源 212 | mCamera = null; 213 | } 214 | if (null != mHolder) { 215 | mHolder.removeCallback(this); 216 | mHolder = null; 217 | } 218 | } catch (Exception e) { 219 | e.printStackTrace(); 220 | } 221 | } 222 | 223 | } -------------------------------------------------------------------------------- /app/src/main/java/com/dong/circlecamera/view/CircleCameraLayout.java: -------------------------------------------------------------------------------- 1 | package com.dong.circlecamera.view; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Color; 7 | import android.graphics.Outline; 8 | import android.graphics.Rect; 9 | import android.hardware.Camera; 10 | import android.os.Build; 11 | import android.os.Handler; 12 | import android.os.Looper; 13 | import android.support.annotation.RequiresApi; 14 | import android.support.v4.content.ContextCompat; 15 | import android.util.AttributeSet; 16 | import android.util.Log; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.view.ViewOutlineProvider; 20 | import android.widget.FrameLayout; 21 | import android.widget.RelativeLayout; 22 | 23 | 24 | import com.dong.circlecamera.R; 25 | 26 | import java.util.Timer; 27 | import java.util.TimerTask; 28 | 29 | /** 30 | * Created by dong on 2018/5/23. 31 | */ 32 | 33 | public class CircleCameraLayout extends RelativeLayout { 34 | 35 | public CircleCameraLayout(Context context) { 36 | super(context); 37 | init(context, null, -1, -1); 38 | } 39 | 40 | public CircleCameraLayout(Context context, AttributeSet attrs) { 41 | super(context, attrs); 42 | init(context, attrs, -1, -1); 43 | } 44 | 45 | public CircleCameraLayout(Context context, AttributeSet attrs, int defStyleAttr) { 46 | super(context, attrs, defStyleAttr); 47 | init(context, attrs, defStyleAttr, -1); 48 | } 49 | 50 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 51 | public CircleCameraLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 52 | super(context, attrs, defStyleAttr, defStyleRes); 53 | init(context, attrs, defStyleAttr, defStyleRes); 54 | } 55 | 56 | 57 | private Timer timer; 58 | private TimerTask pressTask; 59 | private Context mContext; 60 | private int circleWidth = 0;//指定半径 61 | private int borderWidth = 0;//指定边框 62 | private CameraPreview cameraPreview;//摄像预览 63 | 64 | private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 65 | mContext = context; 66 | timer = new Timer(); 67 | if (attrs != null && defStyleAttr == -1 && defStyleRes == -1) { 68 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleCameraLayout, defStyleAttr, defStyleRes); 69 | circleWidth = (int) typedArray.getDimension(R.styleable.CircleCameraLayout_circle_camera_width, ViewGroup.LayoutParams.WRAP_CONTENT); 70 | borderWidth = (int) typedArray.getDimension(R.styleable.CircleCameraLayout_border_width, 5); 71 | typedArray.recycle(); 72 | } 73 | startView(); 74 | } 75 | 76 | /** 77 | * 设置照相预览 78 | * 79 | * @param cameraPreview 80 | */ 81 | public void setCameraPreview(CameraPreview cameraPreview) { 82 | this.cameraPreview = cameraPreview; 83 | } 84 | 85 | /** 86 | * 释放回收 87 | */ 88 | public void release() { 89 | if (null != pressTask) { 90 | pressTask.cancel(); 91 | pressTask = null; 92 | } 93 | if (null != timer) { 94 | timer.cancel(); 95 | timer = null; 96 | } 97 | } 98 | 99 | //延时启动摄像头 100 | public void startView() { 101 | pressTask = new TimerTask() { 102 | @Override 103 | public void run() { 104 | new Handler(Looper.getMainLooper()).post(new Runnable() { 105 | @Override 106 | public void run() { 107 | pressTask.cancel(); 108 | pressTask = null; 109 | if (null != cameraPreview) { 110 | show(); 111 | } else { 112 | startView(); 113 | } 114 | } 115 | }); 116 | } 117 | }; 118 | timer.schedule(pressTask, 50); 119 | } 120 | 121 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 122 | private void show() { 123 | //cmaera根view--layout 124 | RelativeLayout cameraRoot = new RelativeLayout(mContext); 125 | RelativeLayout.LayoutParams rootParams = new RelativeLayout.LayoutParams( 126 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 127 | rootParams.addRule(CENTER_IN_PARENT, TRUE); 128 | cameraRoot.setBackgroundColor(Color.TRANSPARENT); 129 | cameraRoot.setClipChildren(false); 130 | 131 | 132 | //camera--layout 133 | FrameLayout cameraLayout = new FrameLayout(mContext); 134 | Camera.Size preSize = cameraPreview.getCameraSize(); 135 | int cameraHeight = (int) ((float) preSize.width / (float) preSize.height * circleWidth); 136 | RelativeLayout.LayoutParams cameraParams = new RelativeLayout.LayoutParams(circleWidth, cameraHeight); 137 | cameraParams.addRule(CENTER_IN_PARENT, TRUE); 138 | cameraLayout.setLayoutParams(cameraParams); 139 | cameraLayout.addView(cameraPreview); 140 | 141 | cameraLayout.setOutlineProvider(viewOutlineProvider);//把自定义的轮廓提供者设置给imageView 142 | cameraLayout.setClipToOutline(true);//开启裁剪 143 | 144 | //circleView--layout 145 | // CircleView circleView = new CircleView(mContext); 146 | CircleView2 circleView = new CircleView2(mContext); 147 | circleView.setBorderWidth(circleWidth, borderWidth); 148 | 149 | //设置margin值---隐藏超出部分布局 150 | int margin = (cameraHeight - circleWidth) / 2 - borderWidth / 2; 151 | rootParams.setMargins(0, -margin, 0, -margin); 152 | cameraRoot.setLayoutParams(rootParams); 153 | 154 | //添加camera 155 | cameraRoot.addView(cameraLayout); 156 | //添加circle 157 | cameraRoot.addView(circleView); 158 | //添加根布局 159 | this.addView(cameraRoot); 160 | } 161 | 162 | //自定义一个轮廓提供者 163 | public ViewOutlineProvider viewOutlineProvider = new ViewOutlineProvider() { 164 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 165 | @Override 166 | public void getOutline(View view, Outline outline) { 167 | //裁剪成一个圆形 168 | int left0 = 0; 169 | int top0 = (view.getHeight() - view.getWidth()) / 2; 170 | int right0 = view.getWidth(); 171 | int bottom0 = (view.getHeight() - view.getWidth()) / 2 + view.getWidth(); 172 | outline.setOval(left0, top0, right0, bottom0); 173 | } 174 | }; 175 | 176 | } 177 | -------------------------------------------------------------------------------- /app/src/main/java/com/dong/circlecamera/view/CircleView.java: -------------------------------------------------------------------------------- 1 | package com.dong.circlecamera.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.PorterDuff; 9 | import android.graphics.PorterDuffXfermode; 10 | import android.os.Build; 11 | import android.support.annotation.Nullable; 12 | import android.util.AttributeSet; 13 | import android.view.View; 14 | import android.widget.RelativeLayout; 15 | 16 | import com.dong.circlecamera.R; 17 | 18 | 19 | /** 20 | * Created by dong on 2018/5/23. 21 | */ 22 | 23 | public class CircleView extends View { 24 | 25 | public CircleView(Context context) { 26 | super(context); 27 | init(); 28 | } 29 | 30 | public CircleView(Context context, @Nullable AttributeSet attrs) { 31 | super(context, attrs); 32 | init(); 33 | } 34 | 35 | public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 36 | super(context, attrs, defStyleAttr); 37 | init(); 38 | } 39 | 40 | private Paint borderPaint; 41 | private int borderWidth; 42 | 43 | /** 44 | * @param circleWidth 指定view宽高 45 | * @param borderWidth 边框宽度 46 | */ 47 | public void setBorderWidth(int circleWidth, int borderWidth) { 48 | this.borderWidth = borderWidth; 49 | RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(circleWidth, circleWidth + borderWidth * 2); 50 | params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); 51 | setLayoutParams(params); 52 | } 53 | 54 | private void init() { 55 | int borderColor = getResources().getColor(R.color.colorAccent); 56 | if (null == borderPaint){ 57 | borderPaint = new Paint(); 58 | borderPaint.setColor(borderColor); 59 | borderPaint.setStyle(Paint.Style.STROKE); 60 | borderPaint.setAntiAlias(true);//抗锯齿 61 | borderPaint.setDither(true);//防抖动 62 | } 63 | } 64 | 65 | @Override 66 | protected void onDraw(Canvas canvas) { 67 | super.onDraw(canvas); 68 | //画边框 69 | if (borderWidth != 0) { 70 | borderPaint.setStrokeWidth(borderWidth); 71 | float x = getWidth() / 2.0F; 72 | float y = getHeight() / 2.0F; 73 | float radius = getWidth() / 2.0F - borderWidth / 2.0F + 1; 74 | canvas.drawCircle(x, y, radius, borderPaint); 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/dong/circlecamera/view/CircleView2.java: -------------------------------------------------------------------------------- 1 | package com.dong.circlecamera.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.PorterDuff; 9 | import android.graphics.PorterDuffXfermode; 10 | import android.graphics.RectF; 11 | import android.os.Build; 12 | import android.support.annotation.Nullable; 13 | import android.util.AttributeSet; 14 | import android.util.Log; 15 | import android.view.View; 16 | import android.widget.RelativeLayout; 17 | 18 | import com.dong.circlecamera.R; 19 | 20 | 21 | /** 22 | * Created by dong on 2018/5/23. 23 | * kl 24 | */ 25 | 26 | public class CircleView2 extends View { 27 | 28 | public CircleView2(Context context) { 29 | super(context); 30 | init(); 31 | } 32 | 33 | public CircleView2(Context context, @Nullable AttributeSet attrs) { 34 | super(context, attrs); 35 | init(); 36 | } 37 | 38 | public CircleView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 39 | super(context, attrs, defStyleAttr); 40 | init(); 41 | } 42 | 43 | private Paint paint; 44 | private int borderWidth; 45 | private RectF rectF; 46 | 47 | /** 48 | * @param circleWidth 指定view宽高 49 | * @param borderWidth 边框宽度 50 | */ 51 | public void setBorderWidth(int circleWidth, int borderWidth) { 52 | this.borderWidth = borderWidth; 53 | RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(circleWidth, circleWidth + borderWidth * 2); 54 | params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); 55 | setLayoutParams(params); 56 | } 57 | 58 | private void init() { 59 | setBackgroundColor(Color.TRANSPARENT); 60 | //圆形边框 61 | int borderColor = getResources().getColor(R.color.colorAccent); 62 | paint = new Paint(); 63 | paint.setColor(borderColor); 64 | paint.setStyle(Paint.Style.STROKE); 65 | paint.setAntiAlias(true);//抗锯齿 66 | paint.setDither(true);//防抖动 67 | } 68 | 69 | @Override 70 | protected void onDraw(Canvas canvas) { 71 | super.onDraw(canvas); 72 | if (borderWidth != 0) { 73 | paint.setStrokeWidth(borderWidth); 74 | int left = borderWidth / 2; 75 | int top = (getHeight() - (getWidth())) / 2 + borderWidth / 2; 76 | int right = getWidth() - borderWidth / 2; 77 | int bottom = (getHeight() - getWidth()) / 2 + getWidth() - borderWidth / 2; 78 | 79 | if (null == rectF) rectF = new RectF(left, top, right, bottom); 80 | canvas.drawArc(rectF, 0, 360, false, paint); 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/dong/circlecamera/view/Util.java: -------------------------------------------------------------------------------- 1 | package com.dong.circlecamera.view; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageManager; 5 | import android.hardware.Camera; 6 | import android.support.v4.content.ContextCompat; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Created by dong on 2018/5/23. 12 | */ 13 | 14 | public class Util { 15 | 16 | /** 17 | * 通过对比得到与宽高比最接近的预览尺寸(如果有相同尺寸,优先选择) 18 | * 19 | * @param isPortrait 是否竖屏 20 | * @param surfaceWidth 需要被进行对比的原宽 21 | * @param surfaceHeight 需要被进行对比的原高 22 | * @param preSizeList 需要对比的预览尺寸列表 23 | * @return 得到与原宽高比例最接近的尺寸 24 | */ 25 | public static Camera.Size getCloselyPreSize(boolean isPortrait, int surfaceWidth, int surfaceHeight, List preSizeList) { 26 | int reqTmpWidth; 27 | int reqTmpHeight; 28 | // 当屏幕为垂直的时候需要把宽高值进行调换,保证宽大于高 29 | if (isPortrait) { 30 | reqTmpWidth = surfaceHeight; 31 | reqTmpHeight = surfaceWidth; 32 | } else { 33 | reqTmpWidth = surfaceWidth; 34 | reqTmpHeight = surfaceHeight; 35 | } 36 | //先查找preview中是否存在与surfaceview相同宽高的尺寸 37 | for (Camera.Size size : preSizeList) { 38 | if ((size.width == reqTmpWidth) && (size.height == reqTmpHeight)) { 39 | return size; 40 | } 41 | } 42 | // 得到与传入的宽高比最接近的size 43 | float reqRatio = ((float) reqTmpWidth) / reqTmpHeight; 44 | float curRatio, deltaRatio; 45 | float deltaRatioMin = Float.MAX_VALUE; 46 | Camera.Size retSize = null; 47 | for (Camera.Size size : preSizeList) { 48 | curRatio = ((float) size.width) / size.height; 49 | deltaRatio = Math.abs(reqRatio - curRatio); 50 | if (deltaRatio < deltaRatioMin) { 51 | deltaRatioMin = deltaRatio; 52 | retSize = size; 53 | } 54 | } 55 | return retSize; 56 | } 57 | 58 | 59 | /** 60 | * 检查是否拥有指定的所有权限 61 | */ 62 | public static boolean checkPermissionAllGranted(Context context, String[] permissions) { 63 | for (String permission : permissions) { 64 | if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { 65 | // 只要有一个权限没有被授予, 则直接返回 false 66 | return false; 67 | } 68 | } 69 | return true; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 22 | 23 | 27 | 28 | 29 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 48 | 49 |