效果图
修改后动画如下
系统原动画如下
三指截屏
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 的坐标系是以自身左上角的点为原点的。
没错,就像你看到的上图,这个移出动画我们需要分三段来完成(我们先在 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>