MTK Camera学习第一篇(UI相关)

本篇主要学习Camera的UI布局结构

情景模式切换.png

从布局文件camera.xml开始

<?xml version="1.0" encoding="utf-8"?>
<!-- This layout is shared by phone and tablet in landscape orientation. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/camera_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:splitMotionEvents="false">
    <FrameLayout android:id="@+id/camera_surfaceview_root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"> 
    </FrameLayout>
    <FrameLayout android:id="@+id/camera_app_root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:splitMotionEvents="false">
        <View android:id="@+id/camera_cover"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/black"
            android:visibility="visible"/>
        <include layout="@layout/preview_frame"/>
        <include layout="@layout/view_layers"/>
    </FrameLayout>
</FrameLayout>

一个完全由FrameLayout构建的布局,然后进入view_layers.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/view_layer_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:splitMotionEvents="false">
    <!-- review views here -->
    <FrameLayout android:id="@+id/view_layer_bottom"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:splitMotionEvents="false">
    </FrameLayout>
    <!-- normal views here -->
    <RelativeLayout android:id="@+id/view_layer_normal"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:splitMotionEvents="false"
        >
    </RelativeLayout>
    <FrameLayout android:id="@+id/view_layer_top"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:splitMotionEvents="false">
    </FrameLayout>
    <FrameLayout android:id="@+id/view_layer_shutter"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:splitMotionEvents="false"
        >
    </FrameLayout>
    <FrameLayout android:id="@+id/view_layer_setting"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:splitMotionEvents="false"
        >
    </FrameLayout>
    <!-- overlay views here -->
    <FrameLayout android:id="@+id/view_layer_overlay"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:splitMotionEvents="false">
    </FrameLayout>
</FrameLayout>

我们搜索相关View的id确定它们在CameraAppUiImpl.java中被引用,将相关代码取出如下:

public class CameraAppUiImpl implements ICameraAppUi {
    private ViewGroup mViewLayerBottom;
    private ViewGroup mViewLayerNormal;
    private ViewGroup mViewLayerTop;
    private ViewGroup mViewLayerShutter;
    private ViewGroup mViewLayerSetting;
    private ViewGroup mViewLayerOverlay;

    public CameraAppUiImpl(CameraActivity context) {
        Log.i(TAG, "[CameraAppUiImpl] constructor... ");
        mCameraActivity = context;
        mMainHandler = new MainHandler(context.getMainLooper());
    }

    public void initializeViewGroup() {
        mViewLayerBottom = (ViewGroup) mCameraActivity.findViewById(R.id.view_layer_bottom);
        mViewLayerNormal = (ViewGroup) mCameraActivity.findViewById(R.id.view_layer_normal);
        mViewLayerTop = (ViewGroup) mCameraActivity.findViewById(R.id.view_layer_top);
        mViewLayerShutter = (ViewGroup) mCameraActivity.findViewById(R.id.view_layer_shutter);
        mViewLayerSetting = (ViewGroup) mCameraActivity.findViewById(R.id.view_layer_setting);
        mViewLayerOverlay = (ViewGroup) mCameraActivity.findViewById(R.id.view_layer_overlay);
    }
    ...
}

同时查看CameraActivity.java的onCreate方法,选出相关联的代码如下

 @Override
    public void onCreate(Bundle icicle) {
        ...
        mCameraAppUi = new CameraAppUiImpl(this);
        mCameraAppUi.createCommonView();
        initializeCommonManagers();
        mCameraAppUi.initializeCommonView();

        mCameraDeviceCtrl.setCameraAppUi(mCameraAppUi);
        IFileSaver fileSaver = new FileSaverImpl(mFileSaver);
        IFeatureConfig featureConfig = new FeatureConfigImpl();
        ICameraDeviceManager deviceManager = new CameraDeviceManagerImpl(this,
                mCameraDeviceCtrl);
        mISelfTimeManager = new SelfTimerManager(this, mCameraAppUi);
        mModuleManager = new ModuleManager(this, fileSaver, mCameraAppUi,
                featureConfig, deviceManager, moduleCtrl, mISelfTimeManager);
        mISettingCtrl = mModuleManager.getSettingController();
        mCameraAppUi.setSettingCtrl(mISettingCtrl);
        if (isVideoCaptureIntent() || isVideoWallPaperIntent()) {
            mCameraActor = new VideoActor(this, mModuleManager,
                    ModePicker.MODE_VIDEO);
        } else {
            mCameraActor = new PhotoActor(this, mModuleManager,
                    ModePicker.MODE_PHOTO);
        }
        mCameraDeviceCtrl.setModuleManager(mModuleManager);
        mCameraDeviceCtrl.setSettingCtrl(mISettingCtrl);
        mCameraDeviceCtrl.setCameraActor(mCameraActor);
        mCameraDeviceCtrl.resumeStartUpThread();
        mFileSaver.bindSaverService();
        mOtherDeviceConectedManager = new ExternalDeviceManager(this);
        mOtherDeviceConectedManager.onCreate();
        mOtherDeviceConectedManager.addListener(mListener);

        // only initialize some thing for open
        initializeForOpeningProcess();
        initializeAfterPreview();
        ...
    }

