美文网首页程序员
android 右滑返回

android 右滑返回

作者: 沉寂chenji | 来源:发表于2017-09-06 22:28 被阅读0次

    类似于微信的右滑返回,在BaseActivity里利用dispatchTouchEvent()拦截右滑动作,利用setTranslationX()实现动画,在DecorView里添加View作为滑动时的左侧阴影。

    image.png

    渐进步骤:

    1. 设置activity背景透明
    2. 重写finish()等方法设置activity的跳转动画
    3. 重写dispatchTouchEvent()拦截 所需要 右滑动作
    4. 重写onTouchEvent()给根布局设置偏移量
    5. 添加滑动时上层activity的左侧阴影
    6. 滑动时关联下层activity滑动

    注意:步骤中的代码为了不关联到后面的步骤,会与最终的有点不同

    背景透明

        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>
    

    activity的跳转动画

    根据项目需要,重写用到的startActivity(Intent intent),startActivityForResult(Intent intent, int requestCode),finish()等activity跳转和销毁方法

    @Override
    public void startActivity(Intent intent) {
        super.startActivity(intent);
        overridePendingTransition(R.anim.slide_right_in, 0);
    }
    
    @Override
    public void startActivityForResult(Intent intent, int requestCode) {
        super.startActivityForResult(intent, requestCode);
        overridePendingTransition(R.anim.slide_right_in, 0);
    }
    
    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(0, R.anim.slide_right_out);
    }
    
    //R.anim.slide_right_in
    <set xmlns:android="http://schemas.android.com/apk/res/android">
      <translate
          android:duration="300"
          android:fromXDelta="100%"
          android:toXDelta="0"
          android:fromYDelta="0"
          android:toYDelta="0"/>
    </set>
    
    //R.anim.slide_right_out
    <set xmlns:android="http://schemas.android.com/apk/res/android">
      <translate
          android:duration="300"
          android:fromXDelta="0"
          android:toXDelta="100%"
          android:fromYDelta="0"
          android:toYDelta="0" />
    </set>
    

    拦截右滑动作

    所有的触摸事件通过activity.dispatchTouchEvent(MotionEvent ev)向view分发。
    手指在X轴方向右滑动50~100px时,判断是否为(产品经理要)右滑动作

    • 手指落点为全屏幕,X方向滑动距离要比Y方向的大一些;
    • 手指落点为左侧边,X方向滑动距离有一些就行
    private float downX = 0;
    private float downY = 0;
    private boolean shouldIntercept = false;
    private boolean hadJudge = false;
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (shouldIntercept) {
            return onTouchEvent(ev);
        }
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                downX = ev.getRawX();
                downY = ev.getRawY();
                hadJudge = false;
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (hadJudge) break;
                if (ev.getRawX() == downX) break;
                if (ev.getRawX() < downX) {
                    //左滑
                    hadJudge = true;
                    break;
                }
                if (ev.getRawX() - downX >=100){
                    //超出判断距离
                    hadJudge = true;
                    break;
                }
                if (ev.getRawX() - downX > 50) {
                    //x轴右滑50~100px
                    float rate = (ev.getRawX() - downX) / (Math.abs(ev.getRawY() - downY));
                    if ((downX < 50 && rate > 0.5f) || rate > 2) {
                        shouldIntercept = true;
                    }
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                downX =0;
                downY = 0;
                shouldIntercept = false;
                hadJudge=false;
                break;
            }
        }
        //Activity的默认分发
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return true;
    }
    

    根布局位移动画

    根据手指滑动距离设置根布局偏移距离,用滑动距离和手指抬起时的速度判断是否返回

    private View rootView = null;
    private float lastX = -1;    
    private VelocityTracker velocityTracker = null;
    private int maxFlingVelocity;
    
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (rootView == null) {
            ViewGroup rootGroup = (ViewGroup) (getWindow().getDecorView());
            rootView = rootGroup.getChildAt(0);
        }
        //测量手指抬起时的速度
        if (velocityTracker == null) {
            velocityTracker = VelocityTracker.obtain();
            maxFlingVelocity = ViewConfiguration.get(this).getScaledMaximumFlingVelocity();
        }
        velocityTracker.addMovement(event);
    
    
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                lastX = event.getRawX();
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (lastX == -1) {
                    lastX = event.getRawX();
                    break;
                }
                //根据手指滑动距离设置根布局偏移距离
                rootView.setTranslationX(rootView.getTranslationX() + event.getRawX() - lastX);
                if (rootView.getTranslationX() < 0) rootView.setTranslationX(0);
                lastX = event.getRawX();
                break;
            }
            case MotionEvent.ACTION_UP: {
                //测量手指抬起时的速度
                velocityTracker.computeCurrentVelocity(1000, maxFlingVelocity);
                float velocityX = velocityTracker.getXVelocity();
                if (velocityTracker != null) {
                    velocityTracker.recycle();
                    velocityTracker = null;
                }
    
                //判断是否返回
                if (downX < 50 && velocityX > 1000) {
                    //手指在左侧边落下,返回
                    onBack();
                } else if (velocityX > 3600) {
                    //手指快速滑动,返回
                    onBack();
                } else if (rootView.getTranslationX() > ConvertUtil.getWidthInPx() * 0.3) {
                    //滑动距离超过30%屏幕宽度,返回
                    onBack();
                } else {
                    //不返回,根布局偏移归零
                    rootView.animate().translationX(0).setDuration(200).start();
                }
                
                lastX = -1;
                shouldIntercept = false;
                hadJudge=false;
                downX = 0;
                downY = 0;
                break;
            }
        }
        return super.onTouchEvent(event);
    }
    

    添加左侧阴影

    Activity的最顶层View为DecorView,DecorView是一个FrameLayout,里面只有一个Linearlayout,Linearlayout包含着标题栏和自定义布局(setContentView)。
    上一步跟随手指滑动进行偏移的就是Linearlayout,现在要在DecorView里添加一个View,设置背景作为阴影,并跟随Linearlayout进行移动

    private View shadowView = null;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (rootView == null) {
            //添加阴影
            
            ViewGroup rootGroup = (ViewGroup) (getWindow().getDecorView());
    
            shadowView = new View(this);
            rootGroup.addView(shadowView, 0);
            ViewGroup.LayoutParams params = shadowView.getLayoutParams();
            //阴影宽度
            params.width = (int) ((float) ConvertUtil.getWidthInPx() * 0.05f);
            params.height = ConvertUtil.getHeightInPx();
            shadowView.setLayoutParams(params);
            shadowView.setBackgroundResource(R.drawable.shadow_grey_h);
            shadowView.setTranslationX(params.width);
    
            rootView = rootGroup.getChildAt(1);
        }
    
        ...
    
        switch (event.getAction()) {
            ...
            case MotionEvent.ACTION_MOVE: {
                if (lastX == -1) {
                    lastX = event.getRawX();
                    break;
                }
                //根据手指滑动距离设置根布局偏移距离
                rootView.setTranslationX(rootView.getTranslationX() + event.getRawX() - lastX);
                if (rootView.getTranslationX() < 0) rootView.setTranslationX(0);
                //阴影跟随根布局移动
                shadowView.setTranslationX(-shadowView.getWidth()+rootView.getTranslationX());
                lastX = event.getRawX();
                break;
            }
            case MotionEvent.ACTION_UP: {
                ...
                } else {
                    //不返回,根布局偏移归零
                    rootView.animate().translationX(0).setDuration(200).start();
                    //阴影偏移归零
                    shadowView.animate().translationX(-shadowView.getWidth()).setDuration(200).start();
                }
                ...
            }
        }
        ...
    }
    

    关联下层activity滑动

    • 保存所有的activity以获取下层activity
    • 给下层activity添加退出和进入的动画
    • 在上层activity滑动时调用下层滑动

    获取下层activity

    private static ArrayList<Activity> Activity_Stack = new ArrayList<>();
    private BaseSwipeBackActivity lastActivity = null;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        maxFlingVelocity = ViewConfiguration.get(this).getScaledMaximumFlingVelocity();
    
        if (!Activity_Stack.contains(this)) Activity_Stack.add(this);
        if (Activity_Stack.size() >= 2) {
            Activity last = Activity_Stack.get(Activity_Stack.size() - 2);
            if (last instanceof BaseSwipeBackActivity) {
                lastActivity = (BaseSwipeBackActivity) last;
            }
        }
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Activity_Stack.remove(this);
    }
    

    下层activity的退出、进入动画

    private void lowerActivityExitAnim() {
        if (rootView == null) return;
        //只移动30%
        rootView.animate().translationX(-ConvertUtil.getWidthInPx() * 0.3f).setDuration(300).start();
    }
    
    private void lowerActivityEnterAnim(float upperTranslationX) {
        if (rootView == null) return;
        //保证滑动退出时,上下层时间同步
        float r = 1-upperTranslationX/ (float) ConvertUtil.getWidthInPx();
        rootView.animate().translationX(0).setDuration((long) (300f * r)).start();
    }
    

    在跳转时,调用下层activity的退出、进入动画

    @Override
    public void startActivity(Intent intent) {
        super.startActivity(intent);
        overridePendingTransition(R.anim.slide_right_in, 0);
        lowerActivityExitAnim();
    }
    
    @Override
    public void startActivityForResult(Intent intent, int requestCode) {
        super.startActivityForResult(intent, requestCode);
        overridePendingTransition(R.anim.slide_right_in, 0);
        lowerActivityExitAnim();
    }
    
    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(0, R.anim.slide_right_out);
        if (lastActivity != null) lastActivity.lowerActivityEnterAnim(rootView.getTranslationX());
        Activity_Stack.remove(this);
    }
    

    上层activity滑动时关联下层滑动

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        ...
        switch (event.getAction()) {
            ...
            case MotionEvent.ACTION_MOVE: {
                ...
                //根据手指滑动距离设置根布局偏移距离
                rootView.setTranslationX(rootView.getTranslationX() + event.getRawX() - lastX);
                if (rootView.getTranslationX() < 0) rootView.setTranslationX(0);
                //阴影跟随根布局移动
                shadowView.setTranslationX(-shadowView.getWidth() + rootView.getTranslationX());
                //下层activity跟随移动
                if (lastActivity != null && lastActivity.rootView != null)
                    //-ConvertUtil.getWidthInPx() * 0.3f初始的偏移
                    lastActivity.rootView.setTranslationX(-ConvertUtil.getWidthInPx() * 0.3f + rootView.getTranslationX() * 0.3f);
                ...
            }
            case MotionEvent.ACTION_UP: {
                ...
                } else {
                    //不返回,根布局偏移归零
                    rootView.animate().translationX(0).setDuration(200).start();
                    //阴影偏移归零
                    shadowView.animate().translationX(-shadowView.getWidth()).setDuration(200).start();
                    //下层activity偏移复原
                    if (lastActivity != null)
                        lastActivity.lowerActivityExitAnim();
                }
                ...
            }
        }
    
        return super.onTouchEvent(event);
    }
    

    完整的

    public abstract class BaseSwipeBackActivity extends AppCompatActivity {
    
        private static ArrayList<Activity> Activity_Stack = new ArrayList<>();
        private BaseSwipeBackActivity lastActivity = null;
    
        private View rootView = null;
        private View shadowView = null;
    
        private float downX = 0;
        private float downY = 0;
        private boolean shouldIntercept = false;
        private boolean hadJudge = false;
    
        private float lastX = -1;
        private VelocityTracker velocityTracker = null;
        private int maxFlingVelocity;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            maxFlingVelocity = ViewConfiguration.get(this).getScaledMaximumFlingVelocity();
    
            if (!Activity_Stack.contains(this)) Activity_Stack.add(this);
            if (Activity_Stack.size() >= 2) {
                Activity last = Activity_Stack.get(Activity_Stack.size() - 2);
                if (last instanceof BaseSwipeBackActivity) {
                    lastActivity = (BaseSwipeBackActivity) last;
                }
            }
        }
    
        @Override
        protected void onResume() {
            initShadow();
            super.onResume();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            Activity_Stack.remove(this);
        }
    
        @Override
        public void startActivity(Intent intent) {
            super.startActivity(intent);
            overridePendingTransition(R.anim.slide_right_in, 0);
            lowerActivityExitAnim();
        }
    
        @Override
        public void startActivityForResult(Intent intent, int requestCode) {
            super.startActivityForResult(intent, requestCode);
            overridePendingTransition(R.anim.slide_right_in, 0);
            lowerActivityExitAnim();
        }
    
        @Override
        public void finish() {
            super.finish();
            overridePendingTransition(0, R.anim.slide_right_out);
            if (lastActivity != null) lastActivity.lowerActivityEnterAnim(rootView.getTranslationX());
            Activity_Stack.remove(this);
        }
    
        private void initShadow() {
            if (shadowView == null) {
                ViewGroup rootGroup = (ViewGroup) (getWindow().getDecorView());
    
                shadowView = new View(this);
                rootGroup.addView(shadowView, 0);
                ViewGroup.LayoutParams params = shadowView.getLayoutParams();
                //阴影宽度
                params.width = (int) ((float) ConvertUtil.getWidthInPx() * 0.05f);
                params.height = ConvertUtil.getHeightInPx();
                shadowView.setLayoutParams(params);
                //渐变背景作为阴影
                shadowView.setBackgroundResource(R.drawable.shadow_grey_h);
                shadowView.setTranslationX(-params.width);
    
                rootView = rootGroup.getChildAt(1);
            }
        }
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (shouldIntercept) {
                return onTouchEvent(ev);
            }
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    downX = ev.getRawX();
                    downY = ev.getRawY();
                    hadJudge = false;
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    if (hadJudge) break;
                    if (ev.getRawX() == downX) break;
                    if (ev.getRawX() < downX) {
                        //左滑
                        hadJudge = true;
                        break;
                    }
                    if (ev.getRawX() - downX >= 100) {
                        //超出判断距离
                        hadJudge = true;
                        break;
                    }
                    if (ev.getRawX() - downX > 50) {
                        //x轴右滑50~100px
                        float rate = (ev.getRawX() - downX) / (Math.abs(ev.getRawY() - downY));
                        if ((downX < 50 && rate > 0.5f) || rate > 2) {
                            shouldIntercept = true;
                        }
                    }
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    downX = 0;
                    downY = 0;
                    shouldIntercept = false;
                    hadJudge = false;
                    break;
                }
            }
            //Activity的默认分发
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                onUserInteraction();
            }
            if (getWindow().superDispatchTouchEvent(ev)) {
                return true;
            }
            return true;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            initShadow();
    
            //测量手指抬起时的速度
            if (velocityTracker == null) {
                velocityTracker = VelocityTracker.obtain();
                maxFlingVelocity = ViewConfiguration.get(this).getScaledMaximumFlingVelocity();
            }
            velocityTracker.addMovement(event);
    
    
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    lastX = event.getRawX();
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    if (lastX == -1) {
                        lastX = event.getRawX();
                        break;
                    }
                    //根据手指滑动距离设置根布局偏移距离
                    rootView.setTranslationX(rootView.getTranslationX() + event.getRawX() - lastX);
                    if (rootView.getTranslationX() < 0) rootView.setTranslationX(0);
                    //阴影跟随根布局移动
                    shadowView.setTranslationX(-shadowView.getWidth() + rootView.getTranslationX());
                    //下层activity跟随移动
                    if (lastActivity != null && lastActivity.rootView != null)
                        lastActivity.rootView.setTranslationX(-ConvertUtil.getWidthInPx() * 0.3f + rootView.getTranslationX() * 0.3f);
    
                    lastX = event.getRawX();
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    //测量手指抬起时的速度
                    velocityTracker.computeCurrentVelocity(1000, maxFlingVelocity);
                    float velocityX = velocityTracker.getXVelocity();
                    if (velocityTracker != null) {
                        velocityTracker.recycle();
                        velocityTracker = null;
                    }
    
                    //判断是否返回
                    if (downX < 50 && velocityX > 1000) {
                        //手指在左侧边落下,返回
                        onBack();
                    } else if (velocityX > 3600) {
                        //手指快速滑动,返回
                        onBack();
                    } else if (rootView.getTranslationX() > ConvertUtil.getWidthInPx() * 0.3) {
                        //滑动距离超过30%屏幕宽度,返回
                        onBack();
                    } else {
                        //不返回,根布局偏移归零
                        rootView.animate().translationX(0).setDuration(200).start();
                        //阴影偏移归零
                        shadowView.animate().translationX(-shadowView.getWidth()).setDuration(200).start();
                        //下层activity偏移复原
                        if (lastActivity != null) lastActivity.lowerActivityExitAnim();
                    }
    
                    lastX = -1;
                    shouldIntercept = false;
                    hadJudge = false;
                    downX = 0;
                    downY = 0;
                    break;
                }
            }
    
            return super.onTouchEvent(event);
        }
        
    
        private void lowerActivityExitAnim() {
            if (rootView == null) return;
            rootView.animate().translationX(-ConvertUtil.getWidthInPx() * 0.3f).setDuration(300).start();
        }
    
        private void lowerActivityEnterAnim(float upperTranslationX) {
            if (rootView == null) return;
            float r = 1-upperTranslationX/ (float) ConvertUtil.getWidthInPx();
            rootView.animate().translationX(0).setDuration(r == 0.0f ? 10 : (long) (300f * r)).start();
        }
    
        //退出
        abstract public void onBack();
    }

    相关文章

      网友评论

        本文标题:android 右滑返回

        本文链接:https://www.haomeiwen.com/subject/lvivjxtx.html