Android12之原生截图,实现删除功能和流程

概述:

在Android12原生中,截图的按钮中目前只有修改按钮和分享按钮以及单页面情况下隐藏的长截屏按钮。
在基于12原生的基础上,只要触发了截屏,图片就一定会保存,今天就实现一个小功能,在原生的基础上添加删除功能。

主要思路:

首先添加一个删除按钮布局,然后在调用截屏功能后,保存截图资源时,再通过URI去作删除截图产生的媒体资源。

1.添加一个删除按钮布局:

<HorizontalScrollView
    android:id="@+id/global_screenshot_actions_container"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
    android:paddingEnd="@dimen/screenshot_action_container_padding_right"
    android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
    android:elevation="1dp"
    android:scrollbars="none"
    app:layout_constraintHorizontal_bias="0"
    app:layout_constraintWidth_percent="1.0"
    app:layout_constraintWidth_max="wrap"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toEndOf="@+id/global_screenshot_preview_border"
    app:layout_constraintEnd_toEndOf="parent">
    <LinearLayout
        android:id="@+id/global_screenshot_actions"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        //1.复用global_screenshot_action_chip.xml布局,实现增加delete按钮布局
        <include layout="@layout/global_screenshot_action_chip"
            android:id="@+id/screenshot_delete_chip"/>
        <include layout="@layout/global_screenshot_action_chip"
                 android:id="@+id/screenshot_share_chip"/>
        <include layout="@layout/global_screenshot_action_chip"
                 android:id="@+id/screenshot_edit_chip"/>
        <include layout="@layout/global_screenshot_action_chip"
                 android:id="@+id/screenshot_scroll_chip"
                 android:visibility="gone" />
    </LinearLayout>
</HorizontalScrollView>

2.在ScreenshotView.java中初始化

public class ScreenshotView extends FrameLayout implements ViewTreeObserver.OnComputeInternalInsetsListener {
    ...
//1.在ScreenshotView中对screenshot_delete_chip进行初始化
    private ScreenshotActionChip mDeleteChip;
    
    protected void onFinishInflate() {
        ...
        mDeleteChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_delete_chip));
    }
}

3.ScreenshotView.createScreenshotActionsShadeAnimation()

ValueAnimator createScreenshotActionsShadeAnimation() {
    // By default the activities won't be able to start immediately; override this to keep
    // the same behavior as if started from a notification
    try {
        ActivityManager.getService().resumeAppSwitches();
    } catch (RemoteException e) {
    }

    ArrayList<ScreenshotActionChip> chips = new ArrayList<>();

    
//1.设置删除文字
    mDeleteChip.setContentDescription(mContext.getString(com.android.internal.R.string.delete));
//2.设置删除图标
    mDeleteChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_delete), true);
//3.删除的点击监听
mDeleteChip.setOnClickListener(v -> {
            mDeleteChip.setIsPending(true);
            mShareChip.setIsPending(false);
            mEditChip.setIsPending(false);
            if (mQuickShareChip != null) {
                mQuickShareChip.setIsPending(false);
            }
//4.将DELETE赋值给当前的交互状态
    mPendingInteraction = PendingInteraction.DELETE;
    });
    chips.add(mDeleteChip);
    
    mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
    mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
    mShareChip.setOnClickListener(v -> {
        mShareChip.setIsPending(true);
        
//5.也要在其他chip中将除了当前chip外的chip挂起视图置为false
        mDeleteChip.setIsPending(false);
        mEditChip.setIsPending(false);
        if (mQuickShareChip != null) {
            mQuickShareChip.setIsPending(false);
        }
        mPendingInteraction = PendingInteraction.SHARE;
    });
    chips.add(mShareChip);
}

前三步操作是添加删除按钮布局和初始化操作。接下来从调用系统截屏开始,找到截图时保存的那张图片的URI。

4.ScreenshotController.takeScreenshotInternal()

private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) {
//1.先判断当前屏幕是否为竖屏状态
    mScreenshotTakenInPortrait =
            mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;

    // copy the input Rect, since SurfaceControl.screenshot can mutate it
//2.先new一个矩形对象用于保存截图大小,再在captureScreenshot()方法中返回一个位图(该方法下面有具体内容)
    Rect screenRect = new Rect(crop);
    Bitmap screenshot = captureScreenshot(crop);

    if (screenshot == null) {
        Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
        mNotificationsController.notifyScreenshotError(
                R.string.screenshot_failed_to_capture_text);
        if (mCurrentRequestCallback != null) {
            mCurrentRequestCallback.reportError();
        }
        return;
    }
//3.最后调用保存截屏将刚才的返回的图保存
    saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true);
}
private Bitmap captureScreenshot(Rect crop) {
    int width = crop.width();
    int height = crop.height();
    Bitmap screenshot = null;
    final Display display = getDefaultDisplay();//return主设备
    final DisplayAddress address = display.getAddress();
    if (!(address instanceof DisplayAddress.Physical)) {
        Log.e(TAG, "Skipping Screenshot - Default display does not have a physical address: "
                + display);
    } else {
        final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;

        final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(
                physicalAddress.getPhysicalDisplayId());
        final SurfaceControl.DisplayCaptureArgs captureArgs =
                new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
                        .setSourceCrop(crop)
                        .setSize(width, height)
                        .build();
        final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
                SurfaceControl.captureDisplay(captureArgs);
//4.返回ScreenshotHardwareBuffer获取到的位图
        screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
    }
    return screenshot;
}
public Bitmap asBitmap() {
        if (mHardwareBuffer == null) {
            Log.w(TAG, "Failed to take screenshot. Null screenshot object");
            return null;
        }
        return Bitmap.wrapHardwareBuffer(mHardwareBuffer, mColorSpace);
}

5.ScreenshotController.saveScreenshot()

private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
        Insets screenInsets, boolean showFlash) {
    if (mAccessibilityManager.isEnabled()) {
        AccessibilityEvent event =
                new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
        event.setContentDescription(
                mContext.getResources().getString(R.string.screenshot_saving_title));
        mAccessibilityManager.sendAccessibilityEvent(event);
    }

    if (mScreenshotView.isAttachedToWindow()) {
        // if we didn't already dismiss for another reason
        if (!mScreenshotView.isDismissing()) {
            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED);
        }
        if (DEBUG_WINDOW) {
            Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
                    + "(dismissing=" + mScreenshotView.isDismissing() + ")");
        }
        mScreenshotView.reset();
    }
    mScreenshotView.updateOrientation(
            mWindowManager.getCurrentWindowMetrics().getWindowInsets());

    mScreenBitmap = screenshot;

    if (!isUserSetupComplete()) {
        Log.w(TAG, "User setup not complete, displaying toast only");
        // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
        // and sharing shouldn't be exposed to the user.
        saveScreenshotAndToast(finisher);
        return;
    }

    // Optimizations
    mScreenBitmap.setHasAlpha(false);
    mScreenBitmap.prepareToDraw();
    //1.保存截图的线程
    saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady,
            this::showUiOnQuickShareActionReady);
    ...
}
private void saveScreenshotInWorkerThread(Consumer<Uri> finisher,
        @Nullable ScreenshotController.ActionsReadyListener actionsReadyListener,
        @Nullable ScreenshotController.QuickShareActionReadyListener
                quickShareActionsReadyListener) {
    ScreenshotController.SaveImageInBackgroundData
            data = new ScreenshotController.SaveImageInBackgroundData();
    data.image = mScreenBitmap;
    data.finisher = finisher;
    data.mActionsReadyListener = actionsReadyListener;
    data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;

    if (mSaveInBgTask != null) {
        // just log success/failure for the pre-existing screenshot
        mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
    }

    mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mImageExporter,
            mScreenshotSmartActions, data, getActionTransitionSupplier());
//2.保存任务异步进行,后续删除相关
    mSaveInBgTask.execute();
}
private void showUiOnActionsReady(ScreenshotController.SavedImageData imageData) {
//3.记录截图成功与否
    logSuccessOnActionsReady(imageData);
    if (DEBUG_UI) {
        Log.d(TAG, "Showing UI actions");
    }

    resetTimeout();
    if (imageData.uri != null) {
//4.直到动画结束,再将imageData传入到ScreenshotView中,调用setChipIntents()给单个chip提供交互
        mScreenshotHandler.post(() -> {
            if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
                mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        mScreenshotView.setChipIntents(imageData);
                    }
                });
            } else {
                mScreenshotView.setChipIntents(imageData);
            }
        });
    }
}

6.ScreenshotView.setChipIntents()

void setChipIntents(ScreenshotController.SavedImageData imageData) {
    mShareChip.setOnClickListener(v -> {
        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED);
        startSharedTransition(
                imageData.shareTransition.get());
    });
    ...
    if (mDeleteChip != null){
//1.设置PendingIntent(PendingIntent,Runnable)
        mDeleteChip.setPendingIntent(imageData.deleteAction.actionIntent,
                () -> {
                    mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
                    animateDismissal();
                });
    }
    ...
}

7.SaveImageInBackgroundTask.doInBackground()

protected Void doInBackground(Void... paramsUnused) {
    ...
    mImageData.uri = uri;
    mImageData.smartActions = smartActions;
    mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri);
    mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri);
//1.创建删除动作通知,与PendingIntent共同
    mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
    mImageData.quickShareAction = createQuickShareAction(mContext,mQuickShareData.quickShareAction, uri);
    ...
}
Notification.Action createDeleteAction(Context context, Resources r, Uri uri) {
    // Make sure pending intents for the system user are still unique across users
    // by setting the (otherwise unused) request code to the current user id.
    int requestCode = mContext.getUserId();

    // Create a delete action for the notification
    PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
            new Intent(context, DeleteScreenshotReceiver.class)
//携带URI到DeleteScreenshotReceiver.class
                    .putExtra(ScreenshotController.SCREENSHOT_URI_ID, uri.toString())
                    .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
                    .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
                            mSmartActionsEnabled)
                    .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
            PendingIntent.FLAG_CANCEL_CURRENT
                    | PendingIntent.FLAG_ONE_SHOT
                    | PendingIntent.FLAG_IMMUTABLE);
    Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
            Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
            r.getString(com.android.internal.R.string.delete), deleteAction);

    return deleteActionBuilder.build();
}

8.DeleteScreenshotReceiver.onReceive()

public class DeleteScreenshotReceiver extends BroadcastReceiver {

    private final ScreenshotSmartActions mScreenshotSmartActions;
    private final Executor mBackgroundExecutor;

    @Inject
    public DeleteScreenshotReceiver(ScreenshotSmartActions screenshotSmartActions,
            @Background Executor backgroundExecutor) {
        mScreenshotSmartActions = screenshotSmartActions;
        mBackgroundExecutor = backgroundExecutor;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (!intent.hasExtra(SCREENSHOT_URI_ID)) {
            return;
        }

        // And delete the image from the media store
        final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
        mBackgroundExecutor.execute(() -> {
//根据intent携带的uri将其删除
ContentResolver resolver = context.getContentResolver();
            resolver.delete(uri, null, null);
        });
        if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
            mScreenshotSmartActions.notifyScreenshotAction(
                    context, intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false, null);
        }
    }
}
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值