我们看到在CameraAppUiImpl初始化后首先执行了如下方法:

    public void createCommonView() {
        mShutterManager = new ShutterManager(mCameraActivity);
        mInfoManager = new InfoManager(mCameraActivity);
        mRotateProgress = new RotateProgress(mCameraActivity);
        mRemainingManager = new RemainingManager(mCameraActivity);
        mPickerManager = new PickerManager(mCameraActivity);
        mIndicatorManager = new IndicatorManager(mCameraActivity);
        mReviewManager = new ReviewManager(mCameraActivity);
        mRotateDialog = new RotateDialog(mCameraActivity);
        mZoomManager = new ZoomManager(mCameraActivity);
        mThumbnailManager = new ThumbnailViewManager(mCameraActivity);
        if (FeatureSwitcher.isVfbEnable()) {
            mFaceBeautyEntryView = new FaceBeautyEntryView(mCameraActivity); //add for FB entry
        }

        mSettingManager = new SettingManager(mCameraActivity);
        mEffectManager = new EffectViewManager(mCameraActivity, mEffectListener);
        // For tablet
        if (FeatureSwitcher.isSubSettingEnabled()) {
            mSubSettingManager = new SubSettingManager(mCameraActivity);
        }
    }

从第一个ShutterManager开始看,它继承自ViewManager,从它的getView方法可以查看到所使用的布局文件,我们确定它就是相机下方两个按钮,用来切换拍照还是录相

public class ShutterManager extends ViewManager {
    ...
    private ShutterButton mPhotoShutter;
    private ShutterButton mVideoShutter;
    private View mOkButton;
    private View mCancelButton;
    private OnShutterButtonListener mPhotoListener;
    private OnShutterButtonListener mVideoListener;
    private OnClickListener mOklistener;
    private OnClickListener mCancelListener;

    public void setShutterListener(OnShutterButtonListener photoListener,
            OnShutterButtonListener videoListener, OnClickListener okListener,
            OnClickListener cancelListener) {
        mPhotoListener = photoListener;
        mVideoListener = videoListener;
        mOklistener = okListener;
        mCancelListener = cancelListener;
        applyListener();
    }
    ...

}

ShutterButtonListener是一个接口,定义如下:

public class ShutterButton extends RotateImageView implements View.OnLongClickListener {
    private static final String TAG = "ShutterButton";

    /**
     * A callback to be invoked when a ShutterButton's pressed state changes.
     */
    public interface OnShutterButtonListener {
        /**
         * Called when a ShutterButton has been pressed.
         *
         * @param pressed
         *            The ShutterButton that was pressed.
         */
        void onShutterButtonFocus(ShutterButton button, boolean pressed);

        void onShutterButtonClick(ShutterButton button);

        void onShutterButtonLongPressed(ShutterButton button);
    }
    private OnShutterButtonListener mListener;
    private boolean mOldPressed;

    // M: this variable to avoid needless onClick after onLongPressed;
    private boolean mLongPressed;
    ...
}

它的实现在CameraAppUiImpl.java中

    private OnShutterButtonListener mPhotoShutterListener = new OnShutterButtonListener(...);
    private OnShutterButtonListener mVideoShutterListener = new OnShutterButtonListener(...);

    public void applayViewCallbacks() {
        mShutterManager.setShutterListener(mPhotoShutterListener, mVideoShutterListener,
                mCameraActivity.getCameraActor().getOkListener(), mCameraActivity.getCameraActor()
                        .getCancelListener());
    }

第二个InfoManager,同样继承自ViewManager,但它相对简单的多,只是一个TextView的界面

public class InfoManager extends ViewManager {
    private static final String TAG = "InfoManager";

    private TextView mInfoView;
    private CharSequence mInfoText;

    public InfoManager(CameraActivity context) {
        super(context);
    }

    @Override
    protected View getView() {
        View view = inflate(R.layout.onscreen_info);
        mInfoView = (TextView) view.findViewById(R.id.info_view);
        return view;
    }
    ...
}

onscreen_info.xml

<?xml version="1.0" encoding="utf-8"?>
<com.android.camera.ui.RotateLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/on_screen_info"
    style="@style/OnScreenInfoLayout">
    <LinearLayout style="@style/OnScreenInfoGroup">
        <TextView android:id="@+id/info_view"
            style="@style/OnScreenInfoText"/>
    </LinearLayout>
</com.android.camera.ui.RotateLayout>

第三个RotateProgress同样继承自ViewManager,也很简单,界面上显示为正在保存

public class RotateProgress extends ViewManager {
    @SuppressWarnings("unused")
    private static final String TAG = "RotateProgress";

    private ProgressBar mRotateDialogSpinner;
    private TextView mRotateDialogText;

    private String mMessage;

    public RotateProgress(CameraActivity context) {
        super(context, VIEW_LAYER_OVERLAY);
    }

    @Override
    protected View getView() {
        View v = getContext().inflate(R.layout.rotate_progress, getViewLayer());
        mRotateDialogSpinner = (ProgressBar) v.findViewById(R.id.rotate_dialog_spinner);
        mRotateDialogText = (TextView) v.findViewById(R.id.rotate_dialog_text);
        return v;
    }
    ...
}

第四个RemainingManager,它也只是一个显示剩余空间的textview,在某些场景下它将短暂显示,同样继承自ViewManager,但由于其显示与CameraActivity生命周期有关,因此同时将实现CameraActivity的两个接口:

public class RemainingManager extends ViewManager implements
               CameraActivity.Resumable, CameraActivity.OnParametersReadyListener {
    ...
    @Override
    public void begin() {
        if (mWorkerHandler == null) {
            HandlerThread t = new HandlerThread("thumbnail-creation-thread");
            t.start();
            mWorkerHandler = new WorkerHandler(t.getLooper());
            mWorkerHandler.sendEmptyMessage(MSG_UPDATE_STORAGE);
        }
    }

    @Override
    public void resume() {
        Log.d(TAG, "resume()");
        mResumed = true;
        showHint();
    }

    @Override
    public void pause() {
        Log.d(TAG, "pause()");
        mResumed = false;
        if (mStorageHint != null) {
            mStorageHint.cancel();
            mStorageHint = null;
        }
    }

    @Override
    public void finish() {
        if (mWorkerHandler != null) {
            mWorkerHandler.getLooper().quit();
        }
    }
    @Override
    public void onCameraParameterReady() {
        mParametersReady = true;
    }
    ...
}

第五个PickerManager,同样继承自ViewManager,并实现了CameraActivity内定义的两个接口,其内同时又扩展的自己的接口,它在界面上显示为相机右下方竖排多个场景模式的切换

public class PickerManager extends ViewManager implements Listener,
        CameraActivity.OnPreferenceReadyListener, CameraActivity.OnParametersReadyListener {
    private static final String TAG = "PickerManager";

    public interface PickerListener {
        boolean onSlowMotionPicked(String turnon);

        boolean onHdrPicked(String value);

        boolean onGesturePicked(String value);

        boolean onSmilePicked(String value);

        boolean onCameraPicked(int camerId);

        boolean onFlashPicked(String flashMode);

        boolean onStereoPicked(boolean stereoType);

        boolean onModePicked(int mode, String value, ListPreference preference);
    }

    private PickerButton mSlowMotion;
    private PickerButton mGestureShot;
    private PickerButton mHdr;
    private PickerButton mSmileShot;
    private PickerButton mFlashPicker;
    private PickerButton mCameraPicker;
    private PickerButton mStereoPicker;
    private PickerListener mListener;
    private boolean mPreferenceReady;
    private CameraActivity mContext;
    ...

    @Override
    public void onPreferenceReady() {
        Log.i(TAG, "onPreferenceReady()");
        mPreferenceReady = true;
    }

    @Override
    public void onCameraParameterReady() {
        Log.i(TAG, "onCameraParameterReady(), mDefineOrder:" + mDefineOrder + "" +
                ", mPreferenceReady:" + mPreferenceReady);
        if (!mPreferenceReady) {
            return;
        }

        // the max number of button shown on PickerManager UI is 4, Slow motion,
        // hdr, flash, dual camera,
        // stereo camera have high priority, gesture, smile have low priority,
        // but gesture's priority is
        // higher than smile, if the order of button is definite, do not
        // redefine again.
        if (!mDefineOrder) {
            int count = 0;
            for (int i = 0; i < mButtonPriority.length; i++) {
                ListPreference pref = null;
                boolean visible = false;
                int buttonIndex = mButtonPriority[i];
                switch (buttonIndex) {
                case BUTTON_SLOW_MOTION:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_SLOW_MOTION);
                    break;
                case BUTTON_HDR:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_HDR);
                    break;
                case BUTTON_FLASH:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_FLASH);
                    break;
                case BUTTON_CAMERA:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_DUAL_CAMERA);
                    visible = ModeChecker.getCameraPickerVisible(getContext());
                    if (visible) {
                        count++;
                        if (pref != null) {
                            pref.showInSetting(false);
                        }
                    }
                    pref = null;
                    break;
                case BUTTON_STEREO:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_STEREO_MODE);
                    visible = ModeChecker.getStereoPickerVisibile(getContext());
                    if (visible) {
                        count++;
                        if (pref != null) {
                            pref.showInSetting(false);
                        }

                    }
                    pref = null;
                    break;
                case BUTTON_GESTURE_SHOT:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_GESTURE_SHOT);
                    break;
                case BUTTON_SMILE_SHOT:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_SMILE_SHOT);
                    break;
                default:
                    break;
                }

                if (pref != null && pref.getEntries() != null
                        && pref.getEntries().length > 1) {
                    pref.showInSetting(false);
                    count++;
                    if (BUTTON_GESTURE_SHOT == buttonIndex) {
                        sShownStatusRecorder[BUTTON_GESTURE_SHOT] = false;
                    } else if (BUTTON_SMILE_SHOT == buttonIndex) {
                        sShownStatusRecorder[BUTTON_SMILE_SHOT] = false;
                    }
                }

                Log.i(TAG, "count:" + count + ", buttonIndex:" + buttonIndex);
                if (count >= MAX_NUM_OF_SHOWEN) {
                    break;
                }
            }
            mDefineOrder = true;
        } else {
            for (int i = 0; i < mButtonPriority.length; i++) {
                ListPreference pref = null;
                int buttonIndex = mButtonPriority[i];
                switch (buttonIndex) {
                case BUTTON_SLOW_MOTION:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_SLOW_MOTION);
                    break;
                case BUTTON_HDR:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_HDR);
                    break;
                case BUTTON_FLASH:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_FLASH);
                    break;
                case BUTTON_CAMERA:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_DUAL_CAMERA);
                    break;
                case BUTTON_STEREO:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_STEREO_MODE);
                    break;
                case BUTTON_GESTURE_SHOT:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_GESTURE_SHOT);
                    break;
                case BUTTON_SMILE_SHOT:
                    pref = (IconListPreference) getContext().getListPreference(
                            SettingConstants.ROW_SETTING_SMILE_SHOT);
                    break;
                default:
                    break;
                }
                if (pref != null) {
                    pref.showInSetting(sShownStatusRecorder[buttonIndex]);
                }
            }
        }

        refresh();
    }
    ...
}

