相机的使用 --- 拍照,相册导入

一、相机的基本使用

1,获取SurfaceView控件的初始化

        surfaceView = (SurfaceView) findViewById(R.id.surface_sv);
        holder = surfaceView.getHolder();
        surfaceView.setBackgroundColor(0);

        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        holder.addCallback(this);
2,添加CallBack事件监听

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        startPreview(camera, holder);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        camera.stopPreview();
        startPreview(camera, holder);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        releaseCamera();
    }
3,预览相机并设置参数

    /**
     * 预览相机
     */
    private void startPreview(Camera camera, SurfaceHolder holder) {
        try {
            setupCamera(camera);
            camera.setPreviewDisplay(holder);
            //亲测的一个方法 基本覆盖所有手机 将预览矫正
            CameraUtil.getInstance().setCameraDisplayOrientation(this, 0, camera);
            camera.startPreview();
            if (camera.getParameters() != null) {
                Camera.Size size = camera.getParameters().getPreviewSize(); // 获取预览大小
                if (Integer.parseInt(Build.VERSION.SDK) >= 8) {
                    initSurfaceView(size.height, size.width);
                } else {
                    initSurfaceView(size.width, size.height);
                }
            } else {
                Toast.makeText(MainActivity.this, "无法启动相机", Toast.LENGTH_SHORT).show();
                finish();
            }
        } catch (Exception e) {
            Toast.makeText(MainActivity.this, "无法启动相机", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
            finish();
        }
    }

    /**
     * 设置
     */
    private void setupCamera(Camera camera) {
        try {
            Camera.Parameters parameters = camera.getParameters();

            if (parameters.getSupportedFocusModes().contains(
                    Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
            }

            //这里第三个参数为最小尺寸 getPropPreviewSize方法会对从最小尺寸开始升序排列 取出所有支持尺寸的最小尺寸
            Camera.Size previewSize = CameraUtil.getInstance().getPropSizeForHeight(parameters.getSupportedPreviewSizes(), 800);
            parameters.setPreviewSize(previewSize.width, previewSize.height);

            Camera.Size pictrueSize = CameraUtil.getInstance().getPropSizeForHeight(parameters.getSupportedPictureSizes(), 800);
            parameters.setPictureSize(pictrueSize.width, pictrueSize.height);

            camera.setParameters(parameters);

            /**
             * 设置surfaceView的尺寸 因为camera默认是横屏,所以取得支持尺寸也都是横屏的尺寸
             * 我们在startPreview方法里面把它矫正了过来,但是这里我们设置设置surfaceView的尺寸的时候要注意 previewSize.height<previewSize.width
             * previewSize.width才是surfaceView的高度
             * 一般相机都是屏幕的宽度 这里设置为屏幕宽度 高度自适应 你也可以设置自己想要的大小
             *
             */
            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(screenWidth, screenHeight);
            surfaceView.setLayoutParams(params);
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(MainActivity.this, "无法启动相机", Toast.LENGTH_SHORT).show();
            finish();
        }
    }
4,纠正和矫正Surfaceview

    /**
     * 初始化SurfaceView
     *
     * @param camera_w
     * @param camera_h
     */

    private void initSurfaceView(int camera_w, int camera_h) {

        int height = MainApplication.getInstance().getHeight()
                - ScreenUtil.getStatusBarHeight();
        int width = MainApplication.getInstance().getWidth();


        int w = (int) ((height * camera_w) / camera_h);
        int h = (int) ((camera_h * width) / camera_w);

        int h_outer = 0;
        int w_outer = 0;

        if (w <= width) {
            Log.e("", "高度为屏幕高度");
            h_outer = height;
            w_outer = w;
        } else if (h <= height) {
            Log.e("", "宽度为屏幕宽度");// ..
            h_outer = h;
            w_outer = width;
        } else {
            Log.e("", "都不合适========================");
        }

        if (w_outer > 0) {
            if (w_outer < width || h_outer < height) {
                //无法铺满全屏
                float scale_w = (float) width / (float) w_outer;
                float scale_h = (float) height / (float) h_outer;
                float scale = ((scale_h - scale_w) > 0) ? scale_h : scale_w;
                w_outer = (int) (w_outer * scale);
                h_outer = (int) (h_outer * scale);
            }
            preViewSize[0] = w_outer;
            preViewSize[1] = h_outer;
            FrameLayout.LayoutParams lpsurface = new FrameLayout.LayoutParams(w_outer, h_outer);
//            lpsurface.setMargins((width-w_outer)/2,(height-h_outer)/2,0,0);
            surfaceView.setLayoutParams(lpsurface);
            Log.e("111", "相机预览:" + camera_w + "x" + camera_h);//960x1280
            Log.e("111", "surfaceview :" + w_outer + "x" + h_outer);//1305x1740   1080x1440
        }

    }
5,合适的地方释放相机资源

    /**
     * 释放相机资源
     */
    private void releaseCamera() {
        if (camera != null) {
            camera.setPreviewCallback(null);
            camera.stopPreview();
            camera.release();
            camera = null;
        }
    }

此时,相机应该可以正常使用。

【在4中,适配不同的相机预览,便于获取图片不变形】

6,生命周期中的维护

    @Override
    protected void onResume() {
        super.onResume();
        if (camera == null) {
            camera = getCamera();
            if (holder != null) {
                startPreview(camera, holder);
            }
        }
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        releaseCamera();
    }

    /**
     * 获取Camera实例
     *
     * @return
     */
    private Camera getCamera() {
        Camera camera = null;
        try {
            camera = Camera.open(0);
        } catch (Exception e) {

        }
        return camera;
    }

7,拍照

private void captrue() {
        camera.takePicture(null, null, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                //将data 转换为位图 或者你也可以直接保存为文件使用 FileOutputStream
                //这里我相信大部分都有其他用处把 比如加个水印 后续再讲解
                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                Bitmap saveBitmap = CameraUtil.getInstance().setTakePicktrueOrientation(0, bitmap);
                bitmap.recycle();

                bitmap = Bitmap.createScaledBitmap(saveBitmap, preViewSize[0], preViewSize[1], true);
                saveBitmap.recycle();
                rotateMIV.setmSrc(bitmap);

                changeToShowPic();
            }
        });
    }

