MTK Camera學(xué)習(xí)第一篇(UI相關(guān))

本篇主要學(xué)習(xí)Camera的UI布局結(jié)構(gòu)

情景模式切換.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構(gòu)建的布局,然后進(jìn)入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>

我們搜索相關(guān)View的id確定它們在CameraAppUiImpl.java中被引用蓉坎,將相關(guān)代碼取出如下:

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方法澳眷,選出相關(guān)聯(lián)的代碼如下

 @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初始化后首先執(zhí)行了如下方法:

    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方法可以查看到所使用的布局文件钳踊,我們確定它就是相機(jī)下方兩個按鈕衷敌,用來切換拍照還是錄相

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;
    ...
}

它的實(shí)現(xiàn)在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生命周期有關(guān)航罗,因此同時將實(shí)現(xiàn)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,并實(shí)現(xiàn)了CameraActivity內(nèi)定義的兩個接口粥血,其內(nèi)同時又?jǐn)U展的自己的接口柏锄,它在界面上顯示為相機(jī)右下方豎排多個場景模式的切換

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();
    }
    ...
}

它的接口如何實(shí)現(xiàn)呢,我們回到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最后響應(yīng)的是RetakeListener或者PlayListener的onClick方法,具體來說在PhotoActor和VideoMode中都有其實(shí)現(xiàn):

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方法的參數(shù)即可

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,一個進(jìn)行縮放的視圖耕突,必然涉及手勢監(jiān)聽的問題笤成,如下:

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;
    }
    ...
}

這里有必要說明一下,它實(shí)現(xiàn)了GestureDispatcherListener這個接口眷茁,而這個接口在什么時候調(diào)用的呢仆邓?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接口又是在什么時候調(diào)用的呢辕翰? GestureRecognizer中有一個內(nèi)部類MyScaleListener浪藻,它實(shí)現(xiàn)了android.view.ScaleGestureDetector.SimpleOnScaleGestureListener這個接口,在此接口的實(shí)現(xiàn)中調(diào)用了Listener接口的相關(guān)方法登刺。其初始化在CameraActivity的initializeForOpeningProcess()方法中

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

第十個ThumbnailViewManager籽腕,它在相機(jī)的右下角,用于顯示拍照后的小圖窗口

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圖標(biāo)顯示节仿,非常重要的設(shè)置項(xiàng),實(shí)現(xiàn)了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中實(shí)現(xiàn)

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;
                }
            }
        }
    };

接口的調(diào)用過程還是比較清楚的廊宪,從SettingListLayout到SettingManager再到CameraActivity
第十三個EffectViewManager矾瘾,定義了一個接口并在CameraAppUiImpl中使用內(nèi)部類實(shí)現(xiàn),界面上顯示上左邊的一個右箭頭

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壕翩,用于擴(kuò)展多級設(shè)置選項(xiàng)

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將決定它呈現(xiàn)的具體位置,在它的所有子類中將調(diào)用此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;
    }

最終根據(jù)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最后在屏幕的右上方荐操。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芜抒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子托启,更是在濱河造成了極大的恐慌宅倒,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屯耸,死亡現(xiàn)場離奇詭異拐迁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)疗绣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門线召,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人多矮,你說我怎么就攤上這事缓淹。” “怎么了塔逃?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵割卖,是天一觀的道長。 經(jīng)常有香客問我患雏,道長,這世上最難降的妖魔是什么罢维? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任淹仑,我火速辦了婚禮,結(jié)果婚禮上肺孵,老公的妹妹穿的比我還像新娘匀借。我一直安慰自己,他們只是感情好平窘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布吓肋。 她就那樣靜靜地躺著,像睡著了一般瑰艘。 火紅的嫁衣襯著肌膚如雪是鬼。 梳的紋絲不亂的頭發(fā)上肤舞,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機(jī)與錄音均蜜,去河邊找鬼李剖。 笑死,一個胖子當(dāng)著我的面吹牛囤耳,可吹牛的內(nèi)容都是我干的篙顺。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼充择,長吁一口氣:“原來是場噩夢啊……” “哼德玫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起椎麦,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤宰僧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后铃剔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撒桨,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年键兜,在試婚紗的時候發(fā)現(xiàn)自己被綠了凤类。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡普气,死狀恐怖谜疤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情现诀,我是刑警寧澤夷磕,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站仔沿,受9級特大地震影響坐桩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜封锉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一绵跷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧成福,春花似錦碾局、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春像啼,著一層夾襖步出監(jiān)牢的瞬間俘闯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工埋合, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留备徐,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓甚颂,卻偏偏與公主長得像蜜猾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子振诬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,749評論 25 707
  • 那些不回家的清早都失了眠 就會想起那個夏天 我在這喧囂里把你尋找 人見人愛的 喵小姐~ 孤獨(dú)的人不能走出房間 現(xiàn)在...
    病you閱讀 284評論 1 2
  • 聽城市的旋律 聊城市的記憶 在陌生的城市 釋放天性 每座城 每天 不同的故事 貪念的時光如白駒過隙 在心底畫下一個...
    子暮言閱讀 215評論 0 1
  • 我這個人對節(jié)日沒什么特別的好感蹭睡。但我很清楚,在年前還會有一個很操蛋的節(jié)日——情人節(jié)赶么。于我而言肩豁,無非就是沖動消費(fèi)的人...
    屋內(nèi)飲酒門外勸水閱讀 606評論 3 1
  • 聽心理教師技能大賽后感 今天到中心小學(xué)觀看了街道舉行的心理教師技能大賽 這是我們街道第一次舉行心理方面的大型比賽...
    咖啡梁拌閱讀 1,228評論 0 0