Android系统截屏功能提取

来源:互联网 发布:淘宝店授权书范本 编辑:程序博客网 时间:2024/04/29 17:01
Android在4.0版本之后同时按电源键和音量键可以截取当前屏幕,截图后会有一个过渡动画效果,这里提取了将效果这部分提取出来,可以用于应用截图分享功能。

截图功能在源码中的位置是com.android.systemui.screenshot,下面有四个类


其中主要工作都在GlobalScreenshot中,包括截图后的动画效果、保存到本地和显示到通知栏。为了简单,下面的代码只保留了过渡动画部分

class GlobalScreenshot {    private static final String TAG = "GlobalScreenshot";    private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;    private static final int SCREENSHOT_DROP_IN_DURATION = 430;    private static final int SCREENSHOT_DROP_OUT_DELAY = 500;    private static final int SCREENSHOT_DROP_OUT_DURATION = 430;    private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;    private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;    private static final float BACKGROUND_ALPHA = 0.5f;    private static final float SCREENSHOT_SCALE = 1f;    private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;    private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;    private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;    private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;    private Context mContext;    private WindowManager mWindowManager;    private WindowManager.LayoutParams mWindowLayoutParams;    private Display mDisplay;    private DisplayMetrics mDisplayMetrics;    private Bitmap mScreenBitmap;    private View mScreenshotLayout;    private ImageView mBackgroundView;    private ImageView mScreenshotView;    private ImageView mScreenshotFlash;    private AnimatorSet mScreenshotAnimation;    private float mBgPadding;    private float mBgPaddingScale;    private MediaActionSound mCameraSound;    /**     * @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        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);        mScreenshotLayout.setFocusable(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_SYSTEM_ALERT,                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);        mDisplay = mWindowManager.getDefaultDisplay();        mDisplayMetrics = new DisplayMetrics();        mDisplay.getRealMetrics(mDisplayMetrics);        // Scale has to account for both sides of the bg        mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);        mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;        // Setup the Camera shutter sound        mCameraSound = new MediaActionSound();        mCameraSound.load(MediaActionSound.SHUTTER_CLICK);    }    /**     * Takes a screenshot of the current display and shows an animation.     */    void takeScreenshot(View view, Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {        // Take the screenshot        mScreenBitmap = SurfaceControl.screenshot(view);        if (mScreenBitmap == null) {            notifyScreenshotError(mContext);            finisher.run();            return;        }        // Optimizations        mScreenBitmap.setHasAlpha(false);        mScreenBitmap.prepareToDraw();        // Start the post-screenshot animation        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,                statusBarVisible, navBarVisible);    }    /**     * 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) {            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();            }        });    }    private ValueAnimator createScreenshotDropInAnimation() {        final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)                / SCREENSHOT_DROP_IN_DURATION);        final float flashDurationPct = 2f * flashPeakDurationPct;        final Interpolator flashAlphaInterpolator = new Interpolator() {            @Override            public float getInterpolation(float x) {                // Flash the flash view in and out quickly                if (x <= flashDurationPct) {                    return (float) Math.sin(Math.PI * (x / flashDurationPct));                }                return 0;            }        };        final Interpolator scaleInterpolator = new Interpolator() {            @Override            public float getInterpolation(float x) {                // We start scaling when the flash is at it's peak                if (x < flashPeakDurationPct) {                    return 0;                }                return (x - flashDurationPct) / (1f - flashDurationPct);            }        };        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);        anim.setDuration(SCREENSHOT_DROP_IN_DURATION);        anim.addListener(new AnimatorListenerAdapter() {            @Override            public void onAnimationStart(Animator animation) {                mBackgroundView.setAlpha(0f);                mBackgroundView.setVisibility(View.VISIBLE);                mScreenshotView.setAlpha(0f);                mScreenshotView.setTranslationX(0f);                mScreenshotView.setTranslationY(0f);                mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);                mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);                mScreenshotView.setVisibility(View.VISIBLE);                mScreenshotFlash.setAlpha(0f);                mScreenshotFlash.setVisibility(View.VISIBLE);            }            @Override            public void onAnimationEnd(android.animation.Animator animation) {                mScreenshotFlash.setVisibility(View.GONE);            }        });        anim.addUpdateListener(new AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                float t = (Float) animation.getAnimatedValue();                float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)                        - scaleInterpolator.getInterpolation(t)                        * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);                mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);                mScreenshotView.setAlpha(t);                mScreenshotView.setScaleX(scaleT);                mScreenshotView.setScaleY(scaleT);                mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));            }        });        return anim;    }    private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,                                                           boolean navBarVisible) {        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);        anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);        anim.addListener(new AnimatorListenerAdapter() {            @Override            public void onAnimationEnd(Animator animation) {                mBackgroundView.setVisibility(View.GONE);                mScreenshotView.setVisibility(View.GONE);                mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);            }        });        if (!statusBarVisible || !navBarVisible) {            // There is no status bar/nav bar, so just fade the screenshot away in place            anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);            anim.addUpdateListener(new AnimatorUpdateListener() {                @Override                public void onAnimationUpdate(ValueAnimator animation) {                    float t = (Float) animation.getAnimatedValue();                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)                            - t * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);                    mScreenshotView.setAlpha(1f - t);                    mScreenshotView.setScaleX(scaleT);                    mScreenshotView.setScaleY(scaleT);                }            });        } else {            // In the case where there is a status bar, animate to the origin of the bar (top-left)            final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION                    / SCREENSHOT_DROP_OUT_DURATION;            final Interpolator scaleInterpolator = new Interpolator() {                @Override                public float getInterpolation(float x) {                    if (x < scaleDurationPct) {                        // Decelerate, and scale the input accordingly                        return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));                    }                    return 1f;                }            };            // Determine the bounds of how to scale            float halfScreenWidth = (w - 2f * mBgPadding) / 2f;            float halfScreenHeight = (h - 2f * mBgPadding) / 2f;            final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;            final PointF finalPos = new PointF(                    -halfScreenWidth + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,                    -halfScreenHeight + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);            // Animate the screenshot to the status bar            anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);            anim.addUpdateListener(new AnimatorUpdateListener() {                @Override                public void onAnimationUpdate(ValueAnimator animation) {                    float t = (Float) animation.getAnimatedValue();                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)                            - scaleInterpolator.getInterpolation(t)                            * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);                    mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));                    mScreenshotView.setScaleX(scaleT);                    mScreenshotView.setScaleY(scaleT);                    mScreenshotView.setTranslationX(t * finalPos.x);                    mScreenshotView.setTranslationY(t * finalPos.y);                }            });        }        return anim;    }    private void notifyScreenshotError(Context context) {    }    private void saveScreenshotInWorkerThread(Runnable runnable) {    }}
看一下效果


下面是下面就分析一下相关原理:

1、截图如何显示

截图后返回对应的bitmap,用WindowManager直接在屏幕上将bitmap显示出来,也就是将bitmap放到一个imageView中,WindowManager addview就可以显示了。

为了有阴影的效果,这里额外定义了一个layout:global_screenshot.xml,如下

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <ImageView android:id="@+id/global_screenshot_background"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:src="@android:color/black"        android:visibility="gone" />    <ImageView android:id="@+id/global_screenshot"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:background="@drawable/screenshot_panel"        android:visibility="gone"        android:adjustViewBounds="true" />    <ImageView android:id="@+id/global_screenshot_flash"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:src="@android:color/white"        android:visibility="gone" /></FrameLayout>

上面的布局一共有三层,最下面的background用于遮盖,中间的screenshot用于放截图,最上边还有一层flash,主要是用于做一个反光的效果。
截图的bitmap就放在global_screenshot这个imageview中。对应的代码

mScreenshotView.setImageBitmap(mScreenBitmap);        mScreenshotLayout.requestFocus();        // Setup the animation with the screenshot just taken        if (mScreenshotAnimation != null) {            mScreenshotAnimation.end();            mScreenshotAnimation.removeAllListeners();        }        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
这样就可以在屏幕上看到截图了,之后还有一个过渡动画效果,分别是

ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();        ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,                statusBarVisible, navBarVisible);

这两个属性动画完成了显示-缩放-消失这个过程,具体就是对上面三层view进行变换了。

在Activity中调用

protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        final GlobalScreenshot screenshot = new GlobalScreenshot(this);        findViewById(R.id.main_btn).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                screenshot.takeScreenshot(getWindow().getDecorView(), new Runnable() {                    @Override                    public void run() {                    }                }, true, true);            }        });    }
后面的两个boolean参数是表示是否有状态栏,用于显示不同的淡出动画,如果有一个为false,就会直接淡出,而不会向上偏移到状态栏上。
示例代码->http://download.csdn.net/detail/xu_fu/7921645



0 0