二、图片处理   ---  旋转,缩放,截图
1,图片处理的主要实现类,由以下自定义控件实现
@SuppressLint("AppCompatCustomView")
public class MatrixImageView extends ImageView {
    private static final int MODE_NONE = 0x00123;// 默认的触摸模式
    private static final int MODE_DRAG = 0x00321;// 拖拽模式
    private static final int MODE_ZOOM = 0x00132;// 缩放or旋转模式

    private int mode;// 当前的触摸模式

    private float preMove = 1F;// 上一次手指移动的距离
    private float saveRotate = 0F;// 保存了的角度值
    private float rotate = 0F;// 旋转的角度

    private float[] preEventCoor;// 上一次各触摸点的座标集合

    private PointF startPointF, midPointF;// 起点、中点对象


    private Matrix currentMatrix, savedMatrix;// 当前和保存了的Matrix对象


    /**
     * 新增截图保存功能
     * 旋转展示的图片和页面上截图的矩形运算,形成新的图片,将图片保存成为新图片即可!
     * 1,触发事件
     * 2,截图矩形获取
     * 3,图片保存 -- 保存路径
     */

    /**
     * 保存文件的路径
     */
    private String fileURL;


    /**
     * 单字Code码 -- 文件保存名称
     */
    private String fontCode;


    /**
     * 截图矩形
     */
    private Rect cutRect;

    //原始图片
    private Bitmap mSrc;

    //控件的宽度
    private int mWidth;

    // 控件的高度
    private int mHeight;

    private PaintFlagsDrawFilter mDrawFilter;

    int i = 0;
    private Context context;

    public MatrixImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;

