android動手寫控件系列——老豬叫你寫相機


  前記:Android這個開源而自由的系統,為我們帶來開發便利,同時也埋下太多的深坑。例如調用系統自帶的相機就會出現照片丟失,或者其他各種各樣的問題。因此,看來自定義一個相機十分的必要。

  要自定義相機我們無法要利用surfaceview與自帶camera兩把利器。

  首先了解surfaceview的基本含義:

  通常情況程序的View和用戶響應都是在同一個線程中處理的,這也是為什么處理長時間事件(例如訪問網絡)需要放到另外的線程中去(防止阻塞當前UI線程的操作和繪制)。但是在其他線程中卻不能修改UI元素,例如用后台線程更新自定義View(調用View的在自定義View中的onDraw函數)是不允許的。 如果需要在另外的線程繪制界面、需要迅速的更新界面或則渲染UI界面需要較長的時間,這種情況就要使用SurfaceView了。

  SurfaceView中包含一個Surface對象,而Surface是可以在后台線程中繪制的。Surface屬於 OPhone底層顯示系統。SurfaceView的性質決定了其比較適合一些場景:需要界面迅速更新、對幀率要求較高的情況。使用SurfaceView需要注意以下幾點情況: SurfaceView和SurfaceHolder.Callback函數都從當前SurfaceView窗口線程中調用(一般而言就是程序的主線程)。有關資源狀態要注意和繪制線程之間的同步。 在繪制線程中必須先合法的獲取Surface才能開始繪制內容,在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()之間的狀態為合法的,另外在Surface類型為SURFACE_TYPE_PUSH_BUFFERS時候是不合法的。 額外的繪制線程會消耗系統的資源,在使用SurfaceView的時候要注意這點。

  了解了這么多,我們來開始寫我們的相機。首先,需要surfaceview來判斷刷新界面。同樣surefaceview三要素holder,callback,created.

  使用SurfaceView 只要繼承SurfaceView類並實現(1)SurfaceHolder.Callback接口就可以實現一個自定義的SurfaceView了,(2)SurfaceHolder.Callback在底層的Surface狀態發生變化的時候通知View,SurfaceHolder.Callback具有如下的接口:

  (3)surfaceCreated(SurfaceHolder holder):當Surface第一次創建后會立即調用該函數。程序可以在該函數中做些和繪制界面相關的初始化工作,一般情況下都是在另外的線程來繪制界面,所以不要在這個函數中繪制Surface。

  有了這些理論的鋪墊,我們所需做的就是在surfaceCreated對camera打開使其聚焦,做好拍照的操作:相應的源碼如下:

  

private void initSurfaceView() {
        try {
            camera = Camera.open();// 打開硬件攝像頭,這里導包得時候一定要注意是android.hardware.Camera
            Camera.Parameters parameters = camera.getParameters();// 得到攝像頭的參數
            int PreviewWidth = 0;
            int PreviewHeight = 0;
            List<Camera.Size> size_list = parameters.getSupportedPreviewSizes();

            if (size_list.size() > 0) {
                Iterator<Camera.Size> itor = size_list.iterator();
                while (itor.hasNext()) {
                    Camera.Size cur = itor.next();
                    if (cur.width >= PreviewWidth
                            && cur.height >= PreviewHeight) {
                        PreviewWidth = cur.width;
                        PreviewHeight = cur.height;
                        // break;
                    }
                }
            }

            WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);// 得到窗口管理器
            // Display display = wm.getDefaultDisplay();// 得到當前屏幕

            parameters.setPreviewSize(PreviewWidth, PreviewHeight);// 設置預覽照片的大小
            parameters.setPictureFormat(PixelFormat.JPEG);// 設置照片的格式
            parameters.setJpegQuality(100);// 設置照片的質量
            System.out.println(camera.getParameters().flatten());
            List<Size> picSizeValues = camera.getParameters()
                    .getSupportedPictureSizes();
            if (picSizeValues.get(0).width > picSizeValues.get(picSizeValues
                    .size() - 1).width) {
                for (Size size : picSizeValues) {
                    Log.v("show", size.width + "\n");
                    if (size.width <= 1024) {
                        parameters.setPictureSize(size.width, size.height);// 設置照片的大小,默認是和
                        break;
                    }
                }
            } else {
                for (int i = picSizeValues.size() - 1; i >= 0; i -= 1) {
                    Log.v("show", picSizeValues.get(i).width + "\n");
                    if (picSizeValues.get(i).width <= 1024) {
                        parameters.setPictureSize(picSizeValues.get(i).width,
                                picSizeValues.get(i).height);
                        break;
                    }
                }
            }
            parameters.setFlashMode(parameters.FLASH_MODE_AUTO);
            // 屏幕一樣大
            camera.setParameters(parameters);
            camera.setPreviewDisplay(surface_view.getHolder());// 通過SurfaceView顯示取景畫面
            camera.startPreview();// 開始預覽
            is_preview = true;// 設置是否預覽參數為真
        } catch (IOException e) {
            // Log.e(TAG, e.toString());
        }
    }

  通過代碼,我們清晰看到了我們利用camera對象,他是何物了?他是Android提供的對攝像頭操作的相應的API。我們看到上面的代碼,我們設置camera拍攝的范圍應該控制寬度在1024以下,並且預覽的實在surefaceview范圍內。

  這樣應該簡單相機就做出來了。但是,似乎不是那么完美,我們相機似乎差了什么樣的,首先要有一個手動聚焦光圈。要完成這個功能,工作量不小啊:首先需要一個focusmanager來管理聚焦的操作:他的內部應該是這樣的:

private Camera mCamera;

    private static final String TAG="FocusManager";

    private static final int FOCUS_WIDTH = 80;

    private static final int FOCUS_HEIGHT = 80;
    public interface FocusListener {
        public void onFocusStart(boolean smallAdjust);

        public void onFocusReturns(boolean smallAdjust, boolean success);
    }

    private int mFocusKeepTimeMs = 3000;
    private long mLastFocusTimestamp = 0;

    private Handler mHandler;
    private FocusListener mListener;
    private boolean mIsFocusing;
    public final FocusManager mfocusManager;
    private Object mParametersSync=new  Object();

    public FocusManager(Camera mCamera) {
        mHandler = new Handler();
        mIsFocusing = false;
        this.mCamera = mCamera;
        mfocusManager=this;
        Camera.Parameters params = mCamera.getParameters();
        if (params.getSupportedFocusModes().contains("auto")) {
            params.setFocusMode("auto");
        }

        // Do a first focus after 1 second
        mHandler.postDelayed(new Runnable() {
            public void run() {
                checkFocus();
            }
        }, 1000);
    }

    public void setListener(FocusListener listener) {
        mListener = listener;
    }

    public void checkFocus() {
        long time = System.currentTimeMillis();

        if (time - mLastFocusTimestamp > mFocusKeepTimeMs && !mIsFocusing) {
            refocus();
        } else if (time - mLastFocusTimestamp > mFocusKeepTimeMs * 2) {
            // Force a refocus after 2 times focus failed
            refocus();
        }
    }

    public void refocus() {
        if (doAutofocus(this)) {
            mIsFocusing = true;

            if (mListener != null) {
                mListener.onFocusStart(false);
            }
        }

    }

    private boolean doAutofocus(FocusManager focusManager) {
        if (mCamera != null) {
            try {
                
                // Trigger af
                mCamera.cancelAutoFocus();

                mHandler.post(new Runnable() {
                    public void run() {
                        try {
                            mCamera.autoFocus(mfocusManager);
                        } catch (Exception e) {
                            // Do nothing here
                        }
                    }
                });

            } catch (Exception e) {
                return false;
            }
            return true;
        } else {
            return false;
        }
    }


    @Override
    public void onAutoFocus(boolean success, Camera camera) {
        mLastFocusTimestamp = System.currentTimeMillis();
        mIsFocusing = false;

        if (mListener != null) {
            mListener.onFocusReturns(false, success);
        }

    }

    /***
     * 最大區域
     * @return
     */
     @SuppressLint("NewApi")
    public boolean isFocusAreaSupported() {
            if (mCamera != null) {
                try {
                    return (getParameters().getMaxNumFocusAreas() > 0);
                } catch (Exception e) {
                    return false;
                }
            } else {
                return false;
            }
        }

    private Parameters getParameters() {
        Parameters Parameters=null;
        synchronized (mParametersSync) {
            if (mCamera == null) {
                Log.w("", "getParameters when camera is null");
                return null;
            }

            int tries = 0;
            while (Parameters == null) {
                try {
                    Parameters = mCamera.getParameters();
                    break;
                } catch (RuntimeException e) {
                    Log.e(TAG, "Error while getting parameters: ", e);
                    if (tries < 3) {
                        tries++;
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e1) {
                            e1.printStackTrace();
                        }
                    } else {
                        Log.e(TAG, "Failed to get parameters after 3 tries");
                        break;
                    }
                }
            }
        }

        return Parameters;
    }

    @SuppressLint("NewApi")
    public void setFocusPoint(int x, int y) {
        if (x < -1000 || x > 1000 || y < -1000 || y > 1000) {
            Log.e(TAG, "setFocusPoint: values are not ideal " + "x= " + x + " y= " + y);
            return;
        }
        Camera.Parameters params = getParameters();

        if (params != null && params.getMaxNumFocusAreas() > 0) {
            List<Camera.Area> focusArea = new ArrayList<Camera.Area>();
            focusArea.add(new Camera.Area(new Rect(x, y, x + FOCUS_WIDTH, y + FOCUS_HEIGHT), 1000));

            params.setFocusAreas(focusArea);

            try {
                mCamera.setParameters(params);
            } catch (Exception e) {
            }
        }
    }

  首先,有一個camera對象,來通知我們來操作那個camera對象的聚焦。

  又上而下的來掃描此對象,我們觀察操作最多的是對對焦調整。在checkFocus中,在其一定時間范圍內使其強制對焦。在refocus這個方法中,用戶的自動對焦開始,並且監聽相應對焦狀態的變化,用以調整對焦光圈的變化。而在isFocusAreaSupported方法中,我們要判斷camera對象所支持的參數的聚焦的最大的范圍是否大於0。經過這一系列的判斷邏輯,我們對camera聚焦有了控制了。

  我們接下來要做的就是需要對其對焦的光圈來嘮叨嘮叨。對焦光圈就是相機聚焦區域來顯示了一個光圈:他的類名叫做FocusHudRing。源碼如下:

  

