關於手機的錄屏功能小記(1)
阿新 • • 發佈:2018-12-21
最近在開發的過程中,需要呼叫錄屏功能。
大概功能就是:點選錄屏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); } }