        // 初始化
        init();
    }

    /**
     * 初始化
     */
    private void init() {
        // 实例化对象
        currentMatrix = new Matrix();
        savedMatrix = new Matrix();
        startPointF = new PointF();
        midPointF = new PointF();
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        // 模式初始化
        mode = MODE_NONE;

        Drawable drawable = getDrawable();
        try {
            mSrc = drawableToBitamp(drawable);
        } catch (Exception e) {
            mSrc = null;
        }

        if (mSrc == null) {
            //如果为空,赋默认值
            mSrc = BitmapFactory.decodeResource(getResources(), R.mipmap.desc6, null);
        }

    }

    /**
     * 计算控件的高度和宽度
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 设置宽度
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);

        //match_parent或者设置的精确值获取
        //MeasureSpec.EXACTLY
        if (specMode == MeasureSpec.EXACTLY) {
            mWidth = specSize;
        } else {
            // 由图片决定的宽
            //getPaddingLeft(),getPaddingRight()这两个值是控件属性的向内偏移的距离值,所以的一起计算
            //区别于layout_marginLeft,两个控件的左间距值设置
            int desireByImg = getPaddingLeft() + getPaddingRight()
                    + mSrc.getWidth();

            // wrap_content
            if (specMode == MeasureSpec.AT_MOST) {
                //所以最小的值,宽度的话是左右内偏移距离之和
                mWidth = Math.min(desireByImg, specSize);
            } else

                mWidth = desireByImg;
        }

        // 设置高度,部分解释同上
        specMode = MeasureSpec.getMode(heightMeasureSpec);
        specSize = MeasureSpec.getSize(heightMeasureSpec);

        //match_parent或者设置的精确值获取
        //MeasureSpec.EXACTLY
        if (specMode == MeasureSpec.EXACTLY) {
            mHeight = specSize;
        } else {
            int desire = getPaddingTop() + getPaddingBottom()
                    + mSrc.getHeight();

            // wrap_content
            if (specMode == MeasureSpec.AT_MOST) {
                mHeight = Math.min(desire, specSize);
            } else
                mHeight = desire;
        }

        //计算好的宽度以及高度是值,设置进去
        setMeasuredDimension(mWidth, mHeight);
    }

    //drawable转bitmap
    private Bitmap drawableToBitamp(Drawable drawable) {
        //从控件的src获取背景,也是drawable文件获取
        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bd = (BitmapDrawable) drawable;
            return bd.getBitmap();
        }

        //如果没有绘图一个,只不过是空白的图片
//        int w = drawable.getIntrinsicWidth();
//        int h = drawable.getIntrinsicHeight();
        int w = MainApplication.getInstance().getWidth();
        int h = MainApplication.getInstance().getHeight();

        Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, w, h);
        drawable.draw(canvas);
        return bitmap;
    }

    final Paint paint = new Paint();

    @Override
    protected void onDraw(Canvas canvas) {
        //消除锯齿, 图片旋转后的锯齿消除不成功,实在不行图片边缘加一些白色像素点
        canvas.setDrawFilter(mDrawFilter);
        //画经过Matrix变化后的图
        canvas.drawBitmap(mSrc, currentMatrix, null);

        /**
         * 测试切图区域和显示区域一致
         */
        if (cutRect != null) {
            paint.setColor(Color.RED);
            canvas.drawRect(cutRect, paint);
        }
    }


    /**
     * 裁剪图片并保存到本地
     */
    public boolean cutBitmapAndSave() {
        try {
            Bitmap out = Bitmap.createBitmap(cutRect.width(), cutRect.height(), Bitmap.Config.ARGB_8888);
            //画布在右上角
            Canvas outC = new Canvas(out);
            //平移画布到和截图区域一致【注意反向】
            outC.translate(-cutRect.left, -cutRect.top);
            outC.drawBitmap(mSrc, currentMatrix, null);

            String fileParent = Environment.getExternalStorageDirectory().toString() + "/camera/demo/";
            File fileDirectory = new File(fileParent);
            if (!fileDirectory.exists()) {
                boolean mkdirs = fileDirectory.mkdirs();
                Log.i("parent", mkdirs + "");
                if (mkdirs) {
                    File file = new File(fileParent + System.currentTimeMillis() + ".png");
                    if (!file.exists()) {
                        boolean newFile = file.createNewFile();
                        if (newFile) {
                            BitmapUtil.savePNG_After(MainApplication.getInstance(), out, file.toString(), 20);
                            return true;
                        } else {
                            Toast.makeText(context, "创建文件失败", Toast.LENGTH_SHORT).show();
                            return false;
                        }
                    } else {
                        BitmapUtil.savePNG_After(MainApplication.getInstance(), out, file.toString(), 20);
                        return true;
                    }
                } else {
                    Toast.makeText(context, "创建文件夹失败", Toast.LENGTH_SHORT).show();
                    return false;
                }
            } else {
                File file = new File(fileParent + System.currentTimeMillis() + ".png");
                if (!file.exists()) {
                    boolean newFile = file.createNewFile();
                    if (newFile) {
                        BitmapUtil.savePNG_After(MainApplication.getInstance(), out, file.toString(), 20);
                        return true;
                    } else {
                        Toast.makeText(context, "创建文件失败", Toast.LENGTH_SHORT).show();
                        return false;
                    }
                } else {
                    BitmapUtil.savePNG_After(MainApplication.getInstance(), out, file.toString(), 20);
                    return true;
                }
            }
//            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:// 单点接触屏幕时
                savedMatrix.set(currentMatrix);
                startPointF.set(event.getX(), event.getY());
                //单点触摸是移动模式
                mode = MODE_DRAG;
                preEventCoor = null;
                break;
            case MotionEvent.ACTION_POINTER_DOWN:// 第二个点接触屏幕时
                preMove = calSpacing(event);
                if (preMove > 10F) {
                    savedMatrix.set(currentMatrix);
                    // 计算两个触摸点的中点座标
                    calMidPoint(midPointF, event);
                    //两点是旋转或者缩放模式
                    mode = MODE_ZOOM;
                }
                preEventCoor = new float[4];
                preEventCoor[0] = event.getX(0);
                preEventCoor[1] = event.getX(1);
                preEventCoor[2] = event.getY(0);
                preEventCoor[3] = event.getY(1);
                saveRotate = calRotation(event);
                break;
            case MotionEvent.ACTION_UP:// 单点离开屏幕时
            case MotionEvent.ACTION_POINTER_UP:// 第二个点离开屏幕时
                mode = MODE_NONE;
                preEventCoor = null;
                break;
            case MotionEvent.ACTION_MOVE:// 触摸点移动时
            /*
             * 单点触控拖拽平移
             */
                if (mode == MODE_DRAG) {
                    currentMatrix.set(savedMatrix);
                    float dx = event.getX() - startPointF.x;
                    float dy = event.getY() - startPointF.y;
                    currentMatrix.postTranslate(dx, dy);
                }
            /*
             * 两点触控拖放旋转
             */
                else if (mode == MODE_ZOOM && event.getPointerCount() == 2) {
                    float currentMove = calSpacing(event);
                    currentMatrix.set(savedMatrix);
                /*
                 * 指尖移动距离大于10F缩放
                 */
                    if (currentMove > 10F) {
                        float scale = currentMove / preMove;
                        currentMatrix.postScale(scale, scale, midPointF.x, midPointF.y);
                    }
                /*
                 * 保持两点时旋转
                 */
                    if (preEventCoor != null) {
                        rotate = calRotation(event);
                        r = rotate - saveRotate;
                        currentMatrix.postRotate(r, getMeasuredWidth() / 2, getMeasuredHeight() / 2);
                    }
                }
                break;
        }

        setImageMatrix(currentMatrix);
        return true;
    }

    float r;

    /**
     * 计算两个触摸点间的距离
     */
    private float calSpacing(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    /**
     * 计算两个触摸点的中点座标
     */
    private void calMidPoint(PointF point, MotionEvent event) {
        float x = event.getX(0) + event.getX(1);
        float y = event.getY(0) + event.getY(1);
        point.set(x / 2, y / 2);
    }

    /**
     * 计算旋转角度
     *
     * @param
     * @return 角度值
     */
    private float calRotation(MotionEvent event) {
        double deltaX = (event.getX(0) - event.getX(1));
        double deltaY = (event.getY(0) - event.getY(1));
        double radius = Math.atan2(deltaY, deltaX);
        return (float) Math.toDegrees(radius);
    }


    /**
     * 设置原始图片
     *
     * @param mSrc
     */
    public void setmSrc(Bitmap mSrc) {
        this.mSrc = mSrc;
        currentMatrix = new Matrix();
        requestLayout();
        postInvalidate();
    }

    public void setFileURL(String fileURL) {
        this.fileURL = fileURL;
    }

    public void setFontCode(String fontCode) {
        this.fontCode = fontCode;
    }

    public void setCutRect(Rect cutRect) {
        this.cutRect = cutRect;
    }

    /**
     * 更新当前Matrix
     */
    public void setCurrentMatrix() {
        this.currentMatrix = new Matrix();
    }

} 

