1. 程式人生 > >關於手機的錄屏功能小記(1)

關於手機的錄屏功能小記(1)

最近在開發的過程中,需要呼叫錄屏功能。

大概功能就是:點選錄屏button,返回主介面並顯示半透明的自定義控制元件。

                        控制元件可以隨意拖動,點選“開始”就開始錄製並由timer來計時。

                        點選“停止”停止錄製,點選“完成”停止錄製並返回app。

本篇先介紹如何實現自定義控制元件的功能:


public class ScreenGathering extends ViewGroup {

    protected Context mContext;
    protected View mMainView;
    protected VideoThread mVideoThread;
    protected Timer mRecordTimer = new Timer();
    protected WindowManager mWindowManager;
    protected WindowManager.LayoutParams mControlParams;

    protected Button mStopRecord;
    protected Button mStartRecord;
    protected Button mCompleteRecord;

    protected int mScreenWidth;
    protected int mScreenHeight;
    protected boolean isRecording = false;

    protected int nowWidth;
    protected int nowHeight;
    protected int initX;
    protected int initY;
    protected float touchX;
    protected float touchY;
    protected boolean isDragging = false;
    protected final int MOVING_START_THRESHOLD = 50;

	//獲取當前GPU支援最大的Media播放解析度
    protected MediaCodecInfo.VideoCapabilities mVideoCapability = getSupportVideoCapability("Video");

	//自定義ViewGroup都要定義onLayout函式
    @Override
    protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
    }

    public ScreenGathering(final Activity activity){
        super(activity);
        mContext = activity.getBaseContext();
		//載入View檢視
        mMainView = View.inflate(activity, R.layout.vie_recording, null);
        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
		//設定LayoutParams引數
        if(Build.VERSION.SDK_INT >= 26) {
            mControlParams = new WindowManager.LayoutParams(
                    WindowManager.LayoutParams.WRAP_CONTENT,
                    WindowManager.LayoutParams.WRAP_CONTENT,
					/*視窗型別:OOS版本特有的視窗型別,OOS以上使用SYSTEM_ALERT_WINDOW許可權必須使用TYPE_APPLICATION_OVERLAY
					* 特性:1.應用的提示視窗始終顯示在狀態列和輸入法等關鍵系統視窗下面
					*       2.系統可以移動使用TYPE_APPLICATION_OVERLAY視窗型別的視窗或者調整其大小,以改善螢幕顯示效果
					*       3.通過開啟通知欄,使用者可以訪問設定來阻止應用顯示使用TYPE_APPLICATION_OVERLAY視窗型別顯示的提示視窗
					*/
                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
					//設定視窗的模式:不許獲得焦點、允許視窗擴充套件到螢幕之外。
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                    PixelFormat.TRANSLUCENT);
        }else {
            mControlParams = new WindowManager.LayoutParams(
                    WindowManager.LayoutParams.WRAP_CONTENT,
                    WindowManager.LayoutParams.WRAP_CONTENT,
					//視窗型別:電話視窗。它用來提供與使用者互動的介面(特別是接電話的介面),這個window通常會置於所有程式window之上,但是會在狀態列之下
                    WindowManager.LayoutParams.TYPE_PHONE,
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                    PixelFormat.TRANSLUCENT);
        }
		//設定視窗位置
        mControlParams.gravity = Gravity.TOP | Gravity.START;

		//獲取螢幕實際大小
        DisplayMetrics displayMetrics = new DisplayMetrics();
        mWindowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
        mScreenWidth = displayMetrics.widthPixels;
        mScreenHeight = displayMetrics.heightPixels;
		
		//獲取當前佈局的實際大小
        mMainView.measure(mScreenWidth, mScreenHeight);
        if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            int temp = mScreenWidth;
            mScreenHeight = mScreenWidth;
            mScreenWidth = temp;
        }
		//確定自定義View的位置在螢幕中間
        mControlParams.x = mScreenWidth / 2 - mMainView.getMeasuredWidth() / 2;
        mControlParams.y = mScreenHeight / 2 - mMainView.getMeasuredWidth() / 2;
        mWindowManager.addView(mMainView, mControlParams);

        mStopRecord = (Button) mMainView.findViewById(R.id.stopRecording);
        mCompleteRecord = (Button) mMainView.findViewById(R.id.finishRecording);
        mStartRecord  = (Button) mMainView.findViewById(R.id.startRecording);

		//點選錄製button,開始錄屏、點選停止,停止錄屏、點選完成,停止錄屏並返回app
        OnClickListener onClickListener = new OnClickListener() {
            @Override
            public void onClick(View v) {
                if(v == mStartRecord){
                    startRecording();
                }
                else if(v == mStopRecord){
                    stopRecording();
                }
                else if(v == mCompleteRecord){
                    mMainView.setVisibility(View.GONE);
                    stopRecording();
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setComponent(new ComponentName(activity.getApplication().getPackageName(), MainActivity.class.getName()));
                    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                    activity.startActivity(intent);
                }
            }
        };

		//長按View可以任意拖動
        OnTouchListener onTouchListener = new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()){
                    case MotionEvent.ACTION_OUTSIDE:
                        return false;
                    case MotionEvent.ACTION_DOWN:
					    //tap down的時候,獲取當前ViewGroup的佈局大小、位置X,Y
                        nowWidth = mMainView.getMeasuredWidth();
                        nowHeight = mMainView.getMeasuredHeight();
                        initX = mControlParams.x;
                        initY = mControlParams.y;
						//獲取相對螢幕的位置 (getX,getY是獲取相對View的位置)
                        touchX = event.getRawX();
                        touchY = event.getRawY();
                        break;
                    case MotionEvent.ACTION_UP:
                        if (!isDragging) {
                            v.performClick();
                        }
                        isDragging = false;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        mControlParams.x = initX + (int) (event.getRawX() - touchX);
                        mControlParams.y = initY + (int) (event.getRawY() - touchY);

                        mControlParams.x = mControlParams.x < 0 ? 0 : mControlParams.x;
                        mControlParams.y = mControlParams.y < 0 ? 0 : mControlParams.y;

						//判斷新的位置有沒有超出螢幕限制
                        mControlParams.x = mControlParams.x > mScreenWidth - nowWidth ? mScreenWidth - nowWidth : mControlParams.x;
                        mControlParams.y = mControlParams.y > mScreenHeight - nowHeight ? mScreenHeight - nowHeight : mControlParams.y;

                        double distance = Math.sqrt((mControlParams.x - initX) * (mControlParams.x - initX) + (mControlParams.y - initY) * (mControlParams.y - initY));
                        if (distance > MOVING_START_THRESHOLD && !isDragging) {
                            isDragging = true;
                        }
                        if (isDragging) {
                            if(mWindowManager != null && mMainView != null){
                                mWindowManager.updateViewLayout(mMainView, mControlParams);
                            }
                        }
                        break;
                }
                return true;
            }
        };

        mStartRecord.setOnClickListener(onClickListener);
        mStartRecord.setOnTouchListener(onTouchListener);
        mStopRecord.setOnClickListener(onClickListener);
        mStopRecord.setOnTouchListener(onTouchListener);
        mCompleteRecord.setOnClickListener(onClickListener);
        mCompleteRecord.setOnTouchListener(onTouchListener);
    }

	//獲取螢幕實際大小,並比較GPU支援最大的Media播放解析度。取小的作為視訊的解析度引數傳入VideoThead();
    public void startRecording() {
        int videoWidth = mContext.getResources().getDisplayMetrics().widthPixels;
        int videoHeight = mContext.getResources().getDisplayMetrics().heightPixels;

        if (mVideoCapability != null){
            if (videoWidth > mVideoCapability.getSupportedHeights().getUpper()) videoWidth = mVideoCapability.getSupportedHeights().getUpper();
            if (videoHeight > mVideoCapability.getSupportedWidths().getUpper()) videoHeight = mVideoCapability.getSupportedWidths().getUpper();
        }
        if (videoHeight % 2 == 1) videoHeight = videoHeight - 1;
        if (videoWidth % 2 == 1) videoWidth = videoWidth - 1;

        if (!isRecording) {
            isRecording = true;
            mVideoThread = new VideoThread(mContext, videoWidth, videoHeight);
            mStartRecord.setClickable(false);
            mVideoThread.start();
            startTimer();
        }
    }

    public void stopRecording() {
        mStartRecord.setText("開始");
        mStartRecord.setClickable(true);
        isRecording = false;
        if (mVideoThread != null) {
            mVideoThread.quit();
            mVideoThread = null;
        }
        mRecordTimer.cancel();
    }


    public void destroy(){
        stopRecording();
        try {
            if (mWindowManager != null) {
                mWindowManager.removeView(mMainView);
            }
            mWindowManager = null;
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public MediaCodecInfo.VideoCapabilities getSupportVideoCapability(String MINETYPE){
		//https://developer.android.com/reference/android/media/MediaCodecList
		//尋找一個支援給定MIME型別的編碼器(Allows you to enumerate available codecs, each specified as a MediaCodecInfo object, find a codec supporting a given format and query the capabilities of a given codec.)
        int numberCodes = MediaCodecList.getCodecCount();
        for (int i = 0; i< numberCodes; i++) {
            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
            if (!codecInfo.isEncoder()) {
                continue;
            }
            String[] types = codecInfo.getSupportedTypes();
            for(String type : types){
				if (type.equalsIgnoreCase(MINETYPE)) {
					//Encapsulates the capabilities of a given codec component. 
					MediaCodecInfo.CodecCapabilities codecCapabilities = codecInfo.getCapabilitiesForType(type);
					//Returns the video capabilities or null if this is not a video codec.
                    MediaCodecInfo.VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities();
                    if(videoCapabilities != null){
						return videoCapabilities;
					}
				}
			}
        }
        return null;
    }

	//計時器:點選開始錄製的時候開始計時,使用定時任務TimerTask和定時器mRecordTimer.
    public void startTimer(){
        final long timeStart = System.currentTimeMillis();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                Runnable timeAction = new Runnable() {
                    @Override
                    public void run() {
                        long recordTime = (System.currentTimeMillis() - timeStart) / 1000;
                        int second = (int) recordTime % 60;
                        int min = (int) recordTime / 60;

                        String time = String.format("%02d:%02d", min, second);
                        mStartRecord.setText(time);
                        if(min > 1){
                            stopRecording();
                            Toast.makeText(mContext, "Out of Limit", Toast.LENGTH_SHORT).show();
                        }
                    }
                };
                mStartRecord.post(timeAction);
            }
        };
        mRecordTimer.scheduleAtFixedRate(timerTask, 0, 1000);
    }
}