Android屏幕截图实现方式 系统截屏源码分析和三指截屏
Android截屏的方式:
- 1.获取DecorView截屏
通过获取DecorView的方式来实现截屏(前提是当前Activity已经加载完成),DecorView为整个Window界面的最顶层View,因此截屏不包含状态栏(SystemUI)部分.
View view = getWindow().getDecorView(); // 获取DecorView // 方式一: view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmap bitmap1 = view.getDrawingCache(); // 方式二: Bitmap bitmap2 = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(); canvas.setBitmap(bitmap2); view.draw(canvas); // 保存 bitmap1 或 bitmap2 均可保存截屏图片
- 2.使用MediaProjection截屏
实现方式与屏幕录制类似 (Android屏幕录制源码Demo下载)
- 3.调用系统源码截屏(推荐使用)
系统截屏(获取当前屏幕的Bitmap)调用的代码如下:
Bitmap mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
由于是hide api,通过反射调用如下:
public Bitmap takeScreenShot() { Bitmap bmp = null; mDisplay.getMetrics(mDisplayMetrics); float[] dims = {(float) mDisplayMetrics.widthPixels, (float) heightPixels}; float degrees = getDegreesForRotation(mDisplay.getRotation()); boolean requiresRotation = degrees > 0; if (requiresRotation) { mDisplayMatrix.reset(); mDisplayMatrix.preRotate(-degrees); mDisplayMatrix.mapPoints(dims); dims[0] = Math.abs(dims[0]); dims[1] = Math.abs(dims[1]); } try { Class<?> demo = Class.forName("android.view.SurfaceControl"); Method method = demo.getMethod("screenshot", new Class[]{Integer.TYPE, Integer.TYPE}); bmp = (Bitmap) method.invoke(demo, new Object[]{Integer.valueOf((int) dims[0]), Integer.valueOf((int) dims[1])}); if (bmp == null) { return null; } if (requiresRotation) { Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, heightPixels, Bitmap.Config.RGB_565); Canvas c = new Canvas(ss); c.translate((float) (ss.getWidth() / 2), (float) (ss.getHeight() / 2)); c.rotate(degrees); c.translate((-dims[0] / 2), (-dims[1] / 2)); c.drawBitmap(bmp, 0, 0, null); c.setBitmap(null); bmp.recycle(); bmp = ss; } if (bmp == null) { return null; } bmp.setHasAlpha(false); bmp.prepareToDraw(); return bmp; } catch (Exception e) { e.printStackTrace(); return bmp; } }
Android系统自带截屏功能,在Framework层调用即可,普通应用无法调用.系统截屏源码如下:
// PATH: frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java /** {@inheritDoc} */ @Override public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) { final boolean keyguardOn = keyguardOn(); final int keyCode = event.getKeyCode(); // ... ... if (keyCode == KeyEvent.KEYCODE_S && event.isMetaPressed() && event.isCtrlPressed()) { if (down && repeatCount == 0) { int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION : TAKE_SCREENSHOT_FULLSCREEN; mScreenshotRunnable.setScreenshotType(type); mHandler.post(mScreenshotRunnable); return -1; } } // ... ... }
拦截截屏的按键事件,触发截屏.
private class ScreenshotRunnable implements Runnable { private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN; public void setScreenshotType(int screenshotType) { mScreenshotType = screenshotType; } @Override public void run() { takeScreenshot(mScreenshotType); } } private final ScreenshotRunnable mScreenshotRunnable = new ScreenshotRunnable();
调用takeScreenshot()函数截屏.
// Assume this is called from the Handler thread. private void takeScreenshot(final int screenshotType) { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { //截屏结束会关闭TakeScreenshotService服务和连接 return; } final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE, SYSUI_SCREENSHOT_SERVICE); final Intent serviceIntent = new Intent(); serviceIntent.setComponent(serviceComponent); ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mScreenshotLock) { if (mScreenshotConnection != this) { return; } Messenger messenger = new Messenger(service); Message msg = Message.obtain(null, screenshotType); final ServiceConnection myConn = this; Handler h = new Handler(mHandler.getLooper()) { @Override public void handleMessage(Message msg) { synchronized (mScreenshotLock) { if (mScreenshotConnection == myConn) { mContext.unbindService(mScreenshotConnection); mScreenshotConnection = null; mHandler.removeCallbacks(mScreenshotTimeout); } } } }; msg.replyTo = new Messenger(h); msg.arg1 = msg.arg2 = 0; if (mStatusBar != null && mStatusBar.isVisibleLw()) msg.arg1 = 1; if (mNavigationBar != null && mNavigationBar.isVisibleLw()) msg.arg2 = 1; try { messenger.send(msg); } catch (RemoteException e) { } } } @Override public void onServiceDisconnected(ComponentName name) { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { mContext.unbindService(mScreenshotConnection); mScreenshotConnection = null; mHandler.removeCallbacks(mScreenshotTimeout); notifyScreenshotError(); } } } }; if (mContext.bindServiceAsUser(serviceIntent, conn, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, UserHandle.CURRENT)) { mScreenshotConnection = conn; mHandler.postDelayed(mScreenshotTimeout, 10000); } } }
通过bindService启动服务TakeScreenshotService执行截屏
同时通过mHandler发送一个10s的延迟任务,截屏程序在10s中没有完成则触发该任务mScreenshotTimeout(该Runnable主要是解绑TakeScreenshotService同时发送截屏错误的广播,由ScreenshotServiceErrorReceiver接收然后发送错误通知);
服务绑定成功后通过"msg.replyTo = new Messenger(h);"发送一个Messenger给TakeScreenshotService服务,当截屏结束TakeScreenshotService会发送该Messenger,触发handleMessage,执行解绑服务和移除mScreenshotTimeout超时任务.
// PATH: frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java public class TakeScreenshotService extends Service { private static final String TAG = "TakeScreenshotService"; private static GlobalScreenshot mScreenshot; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { final Messenger callback = msg.replyTo; Runnable finisher = new Runnable() { @Override public void run() { Message reply = Message.obtain(null, 1); try { callback.send(reply); } catch (RemoteException e) { } } }; // If the storage for this user is locked, we have no place to store // the screenshot, so skip taking it instead of showing a misleading // animation and error notification. if (!getSystemService(UserManager.class).isUserUnlocked()) { Log.w(TAG, "Skipping screenshot because storage is locked!"); post(finisher); return; } if (mScreenshot == null) { mScreenshot = new GlobalScreenshot(TakeScreenshotService.this); } switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0); break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0); break; } } }; @Override public IBinder onBind(Intent intent) { return new Messenger(mHandler).getBinder(); } @Override public boolean onUnbind(Intent intent) { if (mScreenshot != null) mScreenshot.stopScreenshot(); return true; }}
初始化GlobalScreenshot对象,调用截图函数;接收Messenger,构建Runnable用来发送消息(当截屏保存之后执行发送,如上所述解绑服务,移除超时任务)
/** * @param context everything needs a context :( */ public GlobalScreenshot(Context context) { Resources r = context.getResources(); mContext = context; LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // Inflate the screenshot layout mDisplayMatrix = new Matrix(); mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null); mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background); mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot); mScreenshotFlash = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_flash); mScreenshotSelectorView = (ScreenshotSelectorView) mScreenshotLayout.findViewById( R.id.global_screenshot_selector); mScreenshotLayout.setFocusable(true); mScreenshotSelectorView.setFocusable(true); mScreenshotSelectorView.setFocusableInTouchMode(true); mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // Intercept and ignore all touch events return true; } }); // Setup the window that we are going to use mWindowLayoutParams = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, WindowManager.LayoutParams.TYPE_SCREENSHOT, WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, PixelFormat.TRANSLUCENT); mWindowLayoutParams.setTitle("ScreenshotAnimation"); mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); mDisplay = mWindowManager.getDefaultDisplay(); mDisplayMetrics = new DisplayMetrics(); mDisplay.getRealMetrics(mDisplayMetrics); // Get the various target sizes mNotificationIconSize = r.getDimensionPixelSize(android.R.dimen.notification_large_icon_height); // Scale has to account for both sides of the bg mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding); mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels; // determine the optimal preview size int panelWidth = 0; try { panelWidth = r.getDimensionPixelSize(R.dimen.notification_panel_width); } catch (Resources.NotFoundException e) { } if (panelWidth <= 0) { // includes notification_panel_width==match_parent (-1) panelWidth = mDisplayMetrics.widthPixels; } mPreviewWidth = panelWidth; mPreviewHeight = r.getDimensionPixelSize(R.dimen.notification_max_height); // Setup the Camera shutter sound mCameraSound = new MediaActionSound(); mCameraSound.load(MediaActionSound.SHUTTER_CLICK); }
构造函数中,初始化窗体布局的位置等参数;初始化通知栏服务mNotificationManager;初始化截屏声音mCameraSound
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) { mDisplay.getRealMetrics(mDisplayMetrics); takeScreenshot(finisher, statusBarVisible, navBarVisible, 0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels); } /** * Takes a screenshot of the current display and shows an animation. */ void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible, int x, int y, int width, int height) { // We need to orient the screenshot correctly (and the Surface api seems to take screenshots // only in the natural orientation of the device :!) mDisplay.getRealMetrics(mDisplayMetrics); // 屏幕的高度和宽度 float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; float degrees = getDegreesForRotation(mDisplay.getRotation()); boolean requiresRotation = (degrees > 0); if (requiresRotation) { // Get the dimensions of the device in its native orientation mDisplayMatrix.reset(); mDisplayMatrix.preRotate(-degrees); mDisplayMatrix.mapPoints(dims); dims[0] = Math.abs(dims[0]); dims[1] = Math.abs(dims[1]); } // Take the screenshot 截屏 mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]); if (mScreenBitmap == null) { // 截屏失败 notifyScreenshotError(mContext, mNotificationManager, R.string.screenshot_failed_to_capture_text); finisher.run(); return; } if (requiresRotation) { // Rotate the screenshot to the current orientation Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(ss); c.translate(ss.getWidth() / 2, ss.getHeight() / 2); c.rotate(degrees); c.translate(-dims[0] / 2, -dims[1] / 2); c.drawBitmap(mScreenBitmap, 0, 0, null); c.setBitmap(null); // Recycle the previous bitmap mScreenBitmap.recycle(); mScreenBitmap = ss; } if (width != mDisplayMetrics.widthPixels || height != mDisplayMetrics.heightPixels) { // Crop the screenshot to selected region Bitmap cropped = Bitmap.createBitmap(mScreenBitmap, x, y, width, height); mScreenBitmap.recycle(); mScreenBitmap = cropped; } // Optimizations mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); // Start the post-screenshot animation startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, statusBarVisible, navBarVisible); }
这里为调用截屏native函数执行截屏mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);返回当前屏幕的mScreenBitmap图像.截屏结束,调用startAnimation()播放截屏的动画.
/** * Starts the animation after taking the screenshot */ private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible, boolean navBarVisible) { // Add the view for the animation mScreenshotView.setImageBitmap(mScreenBitmap); mScreenshotLayout.requestFocus(); // Setup the animation with the screenshot just taken if (mScreenshotAnimation != null) { if (mScreenshotAnimation.isStarted()) { mScreenshotAnimation.end(); } mScreenshotAnimation.removeAllListeners(); } mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation(); ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible); mScreenshotAnimation = new AnimatorSet(); mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim); mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { // Save the screenshot once we have a bit of time now saveScreenshotInWorkerThread(finisher); mWindowManager.removeView(mScreenshotLayout); // Clear any references to the bitmap mScreenBitmap = null; mScreenshotView.setImageBitmap(null); } }); mScreenshotLayout.post(new Runnable() { @Override public void run() { // Play the shutter sound to notify that we've taken a screenshot mCameraSound.play(MediaActionSound.SHUTTER_CLICK); mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null); mScreenshotView.buildLayer(); mScreenshotAnimation.start(); } }); }
创建一个截屏的动画集,screenshotDropInAnim和screenshotFadeOutAnim分别表示截屏进入和退出动画.同时添加动画结束的监听.动画结束,调用saveScreenshotInWorkerThread()保存截图
/** * Creates a new worker thread and saves the screenshot to the media store. */ private void saveScreenshotInWorkerThread(Runnable finisher) { SaveImageInBackgroundData data = new SaveImageInBackgroundData(); data.context = mContext; data.image = mScreenBitmap; data.iconSize = mNotificationIconSize; data.finisher = finisher; data.previewWidth = mPreviewWidth; data.previewheight = mPreviewHeight; if (mSaveInBgTask != null) { mSaveInBgTask.cancel(false); } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager) .execute(); }
这里的finisher是TakeScreenshotService.java中的Runnable任务.用来回复PhoneWindowManager启动截屏服务后10s延迟任务的检测.Service中的创建一个任务SaveImageInBackgroundTask对象,然后在子线程中保存截图
class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // ... ... @Override protected Void doInBackground(Void... params) { if (isCancelled()) { return null; } // By default, AsyncTask sets the worker thread to have background thread priority, so bump // it back up so that we save a little quicker. Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); Context context = mParams.context; Bitmap image = mParams.image; Resources r = context.getResources(); try { // Create screenshot directory if it doesn't exist mScreenshotDir.mkdirs(); // media provider uses seconds for DATE_MODIFIED and DATE_ADDED, but milliseconds // for DATE_TAKEN long dateSeconds = mImageTime / 1000; // Save OutputStream out = new FileOutputStream(mImageFilePath); image.compress(Bitmap.CompressFormat.PNG, 100, out); out.flush(); out.close(); // Save the screenshot to the MediaStore ContentValues values = new ContentValues(); ContentResolver resolver = context.getContentResolver(); values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath); values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName); values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName); values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime); values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds); values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds); values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png"); values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth); values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight); values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length()); Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); // Create a share intent String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime)); String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate); Intent sharingIntent = new Intent(Intent.ACTION_SEND); sharingIntent.setType("image/png"); sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); // Create a share action for the notification PendingIntent chooseAction = PendingIntent.getBroadcast(context, 0, new Intent(context, GlobalScreenshot.TargetChosenReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); Intent chooserIntent = Intent.createChooser(sharingIntent, null, chooseAction.getIntentSender()) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent shareAction = PendingIntent.getActivity(context, 0, chooserIntent, PendingIntent.FLAG_CANCEL_CURRENT); Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( R.drawable.ic_screenshot_share, r.getString(com.android.internal.R.string.share), shareAction); mNotificationBuilder.addAction(shareActionBuilder.build()); // Create a delete action for the notification PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0, new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class) .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( R.drawable.ic_screenshot_delete, r.getString(com.android.internal.R.string.delete), deleteAction); mNotificationBuilder.addAction(deleteActionBuilder.build()); mParams.imageUri = uri; mParams.image = null; mParams.errorMsgResId = 0; } catch (Exception e) { // IOException/UnsupportedOperationException may be thrown if external storage is not // mounted mParams.clearImage(); mParams.errorMsgResId = R.string.screenshot_failed_to_save_text; } // Recycle the bitmap data if (image != null) { image.recycle(); } return null; } @Override protected void onPostExecute(Void params) { if (mParams.errorMsgResId != 0) { // Show a message that we've failed to save the image to disk GlobalScreenshot.notifyScreenshotError(mParams.context, mNotificationManager, mParams.errorMsgResId); } else { // Show the final notification to indicate screenshot saved Context context = mParams.context; Resources r = context.getResources(); // Create the intent to show the screenshot in gallery Intent launchIntent = new Intent(Intent.ACTION_VIEW); launchIntent.setDataAndType(mParams.imageUri, "image/png"); launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Log.d("ansen","uri: "+mParams.imageUri); final long now = System.currentTimeMillis(); // Update the text and the icon for the existing notification mPublicNotificationBuilder .setContentTitle(r.getString(R.string.screenshot_saved_title)) .setContentText(r.getString(R.string.screenshot_saved_text)) .setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0)) .setWhen(now) .setAutoCancel(true) .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)); mNotificationBuilder .setContentTitle(r.getString(R.string.screenshot_saved_title)) .setContentText(r.getString(R.string.screenshot_saved_text)) .setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0)) .setWhen(now) .setAutoCancel(true) .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)) .setPublicVersion(mPublicNotificationBuilder.build()) .setFlag(Notification.FLAG_NO_CLEAR, false); mNotificationManager.notify(R.id.notification_screenshot, mNotificationBuilder.build()); } mParams.finisher.run(); //移除PhoneWindowManager中10s截屏超时任务检测 mParams.clearContext(); } // ... ...}
doInBackground中保存截图文件,同时写入多媒体数据库便于及时在图库中更新;onPostExecute()中,如果成功则发送成功的通知.
- 4.Framebuffer截屏
帧缓冲(Framebuffer)是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作.
- 5.DDMS截屏(无法在代码中实现)
手机连接DDMS实现截屏,调用sdk/tools/lib/ddmlib.jar
三指截屏
实现三指截屏主要思路就是在触摸事件传递的最上层判断三指下滑的手势,然后调用系统截屏功能从而实现三指截屏.
- 在ViewGroup中实现 过滤三指截屏的手势
// PATH: frameworks/base/core/java/android/view/ViewGroup.java @Override public boolean dispatchTouchEvent(MotionEvent ev) { // ... ... // Check for cancelation. boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; if (touchAction(ev)) { intercepted = true; canceled = true; } // ... ... } // ansen 170830 @{ private static final float OFFSET_Y = 150; private float mFirstPointStartY; private float mSecondPointStartY; private float mThirdPointStartY; private boolean mEnableThreeFingerScreenshot; private boolean isRelease; private float mStartDownY; private boolean only3Pointer; private boolean touchAction(MotionEvent ev) { switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: // only check this status when receiving first down event. mEnableThreeFingerScreenshot = Settings.System.getInt(mContext.getContentResolver(), "three_finger_screenshot", 1) == 1; if(mEnableThreeFingerScreenshot){ mStartDownY = ev.getRawY(); } break; case MotionEvent.ACTION_POINTER_DOWN: if (mEnableThreeFingerScreenshot) { if (ev.getPointerCount() == 3) { mFirstPointStartY = ev.getY(0); mSecondPointStartY = ev.getY(1); mThirdPointStartY = ev.getY(2); only3Pointer=true; }else{ only3Pointer=false; } } break; case MotionEvent.ACTION_MOVE: if (mEnableThreeFingerScreenshot) { if (only3Pointer && ev.getPointerCount() == 3) { if (((Math.abs(ev.getY(0) - mFirstPointStartY)) > OFFSET_Y) && ((Math.abs(ev.getY(1) - mSecondPointStartY)) > OFFSET_Y) && ((Math.abs(ev.getY(2) - mThirdPointStartY)) > OFFSET_Y)) { Intent intent = new Intent("com.qucii.screenshot"); //Send broadcast to PhoneWindowManager to cature screen getContext().sendBroadcast(intent); Log.d(TAG, "Intercept the key Three finger to capture screen"); only3Pointer=false; } if((ev.getRawY()- mStartDownY)>5){// MotionEvent eventClone = MotionEvent.obtain(ev);// eventClone.setAction(MotionEvent.ACTION_CANCEL);// dispatchTouchEvent(eventClone); if(!isRelease){ return true; } } }else{ if((Math.abs(ev.getRawY() - mStartDownY)) > OFFSET_Y){ isRelease =true; } break; } } break; case MotionEvent.ACTION_UP: isRelease =false; break; } return false; } // ansen @}
- 监听到截屏的指令,执行截屏逻辑
// PATH: frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java /** {@inheritDoc} */ @Override public void init(Context context, IWindowManager windowManager, WindowManagerFuncs windowManagerFuncs) { // ... ... // register for multiuser-relevant broadcasts filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); context.registerReceiver(mMultiuserReceiver, filter); filter = new IntentFilter("com.qucii.screenshot"); context.registerReceiver(mScreenShotReceiver, filter); // monitor for system gestures // ... ... } BroadcastReceiver mScreenShotReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if ("com.qucii.screenshot".equals(intent.getAction())) { mHandler.post(mScreenshotRunnable); // 调用系统代码,执行截屏逻辑 } } };
Tips: 分屏状态下ViewGroup中获取不到手指按在分屏上下部分而产生的三指操作. SystemGesturesPointerEventListener --> onPointerEvent 获取分屏下三指触摸事件 // PointerEventDispatcher --> registerInputEventListener
Android屏幕录制源码Demo下载
Android长截屏(滚动截屏)实现原理
更多相关文章
- Android 屏幕切换横竖屏时防止activity重新执行生命周期
- Android 屏幕操作原理
- android屏幕触摸事件机制
- Android Glide加载图片,宽度占满屏幕高度自适应
- android 屏幕宽高