2,控件的正常使用,需要设置原始图片的默认值

        if (mSrc == null) {
            //如果为空,赋默认值
            mSrc = BitmapFactory.decodeResource(getResources(), R.mipmap.desc6, null);
        }
    /**
     * 设置原始图片
     *
     * @param mSrc
     */
    public void setmSrc(Bitmap mSrc) {
        this.mSrc = mSrc;
        currentMatrix = new Matrix();
        requestLayout();
        postInvalidate();
    }

在切换到显示图片时,对控件设置原始图片,并刷新显示,绘制。


3,在Ondraw()中绘制裁剪区域
      /**
         * 测试切图区域和显示区域一致
         */
        if (cutRect != null) {
            paint.setColor(Color.RED);
            canvas.drawRect(cutRect, paint);
        }

在Activity中获取裁剪区域大小:
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        cutRectIV.post(new Runnable() {
            @Override
            public void run() {
                Rect viewRect = new Rect();
                int[] location = new int[2];
                int[] location1 = new int[2];
//                cutRectIV.getLocalVisibleRect(viewRect);
                cutRectIV.getLocationOnScreen(location);
                contentTakePicRL.getLocationOnScreen(location1);
                /**
                 * getLeft() /getTop() /getBottom() /getRight()
                 * 都是相对父控件的位置
                 */
                //设置截图区域
                viewRect.left = cutRectIV.getLeft();
                viewRect.top = contentTakePicRL.getTop();
                viewRect.bottom = cutRectIV.getBottom() + contentTakePicRL.getTop();
                viewRect.right = cutRectIV.getRight();

                rotateMIV.setCutRect(viewRect);
            }
        });
    }