它的接口如何实现呢,我们回到CameraActivity

    private PickerManager.PickerListener mPickerListener = new PickerManager.PickerListener(...);

    // Here should be lightweight functions!!!
    private void initializeCommonManagers() {
        mModePicker = new ModePicker(this);
        mFileSaver = new FileSaver(this);
        mFrameManager = new FrameManager(this);
        mModePicker.setListener(mModeChangedListener);
        mCameraAppUi.setSettingListener(mSettingListener);
        mCameraAppUi.setPickerListener(mPickerListener);
        mCameraAppUi.addFileSaver(mFileSaver);
        mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        Log.v(TAG, "getSystemService,mPowerManager =" + mPowerManager);
        // For tablet
        if (FeatureSwitcher.isSubSettingEnabled()) {
            mCameraAppUi.setSubSettingListener(mSettingListener);
        }
    }

第六个IndicatorManager,暂不清楚是什么

public class IndicatorManager extends ViewManager implements
        CameraActivity.OnParametersReadyListener, CameraActivity.OnPreferenceReadyListener {
    ...
    @Override
    protected View getView() {
        View view = inflate(R.layout.onscreen_indicators);
        for (int i = 0; i < INDICATOR_COUNT; i++) {
            mViews[i] = (RotateImageView) view.findViewById(VIEW_IDS[i]);
        }
        mIndicatorGroup = view.findViewById(R.id.on_screen_group);
        return view;
    }

    public void onPreferenceReady() {
        for (int i = 0; i < INDICATOR_COUNT; i++) {
            String key = SETTING_KEYS[i];
            mPrefs[i] = getContext().getListPreference(key);
            mDefaults[i] = getContext().getISettingCtrl().getDefaultValue(key);
        }
        mPreferenceReady = true;
    }

    public void onCameraParameterReady() {
        refreshModeIndicator(true);
        refresh();
    }
    ...
}

第七个ReviewManager,暂不确定是什么东西

public class ReviewManager extends ViewManager implements View.OnClickListener {
    private OnClickListener mRetakeLisenter;
    private OnClickListener mPlayListener;
    ...
    @Override
    public void onClick(View view) {
        Log.i(TAG, "onClick, view = " + view);
        OnClickListener listener = null;
        if (mRetakeView == view) {
            // listener = getContext().getCameraActor().getRetakeListener();
            listener = mRetakeLisenter;
        } else {
            // listener = getContext().getCameraActor().getPlayListener();
            listener = mPlayListener;
        }
        // press cancel button will delete the file
        // press ok button will send intent to review the file
        // if press cancel button and ok button quickly, the error will occurs
        if (listener != null && view.isShown()) {
            listener.onClick(view);
        }
        if (LOG) {
            Log.d(TAG, "onClick(" + view + ") listener=" + listener);
        }
    }
    ...
}

onClick最后响应的是RetakeListener或者PlayListener的onClick方法,具体来说在PhotoActor和VideoMode中都有其实现:

PhotoActor.java

    public PhotoActor(CameraActivity context, ModuleManager moduleManager, int mode) {
        super(context);
        ...
        mICameraAppUi.setReviewListener(mRetakeListener, null);
        ...
    }

    private OnClickListener mRetakeListener = new OnClickListener() {
        
        @Override
        public void onClick(View view) {
            if (mIsCameraClosed) {
                Log.i(TAG, "[onClick]mIsCameraClosed = " + mIsCameraClosed);
                return;
            }
            
            mICameraAppUi.hideReview();
            mICameraAppUi.switchShutterType(ShutterButtonType.SHUTTER_TYPE_PHOTO);
            restartPreview(true);
        }
    };

VideoMode.java

    private OnClickListener mReviewPlayListener = new OnClickListener() {
        public void onClick(View v) {
            Log.i(TAG, "[mReviewPlayListener],onClick");
            mVideoModeHelper.startPlayVideoActivity(mCurrentVideoUri, mProfile);
        }
    };

    private OnClickListener mRetakeListener = new OnClickListener() {
        public void onClick(View v) {
            Log.i(TAG, "[mRetakeListener],onClick");
            deleteCurrentVideo();
            mICameraAppUi.hideReview();
            mICameraAppUi.setVideoShutterEnabled(true);
            mICameraAppUi.switchShutterType(ShutterButtonType.SHUTTER_TYPE_VIDEO);
        }
    };

    private void showAlert() {
        ...
            mICameraAppUi.setReviewListener(mRetakeListener, mReviewPlayListener);
        ...  
    }

第八个RotateDialog,就是一个Dialog,注意它的show方法的参数即可

public class RotateDialog extends ViewManager {
    ...
    public void showAlertDialog(String title, String msg, String button1Text, final Runnable r1,
            String button2Text, final Runnable r2) {
        resetValues();
        mTitle = title;
        mMessage = msg;
        mButton1 = button1Text;
        mButton2 = button2Text;
        mRunnable1 = r1;
        mRunnable2 = r2;
        show();
    }
    ...
}

第九个ZoomManager,一个进行缩放的视图,必然涉及手势监听的问题,如下:

public class ZoomManager extends ViewManager implements
        CameraActivity.Resumable, GestureDispatcher.GestureDispatcherListener {
    ...
    @Override
    protected View getView() {
        return null;
    }

    @Override
    public void begin() {
    }

    @Override
    public void resume() {
        mCameraActivity.setGestureDispatcherListener(this);
        Log.i(TAG, "resume()");
    }

    @Override
    public void pause() {
        mCameraActivity.setGestureDispatcherListener(null);
        Log.i(TAG, "pause()");
    }

    @Override
    public void finish() {
    }

    @Override
    public boolean onDown(float x, float y, int width, int height) {
        Log.i(TAG, "onDown(" + x + ", " + y + ")");
        return false;
    }
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
            float velocityY) {
        Log.i(TAG, "[onFling] (" + velocityX + ", " + velocityY + ")");
        return false;
    }

        @Override
    public boolean onScroll(float dx, float dy, float totalX, float totalY) {
        Log.i(TAG, "onScroll(" + dx + ", " + dy + ", " + totalX + ", " + totalY + ")");
        return false;
    }

    @Override
    public boolean onSingleTapUp(float x, float y) {
        Log.i(TAG, "[onSingleTapUp] (" + x + ", " + y + ")");
        return false;
    }
    @Override
    public boolean onSingleTapConfirmed(float x, float y) {
        return false;
    }
    @Override
    public boolean onUp() {
        Log.i(TAG, "onUp");
        return false;
    }

    @Override
    public boolean onDoubleTap(float x, float y) {
        if (!FeatureSwitcher.isSupportDoubleTapUp())
            return false;
        Log.i(TAG, "onDoubleTap(" + x + ", " + y + ") mZoomIndexFactor=" + mZoomIndexFactor
                + ", isAppSupported()=" + isAppSupported() + ", isEnabled()=" + isEnabled());
        if (!isAppSupported() || !isEnabled()) {
            return false;
        }
        int oldIndex = findZoomIndex(mLastZoomRatio);
        int zoomIndex = 0;
        if (oldIndex == 0) {
            zoomIndex = getMaxZoomIndex();
            mZoomIndexFactor = getMaxZoomIndexFactor();
        } else {
            mZoomIndexFactor = ZERO;
        }
        performZoom(zoomIndex, true);
        return true;
    }

    @Override
    public boolean onScale(float focusX, float focusY, float scale) {
        Log.i(TAG, "onScale(" + focusX + ", " + focusY + ", " + scale + ") mZoomIndexFactor="
                + mZoomIndexFactor + ", isAppSupported()=" + isAppSupported()
                + ", isEnabled()=" + isEnabled());
        if (!isAppSupported() || !isEnabled()) {
            return false;
        }
        if (Float.isNaN(scale) || Float.isInfinite(scale)) {
            return false;
        }
        mZoomIndexFactor *= scale;
        if (mZoomIndexFactor <= ZERO) {
            mZoomIndexFactor = ZERO;
        } else if (mZoomIndexFactor >= getMaxZoomIndexFactor()) {
            mZoomIndexFactor = getMaxZoomIndexFactor();
        }
        int zoomIndex = findZoomIndex(Math.round(mZoomIndexFactor * RATIO_FACTOR_RATE));
        performZoom(zoomIndex, true);
        Log.i(TAG, "onScale() mZoomIndexFactor=" + mZoomIndexFactor);
        return true;
    }

    @Override
    public boolean onScaleBegin(float focusX, float focusY) {
    Log.i(TAG, "onScaleBegin(" + focusX + ", " + focusY + ")");
        return true;
    }

    @Override
    public boolean onLongPress(float x, float y) {
        return false;
    }
    ...
}

这里有必要说明一下,它实现了GestureDispatcherListener这个接口,而这个接口在什么时候调用的呢?GestureRecognizer.Listener这个接口中。

public class GestureDispatcher implements GestureRecognizer.Listener,
        CameraActivity.OnOrientationListener{
    ...
    public interface GestureDispatcherListener {
        public boolean onDown(float x, float y, int width, int height);
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
        public boolean onScroll(float dx, float dy, float totalX, float totalY);
        public boolean onSingleTapUp(float x, float y);
        public boolean onSingleTapConfirmed(float x, float y);
        public boolean onUp();
        public boolean onDoubleTap(float x, float y);
        public boolean onScale(float focusX, float focusY, float scale);
        public boolean onScaleBegin(float focusX, float focusY);
        public boolean onLongPress(float x, float y);
    }
    ...
}
public class GestureRecognizer {
    @SuppressWarnings("unused")
    private static final String TAG = "GestureRecognizer";

    public interface Listener {
        boolean onSingleTapUp(float x, float y);
        boolean onSingleTapConfirmed(float x, float y);
        void onLongPress(float x, float y);
        boolean onDoubleTap(float x, float y);
        boolean onScroll(float dx, float dy, float totalX, float totalY);
        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
        boolean onScaleBegin(float focusX, float focusY);
        boolean onScale(float focusX, float focusY, float scale);
        void onScaleEnd();
        void onDown(float x, float y);
        void onUp();
    }
    public GestureRecognizer(Context context, Listener listener) {
        mListener = listener;
        Log.i(TAG, "GestureRecognizer");
        mGestureDetector = new GestureDetector(context, new MyGestureListener(),
                null, true /* ignoreMultitouch */);
        mScaleDetector = new ScaleGestureDetector(
                context, new MyScaleListener());
        mDownUpDetector = new DownUpDetector(new MyDownUpListener());
        mListenerAvaliable = true;
    }
    private class MyScaleListener
            extends ScaleGestureDetector.SimpleOnScaleGestureListener {...};
    ...
}

而这个Listener接口又是在什么时候调用的呢? GestureRecognizer中有一个内部类MyScaleListener,它实现了android.view.ScaleGestureDetector.SimpleOnScaleGestureListener这个接口,在此接口的实现中调用了Listener接口的相关方法。其初始化在CameraActivity的initializeForOpeningProcess()方法中

        if (mGestureDispatcher == null) {
            mGestureDispatcher = new GestureDispatcher(this);
            mGestureRecognizer = new GestureRecognizer(this, mGestureDispatcher);
        }

第十个ThumbnailViewManager,它在相机的右下角,用于显示拍照后的小图窗口

public class ThumbnailViewManager extends ViewManager implements OnClickListener,
        FileSaver.FileSaverListener, CameraActivity.Resumable {
    ...
    @Override
    public void begin() {
        Log.i(TAG, "[begin]...");
        if (mWorkerHandler == null) {
            HandlerThread t = new HandlerThread("thumbnail-creation-thread");
            t.start();
            mWorkerHandler = new WorkerHandler(t.getLooper());
        }
        // move register broadcast from resume to begin, since when Gallery
        // start new activity
        // to edit, will not receive this broadcast. And LocalBroadcastManager
        // use inside process
        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getContext());
        manager.registerReceiver(mUpdatePictureReceiver, mUpdatePictureFilter);
    }

    @Override
    public void resume() {
        Log.i(TAG, "[resume]mResumed = " + mResumed);
        if (!mResumed) {
            getContext().registerReceiver(mIpoShutdownReceiver, mIpoShutdownFilter);
            if (isShowing() && !mIsSavingThumbnail && !getContext().isSecureCamera()) {
                // if the ThumbnailView is not showed, do not get last
                // thumbnail.
                getLastThumbnail();
            }
            mResumed = true;
        }
    }

    @Override
    public void pause() {
        Log.i(TAG, "[pause]mResumed =" + mResumed);
        if (mResumed) {
            getContext().unregisterReceiver(mIpoShutdownReceiver);
            cancelLoadThumbnail();
            saveThumbnailToFile();
            mResumed = false;
        }
        mWorkerHandler.sendEmptyMessage(MSG_RELEASE_URI);
    }

    @Override
    public void setEnabled(boolean enabled) {
        Log.d(TAG, "[setEnabled]enabled = " + enabled);
        super.setEnabled(enabled);
        if (mThumbnailView != null) {
            mThumbnailView.setEnabled(enabled);
            mThumbnailView.setClickable(enabled);
        }
    }

    @Override
    public void finish() {
        Log.i(TAG, "[finish]...");
        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getContext());
        manager.unregisterReceiver(mUpdatePictureReceiver);
        if (mWorkerHandler != null) {
            mWorkerHandler.getLooper().quit();
        }
    }

    @Override
    protected View getView() {
        View view = inflate(R.layout.thumbnail);
        mThumbnailView = (RotateImageView) view.findViewById(R.id.thumbnail);
        mThumbnailView.setOnClickListener(this);
        mPreviewThumb = (RotateImageView) view.findViewById(R.id.preview_thumb);
        return view;
    }

    @Override
    protected void onRefresh() {
        Log.i(TAG, "[onRefresh]...");
        updateThumbnailView();
    }

    @Override
    public void onFileSaved(SaveRequest request) {
        Log.i(TAG, "[onFileSaved]...");
        // If current URI is not valid, don't create thumbnail.
        if (!request.isIgnoreThumbnail() && request.getUri() != null) {
            Log.i(TAG, "[onFileSaved],send MSG_SAVE_THUMBNAIL.");
            mCurrentSaveRequest = request;
            cancelLoadThumbnail();
            mWorkerHandler.removeMessages(MSG_SAVE_THUMBNAIL);
            mWorkerHandler.sendEmptyMessage(MSG_SAVE_THUMBNAIL);
        }
    }

    @Override
    public void onClick(View v) {
        if (getContext().isFullScreen() && getContext().isCameraIdle() && mThumbnail != null) {
            Uri uri = getThumbnailUri();
            if (uri != null && uri.getPath() != null
                    && uri.getPath().isEmpty()) {
                Log.d(TAG, "[onClick] cancel. uri: " + uri + ", UriPath: "
                        + uri.getPath());
                return;
            }
            Log.i(TAG, "[onClick]call gotoGallery.");
            getContext().gotoGallery();
        }
    }
    ...
}

第十一个FaceBeautyEntryView

public class FaceBeautyEntryView extends ViewManager implements OnClickListener {...}

第十二个SettingManager,左下角setting图标显示,非常重要的设置项,实现了SettingListLayout的Listener接口、CameraActivity的OnPreferenceReadyListener接口与TabHost的OnTabChangeListener接口

public class SettingListLayout extends FrameLayout implements InLineSettingItem.Listener,
        AdapterView.OnItemClickListener, OnScrollListener {
    @SuppressWarnings("unused")
    private static final String TAG = "SettingListLayout";

    private Listener mListener;
    private ArrayList<ListPreference> mListItem = new ArrayList<ListPreference>();
    private ArrayAdapter<ListPreference> mListItemAdapter;
    private InLineSettingItem mLastItem;
    private ListView mSettingList;

    public interface Listener {
        void onSettingChanged(SettingListLayout settingList, ListPreference preference);
        void onStereoCameraSettingChanged(SettingListLayout settingList, ListPreference preference,
                int index, boolean showing);
        void onRestorePreferencesClicked();
        void onVoiceCommandChanged(int index);
    }
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        if ((position == mListItem.size() - 1) && (mListener != null)) {
            mListener.onRestorePreferencesClicked();
        }
    }
    ...
}
public class SettingManager extends ViewManager implements View.OnClickListener,
        SettingListLayout.Listener, CameraActivity.OnPreferenceReadyListener, OnTabChangeListener {
    private static final String TAG = "SettingManager";

    public interface SettingListener {
        void onSharedPreferenceChanged(ListPreference preference);
        void onRestorePreferencesClicked();
        void onSettingContainerShowing(boolean show);
        void onVoiceCommandChanged(int index);
        void onStereoCameraPreferenceChanged(ListPreference preference, int type);
    }
    protected SettingListener mListener;
    ...
}

Manager中定义的接口依然在Activity中实现

private SettingManager.SettingListener mSettingListener = new SettingManager.SettingListener() {
        @Override
        public void onSharedPreferenceChanged(ListPreference preference) {
            Log.d(TAG, "[onSharedPreferenceChanged]");
            if (!isCameraOpened()) {
                return;
            }
            if (preference != null) {
                String settingKey = preference.getKey();
                String value = preference.getValue();
                mISettingCtrl.onSettingChanged(settingKey, value);
            }
            mCameraDeviceCtrl.applyParameters(false);
        }

        @Override
        public void onRestorePreferencesClicked() {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG, "[onRestorePreferencesClicked.run]");
                    mIsFromRestore = true;
                    mCameraActor.onRestoreSettings();
                    mCameraAppUi.collapseViewManager(true);
                    mCameraAppUi.resetSettings();

                    SharedPreferences globalPref = mPreferences.getGlobal();
                    SharedPreferences.Editor editor = globalPref.edit();
                    editor.clear();
                    editor.apply();
                    SettingUtils.upgradeGlobalPreferences(globalPref,
                            CameraHolder.instance().getNumberOfCameras());
                    SettingUtils.writePreferredCameraId(globalPref,
                            mCameraDeviceCtrl.getCameraId());

                    int backCameraId = CameraHolder.instance()
                            .getBackCameraId();
                    SettingUtils.restorePreferences(
                            (Context) CameraActivity.this,
                            getSharePreferences(backCameraId),
                            mCameraDeviceCtrl.getParametersExt(),
                            isNonePickIntent());

                    // restore front camera setting
                    int frontCameraId = CameraHolder.instance()
                            .getFrontCameraId();
                    SettingUtils.restorePreferences(
                            (Context) CameraActivity.this,
                            getSharePreferences(frontCameraId),
                            mCameraDeviceCtrl.getParametersExt(),
                            isNonePickIntent());

                    SettingUtils.initialCameraPictureSize(
                            (Context) CameraActivity.this,
                            mCameraDeviceCtrl.getParametersExt(),
                            getSharePreferences());

                    mISettingCtrl.restoreSetting(backCameraId);
                    mISettingCtrl.restoreSetting(frontCameraId);

                    mCameraAppUi.resetZoom();
                    // we should apply parameters if mode is default too.
                    int mode = mCameraActor.getMode();
                    if (ModePicker.MODE_PHOTO == mode || !isNonePickIntent()
                            || ModePicker.MODE_PHOTO_SGINLE_3D == mode
                            || ModePicker.MODE_PHOTO_3D == mode) {
                        if (ModePicker.MODE_VIDEO == mode
                                && !isNonePickIntent()) {
                            mISettingCtrl.onSettingChanged(
                                    SettingConstants.KEY_VIDEO, SETTING_ON);
                        }
                        mCameraDeviceCtrl.applyParameters(false);
                    } else {
                        mModePicker.setModePreference(null);
                        mModePicker.setCurrentMode(ModePicker.MODE_PHOTO);
                    }
                    mIsFromRestore = false;
                }
            };
            mCameraAppUi.showAlertDialog(null,
                    getString(R.string.confirm_restore_message),
                    getString(android.R.string.cancel), null,
                    getString(android.R.string.ok), runnable);
        }

        @Override
        public void onSettingContainerShowing(boolean show) {
            mModuleManager.onSettingContainerShowing(show);
            if (show) {
            } else {
                if (isFaceBeautyEnable()
                        && getCurrentMode() == ModePicker.MODE_FACE_BEAUTY
                        && !mIsFromRestore) {
                    // when face is detected will show the icon
                    // otherwise don't show
                    Log.i(TAG,
                            "onSettingContainerShowing, will set modify icon stautes true,"
                                    + "and show FB icon");
                    // mLomoEffectsManager.show();
                }
            }
        }

        @Override
        public void onVoiceCommandChanged(int commandId) {
            mModuleManager.onVoiceCommandNotify(commandId);
        }

        @Override
        public void onStereoCameraPreferenceChanged(ListPreference preference,
                int type) {
            if (preference != null
                    && preference.getKey().equals(
                            SettingConstants.KEY_DUAL_CAMERA_MODE)) {
                Log.i(TAG, "onStereoCameraPreferenceChanged, type = " + type);
                if (getCurrentMode() == ModePicker.MODE_STEREO_CAMERA) {
                    if (type == DUAL_CAMERA_ENHANCE_ENABLE) {
                        enableDualCameraExtras();
                    }
                    if (type == DUAL_CAMERA_ENHANCE_DISABLE) {
                        disableDualCameraExtras();
                    }
                    if (type == DUAL_CAMERA_START) {
                        singleDualCameraExtras();
                        mCameraDeviceCtrl.applyParameters(false);
                        return;
                    }
                    mCameraDeviceCtrl.applyParameters(false);
                    return;
                } else {
                    if (type == DUAL_CAMERA_ENHANCE_ENABLE) {
                        enableDualCameraExtras();
                    }
                    if (type == DUAL_CAMERA_ENHANCE_DISABLE) {
                        disableDualCameraExtras();
                    }
                    if (type == DUAL_CAMERA_START) {
                        singleDualCameraExtras();
                        initializeDualCamera(false);
                        return;
                    }
                    if (type == DUAL_CAMERA_SWITCH_IN_REFOCUS) {
                        singleDualCameraExtras();
                        mCameraDeviceCtrl.applyParameters(false);
                        return;
                    }
                    initializeDualCamera(false);
                    return;
                }
            }
        }
    };

接口的调用过程还是比较清楚的,从SettingListLayout到SettingManager再到CameraActivity
第十三个EffectViewManager,定义了一个接口并在CameraAppUiImpl中使用内部类实现,界面上显示上左边的一个右箭头

public class EffectViewManager extends ViewManager implements View.OnClickListener {
    private static final String TAG = "EffectViewManager";

    private EffectListener mListener;
    private RotateImageView mIndicator;

    public interface EffectListener {
        public boolean onClick();
    }
    ...
}

CameraAppUiImpl.java

    private class EffectListenerImpl implements EffectListener {
        @Override
        public boolean onClick() {
            mCameraActivity.getModuleManager().onEffectClick();
            return true;
        }
    }

第十四个SubSettingManager,它继承自SettingManager,用于扩展多级设置选项

public class SubSettingManager extends SettingManager {
    private static final String TAG = "SubSettingManager";

    public SubSettingManager(CameraActivity context) {
        super(context);
    }
    ...
}

最后我们看一下这些Manager的父类ViewManager.java

public abstract class ViewManager implements CameraActivity.OnOrientationListener {
    private final int mViewLayer;
    ...
    public ViewManager(CameraActivity context, int layer) {
        mContext = context;
        mContext.addViewManager(this);
        mContext.addOnOrientationListener(this);
        mOrientation = mContext.getOrientationCompensation();
        mViewLayer = layer;
    }

    public ViewManager(CameraActivity context) {
        this(context, VIEW_LAYER_NORMAL);
    }
    public final View inflate(int layoutId) {
        return getContext().inflate(layoutId, mViewLayer);
    }
    ...
}

这个mViewLayer将决定它呈现的具体位置,在它的所有子类中将调用此inflate方法,而该方法最后指向了CameraActivity的inflate()方法

public View inflate(int layoutId, int layer) {   
    return mCameraAppUi.inflate(layoutId, layer);
}                                                

回到CameraAppUiImpl.java

    public View inflate(int layoutId, int layer) {
        // mViewLayerNormal, mViewLayerBottom and mViewLayerTop are same
        // ViewGroup.
        // Here just use one to inflate child view.
        return mCameraActivity.getLayoutInflater().inflate(layoutId, getViewLayer(layer), false);
    }
    private ViewGroup getViewLayer(int layer) {
        Log.i(TAG, "[getViewLayer] layer:" + layer);
        ViewGroup viewLayer = null;
        switch (layer) {
        case ViewManager.VIEW_LAYER_BOTTOM:
            viewLayer = mViewLayerBottom;
            break;
        case ViewManager.VIEW_LAYER_NORMAL:
            viewLayer = mViewLayerNormal;
            break;
        case ViewManager.VIEW_LAYER_TOP:
            viewLayer = mViewLayerTop;
            break;
        case ViewManager.VIEW_LAYER_SHUTTER:
            viewLayer = mViewLayerShutter;
            break;
        case ViewManager.VIEW_LAYER_SETTING:
            viewLayer = mViewLayerSetting;
            break;
        case ViewManager.VIEW_LAYER_OVERLAY:
            viewLayer = mViewLayerOverlay;
            break;
        default:
            throw new RuntimeException("Wrong layer:" + layer);
        }
        return viewLayer;
    }

最终根据layer值确定managerview填充在哪一层FrameLayout,而具体的位置由managerview所加载的布局文件决定,例如

    style="@style/RemainingLayout"
    <style name="RemainingLayout" parent="ScreenOnMargin">
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_alignParentTop">true</item>
        <item name="android:layout_alignParentRight">true</item>
    </style>

这样就确定了RemainingManager最后在屏幕的右上方。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,472评论 25 707
  • 那些不回家的清早都失了眠 就会想起那个夏天 我在这喧嚣里把你寻找 人见人爱的 喵小姐~ 孤独的人不能走出房间 现在...
    病you阅读 283评论 1 2
  • 听城市的旋律 聊城市的记忆 在陌生的城市 释放天性 每座城 每天 不同的故事 贪念的时光如白驹过隙 在心底画下一个...
    子暮言阅读 214评论 0 1
  • 我这个人对节日没什么特别的好感。但我很清楚,在年前还会有一个很操蛋的节日——情人节。于我而言,无非就是冲动消费的人...
    屋内饮酒门外劝水阅读 604评论 3 1
  • 听心理教师技能大赛后感 今天到中心小学观看了街道举行的心理教师技能大赛 这是我们街道第一次举行心理方面的大型比赛...
    咖啡梁拌阅读 1,223评论 0 0