探探位置模拟,打造仿陌陌视频播放页

Hook实现Android 微信、陌陌 、探探位置模拟

本篇为该系列的第三篇,将通过一个实际的业务需求来讲述ViewDragHelper的实际运用。

 最近需要对微信,陌陌等程序进行位置模拟
实现世界各地发朋友圈,搜索附近人的功能,本着站在巨人肩膀上的原则
爱网上搜索一番。

ViewDragHelper 的介绍以及初步使用请阅读这篇:ViewDragHelper –
介绍及简单用例ViewDragHelper
的源码以及Callback的详情介绍请阅读这篇:ViewDragHelper –
源码及原理解读利用DrageHelper 打造仿陌陌APP视频播放页的demo请阅读这篇:
ViewDragHelper – 打造仿陌陌视频播放页

 也找到一些 代码和文章,但是代码大都雷同而且都有一个弊端 比如说 微信
对目标函数实现hook之后第一次打开微信 第一次定位是可以改变的

介绍

首先,系统的DrawerLayout
抽屉想必大家都不陌生,它的侧重点在于左右滑动。鉴于已经有很多大牛写过类似的,咱们就不再过多地讲述这个了,有兴趣的朋友可以自行查找相关的文章。本篇文章主要讲解如何利用ViewDragHelper来打造一个可以下拉拖拽关闭以及左右滑动切换的功能。

  但是 我如果想更换地址的话
就需要重启手机了,重新加载hook了,试了很多次都是这样满足不了需求。

效果演示

QQ空间视频播放页也有这个下拉关闭的功能,效果图如下:

冠亚体育手机网站 1QZone.gif

若想要的功能仅仅只是它,那么可以直接参考第一篇文章的代码,会简洁很多,文章链接:ViewDragHelper

  • 介绍及简单用例

陌陌播放页的效果图:

冠亚体育手机网站 2Momo.gif

下面是本项目的效果图:

冠亚体育手机网站 3draggableView.gif

真机展示的效果可能会好点儿

冠亚体育手机网站 4horiztonal.gif冠亚体育手机网站 5vertical.gif

为了改进这个地方我们从gps定义的源代码流程开始看寻找hook系统函数的突破口 

正文

本文主要讲解的点有如下几个:

  1. 滑动方向判定。
  2. 如何限制为单个方向的拖拽。
  3. 事件分发以及拦截。
  4. 平移动画问题。
  5. 下拉时缩放及背景透明处理。
  6. 背景高斯图片替换处理。
  7. 嵌套ScrollView / RecyclerView事件冲突处理。
  8. 多点触控 Invalid pointerId 问题解决。

首先,
我们还是和第一篇文章一样,创建一个DragView(继承自ViewGroup),以及一个CallBack(继承自
ViewDragHelper.Callback)。然后进行相关初始化操作。

  1. 初始化ViewDragHelper。
  2. 初始化CallBack
    ,用于监听ViewDragHelper相关事件,回调给DraggableView。
  3. 初始化DraggableListener,用于回调给外部。

具体代码可参考demo,项目地址贴在文章底部。

代码总篇幅太长,就不贴完整源代码了,用伪代码描述大致实现思路。有需要完整代码的朋友可以自行下载GitHub
上面的demo。