特别注意:gettop(),getLeft()都是相对父布局,在计算时,父布局与实际所在的位置一定要清晰。



三、相册导入

1,相册导入一般使用系统相册导入
注意点:
(1)自己构建相册需要管理相册内存;
(2)实际倒入效果并不会有很好的提升。
导入时注意对图片进行压缩:

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //获取图片路径
        if (requestCode == IMAGE && resultCode == Activity.RESULT_OK && data != null) {
            Uri selectedImage = data.getData();
            String[] filePathColumns = {MediaStore.Images.Media.DATA};
            Cursor c = getContentResolver().query(selectedImage, filePathColumns, null, null, null);
            c.moveToFirst();
            int columnIndex = c.getColumnIndex(filePathColumns[0]);
            String imagePath = c.getString(columnIndex);

            int[] size = getImageWidthHeight(imagePath);
            int width = screenWidth;
            //导入进来,让宽适配屏幕宽度
            int height = (int) (((float) (screenWidth * size[1])) / ((float) size[0]));


            Bitmap bitmap = BitmapUtil.getBitmapBySize(imagePath, width, height);

            rotateMIV.setmSrc(bitmap);

            changeToShowPic();
            c.close();
        }
    }

现获取图片大小,再依据最后显示大小,压缩图片后再显示。

    public static Bitmap getBitmapBySize(String path, int width, int height) {
        BitmapFactory.Options option = new BitmapFactory.Options();
        option.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, option);
        option.inSampleSize = computeSampleSize(option, -1, width * height);

        option.inJustDecodeBounds = false;
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeFile(path, option);
            if (bitmap.getWidth() != width || bitmap.getHeight() != height) {
                Matrix mx = new Matrix();
                mx.setScale((float) width / (float) bitmap.getWidth(), (float) height / (float) bitmap.getHeight());
                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), mx, true);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return bitmap;
    }

2,保存图片

在图片保存中,存在已经设置权限了,还是创建目录不成功的情形,需要动态设置权限。
onCreate()中添加权限动态访问:

    ActivityCompat.requestPermissions(MainActivity.this, new String[]{android
                .Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);


在权限获取结果中处理文件夹的创建管理:

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    //创建文件夹
                    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                        File file = new File(Environment.getExternalStorageDirectory().toString() + "/camera/demo/");
                        if (!file.exists()) {
                            Log.d("fileCreate", "file create:" + file.mkdirs());
                        }
                    }
                    break;
                }
        }
    }


3,有些手机有虚拟导航,为保持页面的一致,需要管控页面。

    @SuppressLint("NewApi")
    private void hideVirtualGuideBar() {
        //隐藏不显示
        Window window = getWindow();
        WindowManager.LayoutParams params = window.getAttributes();
        params.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE;
        window.setAttributes(params);
    }


因为需求的关系,获取到的图片,还需要做二值化。
且听下回分解。




传送门 -- 源码哦



偶尔会依旧很迷茫,偶尔会找不到自身的定位。总是在跌跌撞撞中找寻。。。


































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章