private FocusManager mFocusManager;
    public FocusHudRing(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
    }

    public FocusHudRing(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public FocusHudRing(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    @Override
    @SuppressLint("ClickableViewAccessibility")
    protected void onFinishInflate() {
        // TODO Auto-generated method stub
        super.onFinishInflate();
        setImage(true);
    }

    public void setImage(boolean success) {
        // TODO Auto-generated method stub
        if (success) {
            setImageResource(R.drawable.hud_focus_ring_success);
        } else {
            setImageResource(R.drawable.hud_focus_ring_fail);
        }
    }
    
     public void setPosition(float x, float y) {
            setX(x - getWidth() / 2.0f);
            setY(y - getHeight() / 2.0f);
            applyFocusPoint();
        }

    private void applyFocusPoint() {
        ViewGroup parent = (ViewGroup) getParent();
        if (parent == null) return;

        // We swap X/Y as we have a landscape preview in portrait mode
        float centerPointX = getY() + getHeight() / 2.0f;
        float centerPointY = parent.getWidth() - (getX() + getWidth() / 2.0f);

        centerPointX *= 1000.0f / parent.getHeight();
        centerPointY *= 1000.0f / parent.getWidth();

        centerPointX = (centerPointX - 500.0f) * 2.0f;
        centerPointY = (centerPointY - 500.0f) * 2.0f;
        if (mFocusManager != null) {
            mFocusManager.setFocusPoint((int) centerPointX, (int) centerPointY);
        }
    }
    public boolean onTouch(View view, MotionEvent motionEvent) {
        super.onTouch(view, motionEvent);

        if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {
            applyFocusPoint();
            if (mFocusManager != null) {
                mFocusManager.refocus();
            }
        }
       return true;     
    }

    public FocusManager getFocusManager() {
        return mFocusManager;
    }

    public void setFocusManager(FocusManager mFocusManager) {
        this.mFocusManager = mFocusManager;
    }

  focushudring又不是簡單存在,他又繼承與hudring這個自定義控件,用戶點擊到了某個區域的話,這個控件就會出現在一定區域。就是依靠這個setposition控件,applyfocuspoint就是計算這個圓形,並且開啟動畫。

  有了這兩個類以后,一個自定義聚焦控件就做好,你所做的就是監聽下屏幕的手勢事件,可能我說的不夠清楚,大家請看代碼吧?代碼的位置是:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM