效果图

修改后动画如下

android三指下滑手势库 安卓手机三指操作_三指截屏

系统原动画如下

android三指下滑手势库 安卓手机三指操作_ide_02

三指截屏

PhoneWindowManager 同级目录下的 SystemGesturesPointerEventListener.java 主要负责处理界面的手势监听

public class SystemGesturesPointerEventListener implements PointerEventListener {

	public void systemReady() {
        Handler h = new Handler(Looper.myLooper());
        mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), h);
        mOverscroller = new OverScroller(mContext);
    }

    @Override
    public void onPointerEvent(MotionEvent event) {
        if (mGestureDetector != null && event.isTouchEvent()) {
            mGestureDetector.onTouchEvent(event);
        }
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                mSwipeFireable = true;
                mDebugFireable = true;
                mDownPointers = 0;
                captureDown(event, 0);
                if (mMouseHoveringAtEdge) {
                    mMouseHoveringAtEdge = false;
                    mCallbacks.onMouseLeaveFromEdge();
                }
                mCallbacks.onDown();
                break;
			....

			case MotionEvent.ACTION_MOVE:
                if (mSwipeFireable) {
                    final int swipe = detectSwipe(event);
                    mSwipeFireable = swipe == SWIPE_NONE;
                    if (swipe == SWIPE_FROM_TOP) {
                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop");
                        mCallbacks.onSwipeFromTop();
                    } else if (swipe == SWIPE_FROM_BOTTOM) {
                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom");
                        mCallbacks.onSwipeFromBottom();
                    } else if (swipe == SWIPE_FROM_RIGHT) {
                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight");
                        mCallbacks.onSwipeFromRight();
                    }
					//add
					if(event.getPointerCount() == 3){
	                    mCallbacks.onMorePointerSwipe();
	                }
                }
                break;

			....		
		}


		....
	}

	interface Callbacks {
        void onSwipeFromTop();
        void onSwipeFromBottom();
        void onSwipeFromRight();
		void onMorePointerSwipe();//add
        void onFling(int durationMs);
        void onDown();
        void onUpOrCancel();
        void onMouseHoverAtTop();
        void onMouseHoverAtBottom();
        void onMouseLeaveFromEdge();
        void onDebug();
    }

}

从字面意思理解可知从顶部、底部、右边滑动,接鼠标时从顶部、底部按住滑动,我们在 Callbacks 中增加一个 void onMorePointerSwipe(); 用来回调三指同时滑动的情况,通过 event.getPointerCount() 获取当前屏幕手指按下个数,也可打开调试模式里的指针和触点显示。
当手指数 == 3 时,同时滑动,则我们在 case MotionEvent.ACTION_MOVE 中添加

if(event.getPointerCount() == 3){
    mCallbacks.onMorePointerSwipe();
}

接口这边搞定了接下来我们就需要来实现具体截屏逻辑

// monitor for system gestures
 mSystemGestures = new SystemGesturesPointerEventListener(context,
        new SystemGesturesPointerEventListener.Callbacks() {
            @Override
            public void onSwipeFromTop() {
                if (mStatusBar != null) {
                    requestTransientBars(mStatusBar);
                }
            }
			//add
			@Override
            public void onMorePointerSwipe() {
                if (mStatusBar != null) {
                   mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
				   mHandler.post(mScreenshotRunnable);
                }
            }
            @Override
            public void onSwipeFromBottom() {
                if (mNavigationBar != null && mNavigationBarOnBottom) {
                    requestTransientBars(mNavigationBar);
                }
            }
}

这样就搞定了三指滑动截屏,是不是很容易,mmm 编译重新 push 替换可查看效果

仿 IOS 截屏动画

再看下原来的动画效果,是一个组合动画,分为 screenshotDropInAnim 和 screenshotFadeOutAnim,有点类似 ppt 中的移入和移出动画,screenshotDropInAnim 中先将缩略图进行适当的缩小和透明度减小,然后在 500 ms的延时后再执行 createScreenshotDropOutAnimation 动画,将缩略图恢复初始的大小和透明度,并相对当前位置朝着自身左上角平移。在移动的同时缩小宽高和降低透明度,直到最终消失在左上角。

来看下我们的预期,修改移出动画为向左下角平移,最终停留在距离屏幕左边和右边各 100 处,超过 3 s 未点击则向左移出屏幕,点击则跳转至图库界面放大查看。

既然是动画我们先来搞清坐标系,因为缩略图是通过 WindowManager 的 addView 方法添加,layoutParams.gravity 如果未指定,则初始位置默认在屏幕正中间,经过测试发现,mScreenshotView 的坐标系是以自身左上角的点为原点的。

android三指下滑手势库 安卓手机三指操作_ide_03

没错,就像你看到的上图,这个移出动画我们需要分三段来完成(我们先在 Demo 中进行调试)

第一段

public ValueAnimator loadScreenThumbAnimation(){
        //起点
        Point startPoint = new Point(layoutParams.x, layoutParams.y);
        //终点
        Point endPoint = new Point(-260, 380);

        ValueAnimator valueAnimator = ValueAnimator.ofObject(new ObjectEvaluator(), startPoint, endPoint);
        valueAnimator.setDuration(800);
        valueAnimator.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                //screenShotThumbView.setTranslationX(-50);

                //第一段动画执行完成,3 S 后执行第二段
                new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        slowSlideScreenThumb();
                    }
                },3000);

            }
        });
        //移动过程中改变 x 和 y
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Point animatedValue = (Point) animation.getAnimatedValue();
                Log.e("anim", "currentX="+animatedValue.x);
                layoutParams.x = animatedValue.x;
                layoutParams.y = animatedValue.y;
                windowManager.updateViewLayout(screenShotThumbView, layoutParams);
            }
        });
        //valueAnimator.start();

        return valueAnimator;
    }


    public class ObjectEvaluator implements TypeEvaluator{
        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            Log.i("anim", "fraction="+fraction);
            // 将动画初始值startValue 和 动画结束值endValue 强制类型转换成Point对象
            Point startPoint = (Point) startValue;
            Point endPoint = (Point) endValue;

            // 根据fraction来计算当前动画的x和y的值
            int x = (int) (startPoint.x + fraction * (endPoint.x - startPoint.x));
            int y = (int) (startPoint.y + fraction * (endPoint.y - startPoint.y));
            Log.i("anim", "fractionX="+x);

            // 将计算后的坐标封装到一个新的Point对象中并返回
            return new Point(x, y);
        }
    }

第二段

//第二段只改变 x
    public void slowSlideScreenThumb(){
//        final ValueAnimator animator = ValueAnimator.ofInt(80, -10);
        final ValueAnimator animator = ValueAnimator.ofInt(layoutParams.x, layoutParams.x - 80);
        animator.setDuration(200);
        animator.setInterpolator(new LinearInterpolator());
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                Log.e("Screenshots","onAnimationEnd=");
                //windowManager.removeViewImmediate(screenShotThumbView);
                //screenShotThumbView.setTranslationX(-50);
                //第二段结束,执行第三段,移出屏幕
                dismissScreenThumbAnimation();
            }
        });
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int current = (int) animator.getAnimatedValue();
                Log.i("anim", "current="+current);
                //layoutParams.x = Math.abs(current);
                //screenShotThumbView.setTranslationX(current);
                layoutParams.x = current;
                windowManager.updateViewLayout(screenShotThumbView, layoutParams);
            }
        });
        animator.start();
    }

第三段

//执行第三段,移出屏幕
    private void dismissScreenThumbAnimation(){
        final ValueAnimator animator = ValueAnimator.ofInt(0, -thumbWidth);
        animator.setDuration(230);
        animator.setInterpolator(new LinearInterpolator());
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                Log.e("Screenshots","onAnimationEnd=");
                windowManager.removeViewImmediate(screenShotThumbView);
            }
        });
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int current = (int) animator.getAnimatedValue();
                Log.i("anim", "current="+current);
                screenShotThumbView.setTranslationX(current);
            }
        });
        animator.start();
    }

点击跳转

screenShotThumbView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //removeScreenShotThumbWindow();

        //Uri imageUri =  Uri.fromFile(queryNearestScreenShotFile());
        Uri imageUri =  FileProvider.getUriForFile(mContext,
                "com.cczheng.androiddemo.fileprovider", queryNearestScreenShotFile());

        // Create the intent to show the screenshot in gallery
        Intent launchIntent = new Intent(Intent.ACTION_VIEW);
        launchIntent.setDataAndType(imageUri, "image/png");
        launchIntent.setFlags(
                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        startActivity(launchIntent);
    }
});

//查询最新时间的截屏图片
private File queryNearestScreenShotFile(){
        final String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString()
                + "/Screenshots/";
        File rootFile = new File(path);
        File nearestFile = null;
        long maxModifiedTime = 0;
        for (File file : rootFile.listFiles()){
            long lastModified = file.lastModified();
            //Log.e("Screenshots","lastModified="+lastModified + " fileName="+file.getName());
            if (lastModified > maxModifiedTime){
                maxModifiedTime = lastModified;
                nearestFile = file;
            }
        }
        return nearestFile;
    }

上面获取跳转图片的 URI,需要采用 FileProvider 的方式,不然会崩溃,在 manifest 中添加如下配置

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.cczheng.androiddemo.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths"/>
</provider>

同时在 res 目录下新建 xml 文件夹,增加** file_paths.xml**

<?xml version="1.0" encoding="utf-8"?>
<paths >
    <external-path name="png" path="."/>
</paths>