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 {
// ... ...
@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;
// FEATURE-screenshot3-ansen-170830 @{
if (touchAction(ev)) {
intercepted = true;
canceled = true;
}
// FEATURE-screenshot3-ansen-170830 @}
// ... ...
}
// FEATURE-screenshot3-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;
}
// FEATURE-screenshot3-ansen-170830 @}
- 监听到截屏的指令,执行截屏逻辑
// 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);
// FEATURE-screenshot3-ansen-180725 @{
filter = new IntentFilter("com.qucii.screenshot");
context.registerReceiver(mScreenShotReceiver, filter);
// FEATURE-screenshot3-ansen-180725 @}
// monitor for system gestures
// ... ...
}
// FEATURE-screenshot3-ansen-180725 @{
private static long sTimeEnd;
BroadcastReceiver mScreenShotReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if ("com.qucii.screenshot".equals(intent.getAction())) {
long start = System.currentTimeMillis();
if (Math.abs((start - sTimeEnd)) > 1000) {
sTimeEnd = start;
mHandler.post(mScreenshotRunnable); // 调用系统代码,执行截屏逻辑
Log.d(TAG,"mScreenshotRunnable is run");
}
}
}
};
// FEATURE-screenshot3-ansen-180725 @}
Tips:
分屏状态下ViewGroup中获取不到手指按在分屏上下部分而产生的三指操作.
SystemGesturesPointerEventListener --> onPointerEvent 获取分屏下三指触摸事件
// PointerEventDispatcher --> registerInputEventListener
import static android.view.WindowManager.DOCKED_INVALID;
@Override
public void onPointerEvent(MotionEvent event) {
// FEATURE-screenshot3-ansen-180725 @{
if (getDockSide() != DOCKED_INVALID) {
touchAction(event);
Log.d(TAG,"SystemGesturesPointerEventListener to screen shot");
}
// FEATURE-screenshot3-ansen-180725 @}
if (mGestureDetector != null && event.isTouchEvent()) {
mGestureDetector.onTouchEvent(event);
}
}
// FEATURE-screenshot3-ansen-180725
public int getDockSide() {
try {
return WindowManagerGlobal.getWindowManagerService().getDockedStackSide();
} catch (RemoteException e) {
Log.w(TAG, "Failed to get dock side: " + e);
}
return DOCKED_INVALID;
}
Android屏幕录制源码Demo下载
Android长截屏(滚动截屏)实现原理