onInterceptTouchEvent
方法里面判断了手势方向,以及滑动冲突,多点触控导预防处理。伪代码如下:

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) { if (!isEnabled { return false; } switch { case ACTION_DOWN: if (activePointerId == INVALID_POINTER) { return false; } case ACTION_UP or ACTION_CANCEL: viewDragHelper.cancel(); }return isViewUnderChild || shouldInterceptTouchEvent;}

onTouchEvent方法伪代码如下:

 @Override public boolean onTouchEvent(MotionEvent ev) { if (activePointerId == INVALID_POINTER_POINTER) { return false; } if (ev.getPointerCount { //屏蔽多指操作 Log.e(TAG, "onTouchEvent, getPointerCount > 1 "); mDragView.dispatchTouchEvent(cloneMotionEventWithAction(ev, MotionEvent.ACTION_CANCEL)); viewDragHelper.cancel(); return false; } switch (EVENT.ACTION) { case ACTION_DOWN: //记录按下位置的XY坐标 viewDragHelper.processTouchEvent; mDragView.dispatchTouchEvent; break; case ACTION_MOVE: //当前手指的XY坐标与按下坐标相减。 if (!isJudgeWay) {//还未判定滑动方向 //判断滑动方向 //从按下 ,到手指当前位置的移动距离 = 根号(X^2 + Y^2) //若移动距离超过系统移动默认阈值 if (mMoveDistance >= viewDragHelper.getTouchSlop { isJudgeWay = true; } } /** * 将事件分发给子控件的条件: * 1.没有被关闭 * 2.非顶部下滑动 * 3.向上滑动 */ if (isFullScreen) {//全屏状态 mDragView.dispatchTouchEvent; } else {//非全屏状态下 if(不在顶部下滑,或者上滑){ // 分发事件给子控件 mDragView.dispatchTouchEvent; }else if (已在ScrollView顶部,并且手势为下滑) { //顶部下拉拖拽,分发事件给DragHelper viewDragHelper.processTouchEvent; //分发CANCEL事件给子控件,以取消DOWN事件 } else if (MOVE_LEFT || MOVE_RIGHT) {//左右滑动 //分发事件给DragHelper进行左右拖拽 viewDragHelper.processTouchEvent; //分发CANCEL事件给子控件,以取消DOWN事件 } else { //都不符合条件 //分发 ACTION_CANCEL给子控件; } } break; default: if (ev.getAction() == MotionEvent.ACTION_UP) { //还原方向判定 } mDragView.dispatchTouchEvent; viewDragHelper.processTouchEvent; break; } //可见时消费掉触摸事件,避免底层其他控件触发 return !isClosedAtBottom(); }

在onInterceptTouchEvent
方法中处理事件的拦截逻辑,当手指点在子控件的有效范围区域,DraggableView
将会拦截事件,触发自身的onTouchEvent方法。

在onTouchEvent方法中判断滑动方向,并根据方向以及是否在ScrollView的顶部来分发事件。若在ScrollView顶部并且符合下拉拖拽,则将事件分发给viewDragHelper,否则分发给子控件。具体细节可参考源代码。

 我也是看完之后才找到hook的地方 LocationMangerService  这个类

Y轴方向改变监听

当子控件的位置发生改变时,会触发ViewDragHelper.Callback的onViewPositionChanged方法。我们在创建CallBack对象时,在构造方法传入了DraggableView对象进来。此时我们调用
draggableView.onViewPositionChanged()方法回调View的坐标参数。参数根据实际需求添加即可,这里我们只需要用上top,即顶部位置,因此只写了一个参数。然后在draggableView中通过listener将其回调给外部。

@Override
 public void reportLocation(Location location, boolean passive) {
 checkCallerIsProvider(); //检测权限和uid

 if (!location.isComplete()) {
  Log.w(TAG, "Dropping incomplete location: " + location);
  return;
 }
  //发送位置信息
 mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location);
 Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location);
 m.arg1 = (passive ? 1 : 0);
 mLocationHandler.sendMessageAtFrontOfQueue(m);
 }
限制垂直方向只能下拉,不能往上拖拽。
 @Override public int clampViewPositionVertical(View child, int top, int dy) { Log.d(TAG, "clampViewPositionVertical" + ", top" + top + ", dy:" + dy); //水平滑动时触发了竖直方向,屏蔽掉 if (draggableView.Move_Way.equals(draggableView.MOVE_LEFT) || draggableView.Move_Way.equals(draggableView.MOVE_RIGHT)) { return 0; } mRangeY += dy; return Math.max(mRangeY, 0); }

由于viewDragHelper内部,会因为滑动嵌套的原因,在滑动距离未超过系统的mTouchSlop值时,会触发cancel
导致重置到原位,因此这里我们通过累加dy来实现。并且保证 return
大于0,不让子控件向上拖拽。从而实现单向地下拉拖拽的功能。

那么我们可以hook掉这个location的参数
修改为我们想要定位的地方就可以实现效果了,

水平方向滑动

若手势为左右滑动,则屏蔽掉垂直方向,限制只能左右滑动。

 @Override public int clampViewPositionHorizontal(View child, int left, int dx) { // Log.d(TAG, "clampViewPositionHorizontal" + ", left" + left + ", dx:" + dx); //竖直滑动时触发了水平方向,屏蔽掉 if (draggableView.Move_Way.equals(draggableView.MOVE_TOP) || draggableView.Move_Way.equals(draggableView.MOVE_BOTTOM)) { return 0; } return left; }
 XposedHelpers.findAndHookMethod("com.android.server.LocationManagerService", lpparam.classLoader, "reportLocation", Location.class, boolean.class, new XC_MethodHook() {
  @Override
  protected void afterHookedMethod(MethodHookParam param) throws Throwable {
  super.afterHookedMethod(param);
  Location location = (Location) param.args[0];
  XposedBridge.log("实际 系统 经度"+location.getLatitude() +" 系统 纬度"+location.getLongitude() +"系统 加速度 "+location.getAccuracy());
  XSharedPreferences xsp =new XSharedPreferences("com.markypq.gpshook","markypq");
  if (xsp.getBoolean("enableHook",true)){
   double latitude = Double.valueOf(xsp.getString("lan","117.536246"))+ (double) new Random().nextInt(1000) / 1000000 ;
   double longtitude = Double.valueOf(xsp.getString("lon","36.681752"))+ (double) new Random().nextInt(1000) / 1000000 ;
   location.setLongitude(longtitude);
   location.setLatitude(latitude);
   XposedBridge.log("hook 系统 经度"+location.getLatitude() +" 系统 纬度"+location.getLongitude() +"系统 加速度 "+location.getAccuracy());
  }

  }
 });
手指松开,计算并处理滑动

ViewDragHelper的 ACTION_冠亚体育手机网站,UP
或CANCEL事件触发时,会回调onViewReleased方法。我们需要在该方法里面判断已移动距离和方向,让子控件继续自动滑动到指定的位置。

 @Override public void onViewReleased(View releasedChild, float xVel, float yVel) { super.onViewReleased(releasedChild, xVel, yVel); //Log.d(TAG, "onViewReleased" + "xVel:" + xVel + ", yVel:" + yVel); mRangeY = 0; int top = releasedChild.getTop(); //获取子控件Y值 int left = releasedChild.getLeft(); //获取子控件X值 if (Math.abs <= Math.abs {//竖直滑动 triggerOnReleaseActionsWhileVerticalDrag; } else if (Math.abs < Math.abs {//水平滑动 triggerOnReleaseActionsWhileHorizontalDrag; } } /** * 计算竖直方向的滑动 */ private void triggerOnReleaseActionsWhileVerticalDrag(float moveY) { //Log.d(TAG, "ReleaseVerticalDrag"+", moveY:" + moveY); if (moveY < 0 && moveY <= -Y_MIN_DISTANCE) { draggableView.onReset(); } else if (moveY > 0 && moveY >= Y_MIN_DISTANCE) { draggableView.closeToBottom(); } else { draggableView.onReset(); } } /** * 计算水平方向的滑动 */ private void triggerOnReleaseActionsWhileHorizontalDrag(float moveX) {// Log.d(TAG, "ReleaseHorizontalDrag"+", moveX:" + moveX); if (moveX < 0 && moveX <= -X_MIN_DISTANCE) { draggableView.closeToLeft(); } else if (moveX > 0 && moveX >= X_MIN_DISTANCE) { draggableView.closeToRight(); } else { draggableView.onReset(); } }

其中,viewDragHelper的 settleCapturedViewAt() 以及
smoothSlideViewTo()方法都需要利用 computeScroll() 来实时刷新位置。

 @Override public void computeScroll() { if (viewDragHelper.continueSettling { ViewCompat.postInvalidateOnAnimation; } }

在onViewReleased
方法触发并计算滑动方向之后。就会根据计算结果判定是否回弹或者滑动到屏幕指定位置,并通过listener回调通知外部。

/** * Called when the view is minimized. */ void onClosedToBottom(); /** * Called when the view is closed to the left. */ void onClosedToLeft(); /** * Called when the view is closed to the right. */ void onClosedToRight(); /** * Called when the child view location changed * @param top */ void onBackgroundChanged;

DraggableListener接口的四个方法分别是下拉关闭,向左切换,向右切换,位置改变时回调。

我们通过onBackgroundChanged回调方法获取
子控件top位置的改变值。然后设置透明及缩放动画。具体代码如下:

 @Override public void onBackgroundChanged { int newAlpha = 255 -  (255 *  top /  dragView.getRootView().getHeight; if (newAlpha == 255) { dragView.setBackgroundResource(R.mipmap.bg_gauss_blur); } else { dragView.setBackgroundColor(ContextCompat.getColor(this, R.color.colorBackground)); } dragView.getBackground().setAlpha; if (newAlpha < 216) { //达到子控件缩放最小值,原大小的0.85倍 scrollView.setScaleX; scrollView.setScaleY; } else {// newAlpha >= 204 平滑缩放 scrollView.setScaleX(1 - (255.0f -  newAlpha) / 255); scrollView.setScaleY(1 - (255.0f -  newAlpha) / 255); } }

ViewDragHelper实质上也是通过分析MotionEvent
事件来进行操控子控件的移动。在实际使用过程中,我们特别需要注意控件嵌套滑动的问题。通过这个demo,我们想必也会发现,利用ViewDragHelper可以帮助我们省去一部分滑动动画的繁琐逻辑。但我们需要更加注意事件的滑动冲突,合理分发事件。通过这个系列,相信大家对于View事件分发机制及滑动冲突处理也会有一个更加深刻的认识。若有疑问,欢迎留言讨论~

GitHub
项目地址传送门:ViewDragHelperDemo有兴趣的朋友可以下载完整喜欢的朋友可以
star 一波~

如果我想主动调用这个函数 必须要得到这个LocationMangerService 的对象
获取这个对象可以通过hook LocationManager 的构造函数获取,

 XposedBridge.hookAllConstructors(LocationManager.class,new XC_MethodHook() {
  @Override
  protected void afterHookedMethod(MethodHookParam param) throws Throwable {
  super.afterHookedMethod(param);
  if (param.args.length==2) {
   Context context = (Context) param.args[0]; //这里的 context
   XposedBridge.log(" 对 "+getProgramNameByPackageName(context)+" 模拟位置");
   //把权限的检查 hook掉
   XposedHelpers.findAndHookMethod(context.getClass(), "checkCallingOrSelfPermission", String.class, new XC_MethodHook() {
   @Override
   protected void afterHookedMethod(MethodHookParam param) throws Throwable {
    super.afterHookedMethod(param);
    if (param.args[0].toString().contains("INSTALL_LOCATION_PROVIDER")){
    param.setResult(PackageManager.PERMISSION_GRANTED);
    }
   }
   });
   XposedBridge.log("LocationManager : " + context.getPackageName() + " class:= " + param.args[1].getClass().toString());
   //获取到 locationManagerService 主动调用 对象的 reportLocation 方法 可以去模拟提供位置信息
   //这里代码中并没有涉及到主动调用
   Object locationManagerService = param.args[1];
  }
  }
 });

冠亚体育手机网站 6

冠亚体育手机网站 7

当然还需要hook一些其他的辅助函数 ,这些函数都可以在 Android studio
中看到Java的代码 我们就无需过多解释了 上 源代码

源码下载

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

您可能感兴趣的文章:

  • Android开发中RecyclerView模仿探探左右滑动布局功能
  • Android开源堆叠滑动控件仿探探效果
  • 如何在Android中实现左右滑动的指引效果
  • android
    左右滑动+索引图标实现方法与代码
  • Android实现左右滑动效果的方法详解
  • 如何在Android中实现渐显按钮的左右滑动效果
  • Android实现顶部导航菜单左右滑动效果
  • 详解Android中实现ListView左右滑动删除条目的方法
  • Android自定义控件实现可左右滑动的导航条
  • Android仿探探卡片式滑动效果实现

Post Author: admin

发表评论

电子邮件地址不会被公开。 必填项已用*标注