基本思想

我们的滑动逻辑主要是利用View的scrollBy() 方法, scrollTo()方法和Scroller类来实现的
当手指拖动视图的时候,我们监听手指在屏幕上滑动的距离
利用View的scrollBy() 方法使得View随着手指的滑动而滑动
而当手指离开屏幕,我们在根据逻辑使用Scroller类startScroll()方法设置滑动的参数,然后再根据View的scrollTo进行滚动。
对于View的滑动,存在一些Touch事件消费的处理等问题,最主要的就是Activity里面有一些ListView、 GridView、ScrollView等控件
假如我们Activity里面存在ListView、GridView等控件的话,我们对Activity的最外层布局进行滚动根本就无效果,因为Touch事件被ListView、GridView等控件消费了,所以Activity的最外层布局根本得不到Touch事件,也就实现不了Touch逻辑了
为了解决此Touch事件问题,我们将OnTouchListener直接设置到ListView、GridView上面,这样子就避免了Activity的最外层接受不到Touch事件的问题了

核心:一个自定义View

public class SwipeBackLayout extends FrameLayout {
    //常量
    /**手指向右滑动时的最小【滑动】距离(只有滑动超过此距离才滚动view)*/
    public static int X_MIN_DISTANCE_IF_MOVE = 8;
    /**手指向右滑动时的最大【起始】距离(防止误操作,只有从左边缘滑动才有效),这个值最好大于上面的值*/
    public static int X_MIN_START_DISTANCE = 10;
    /**手指向右滑动时的最小【滑动】距离(防止误操作,只有滑动超过一定距离才关闭)*/
    public static int X_MIN_DISTANCE_FROM_LEFT = 90;
    /**自动滚动到左侧(回到初始位置)消耗的时间*/
    public static final int TIME_MOVE_TO_LEFT = 20;
    /**自动滚动到右侧(关闭应用前)消耗的时间*/
    public static final int TIME_MOVE_TO_RIGHT = 200;
    //一些可以设置的成员
    /**滑动时左边缘是否显示阴影*/
    private boolean isShowShadow = true;
    /**当touch位置有ViewPager,但是ViewPager不是在item0时,是否拦截【从屏幕边缘down】的滑动事件*/
    private boolean isInterceptWhenTouchViewPagerIfNotFirst = false;
    /**滑动时左边缘添加阴影*/
    private Drawable mShadowDrawable = getResources().getDrawable(R.drawable.shadow_left);
    //临时变量
    /**是否要finish掉Activity*/
    private boolean isFinish;
    private Activity mActivity;
    /**记录按下时的触摸点、移动时的触摸点在屏幕上的X坐标*/
    private int downX, touchX;
    private Scroller mScroller = new Scroller(getContext());
    private List<ViewPager> mViewPagers = new LinkedList<ViewPager>();
    public SwipeBackLayout(Context context) {
        this(context, null);
    }
    public SwipeBackLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        DisplayMetrics metric = new DisplayMetrics();
        ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metric);
        X_MIN_DISTANCE_FROM_LEFT = (int) (metric.widthPixels * 0.2f);//屏幕宽的1/5
        //滑动的时候,手的移动大于这个距离才开始移动控件;如果小于这个距离就不触发移动控件。ViewPage就是用这个距离来判断用户是否翻页的
        X_MIN_DISTANCE_IF_MOVE = ViewConfiguration.get(context).getScaledTouchSlop();
        Log.i("bqt", X_MIN_DISTANCE_FROM_LEFT + "---" + X_MIN_DISTANCE_IF_MOVE);//144-16
        X_MIN_START_DISTANCE = X_MIN_DISTANCE_IF_MOVE + 2;//这个值最好大于X_MIN_DISTANCE_IF_MOVE
    }
    //必须手动调用的方法
    public void attachToActivityAndAsRootLayout(Activity activity) {
        mActivity = activity;
        FrameLayout decorView = (FrameLayout) activity.getWindow().getDecorView();//所有窗口的根View
        ViewGroup decorChild = (ViewGroup) decorView.getChildAt(0);//封装内容区域和ActionBar区域的容器
        decorView.removeView(decorChild);
        this.addView(decorChild);
        decorView.addView(this);
    }
    //******************************************************************************************
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (!isInterceptWhenTouchViewPagerIfNotFirst) {
            ViewPager mViewPager = getMyTouchViewPager(mViewPagers, event);
            //如果存在ViewPager并且ViewPager不是处在第一个Item,我们才拦截Touch事件,否则不拦截(Touch事件由ViewPager处理)
            if (mViewPager != null && mViewPager.getCurrentItem() != 0) return super.onInterceptTouchEvent(event);
        }
        switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            downX = (int) event.getRawX();//getRawX获取的是相对父View(也即整个屏幕左上角)的位置坐标
            touchX = downX;
            break;
        case MotionEvent.ACTION_MOVE:
            if (downX <= X_MIN_START_DISTANCE //不是从屏幕左边缘开始的不拦截
                    && event.getRawX() - downX > X_MIN_DISTANCE_IF_MOVE) return true;//滑动距离太小时暂不拦截
            break;
        }
        return super.onInterceptTouchEvent(event);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
        case MotionEvent.ACTION_MOVE:
            if (event.getRawX() - downX > X_MIN_DISTANCE_IF_MOVE) scrollBy(touchX - (int) event.getRawX(), 0);//将View中的内容滚动指定距离
            touchX = (int) event.getRawX();
            break;
        case MotionEvent.ACTION_UP:
            if (Math.abs(getScrollX()) >= X_MIN_DISTANCE_FROM_LEFT) scrollRightOrLeft(true);//当滑动的距离大于我们设定的最小距离时,滑到右侧
            else scrollRightOrLeft(false);//当滑动的距离小于我们设定的最小距离时,回到起始位置
            break;
        }
        return true;
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) getAlLViewPager(mViewPagers, this);//【递归】遍历整个View树,获取里面的ViewPager的集合
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {//调用View.onDraw为绘制VIew本身,调用dispatchDraw为绘制自己的孩子
        //Called by draw to draw the child views. This may be overridden by derived classes to gain control just before its children are drawn 
        super.dispatchDraw(canvas);
        if (isShowShadow && mShadowDrawable != null) {
            int left = getLeft() - mShadowDrawable.getIntrinsicWidth();
            int right = left + mShadowDrawable.getIntrinsicWidth();
            mShadowDrawable.setBounds(left, getTop(), right, getBottom());
            mShadowDrawable.draw(canvas);
        }
    }
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
            if (mScroller.isFinished() && isFinish) mActivity.finish();
        }
    }
    //******************************************************************************************
    /**
     * 【递归】遍历整个View树,获取里面的ViewPager的集合
     */
    private void getAlLViewPager(List<ViewPager> mViewPagers, ViewGroup parent) {
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            if (child instanceof ViewPager) mViewPagers.add((ViewPager) child);
            else if (child instanceof ViewGroup) getAlLViewPager(mViewPagers, (ViewGroup) child);
        }
    }
    /**
     * 返回我们touch范围内的那个ViewPager
     */
    private ViewPager getMyTouchViewPager(List<ViewPager> mViewPagers, MotionEvent ev) {
        if (mViewPagers == null || mViewPagers.size() == 0) return null;
        Rect mRect = new Rect();
        for (ViewPager viewPager : mViewPagers) {
            viewPager.getHitRect(mRect);
            if (mRect.contains((int) ev.getX(), (int) ev.getY())) return viewPager;
        }
        return null;
    }
    /**
     * 滚动出界面或滚动到起始位置
     */
    private void scrollRightOrLeft(boolean toRight) {
        isFinish = toRight;
        if (toRight) mScroller.startScroll(getScrollX(), 0, -getWidth() - getScrollX(), 0, TIME_MOVE_TO_RIGHT);
        else mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0, TIME_MOVE_TO_LEFT);//int startX, int startY, int dx, int dy, int duration
        postInvalidate();//刷新界面
    }
    //get和set方法******************************************************************************************
    public boolean isInterceptWhenTouchViewPagerIfNotFirst() {
        return isInterceptWhenTouchViewPagerIfNotFirst;
    }
    public void setInterceptWhenTouchViewPagerIfNotFirst(boolean isInterceptWhenTouchViewPagerIfNotFirst) {
        this.isInterceptWhenTouchViewPagerIfNotFirst = isInterceptWhenTouchViewPagerIfNotFirst;
    }
    public Drawable getmShadowDrawable() {
        return mShadowDrawable;
    }
    public void setmShadowDrawable(Drawable mShadowDrawable) {
        this.mShadowDrawable = mShadowDrawable;
    }
    public boolean isShowShadow() {
        return isShowShadow;
    }
    public void setShowShadow(boolean isShowShadow) {
        this.isShowShadow = isShowShadow;
    }

}

Activity基类

/**
 * 想要实现向右滑动删除Activity效果只需要继承SwipeBackActivity即可
 */
public class SwipeBackActivity extends Activity {
    protected SwipeBackLayout rootLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);//可以放在super.onCreate之后,但必须放在attachToActivityAndAsRootLayout之前
        super.onCreate(savedInstanceState);
        rootLayout = new SwipeBackLayout(this);
        rootLayout.attachToActivityAndAsRootLayout(this);
    }
    @Override
    public void startActivity(Intent intent) {
        super.startActivity(intent);
        overridePendingTransition(R.anim.base_slide_right_in, R.anim.base_slide_remain);
    }
    @Override
    public void onBackPressed() {
        super.onBackPressed();
        overridePendingTransition(0, R.anim.base_slide_right_out);
    }
    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(0, R.anim.base_slide_right_out);
    }

}

MainActivity

public class MainActivity extends ListActivity {
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        String[] array = { "普通的Activity", //
                "普通的Activity:不显示阴影",//
                "普通的Activity:自定义阴影", //
                "普通的Activity:背景透明",//
                "普通的Activity:Translucent主题",//
                "普通的Activity:Holo_Light主题",//
                "有ListView的Activity", //
                "当touch位置有ViewPager,但ViewPager不是在item0时,不拦截滑动事件",//
                "当touch位置有ViewPager,即使ViewPager不是在item0时,也拦截【从屏幕边缘down】的滑动事件" };//我感觉这种方式的用户体验更好
        setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(array))));
    }
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        if (position <= 5) {
            Intent intent = new Intent(this, NormalActivity.class);
            intent.putExtra("position", position);
            startActivity(intent);
        } else if (position == 6) startActivity(new Intent(MainActivity.this, ListViewActivity.class));
        else {
            Intent intent = new Intent(this, ViewPagerActivity.class);
            intent.putExtra("position", position);
            startActivity(intent);
        }
        overridePendingTransition(R.anim.base_slide_right_in, R.anim.base_slide_remain);
    }

}

NormalActivity

public class NormalActivity extends SwipeBackActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //如果子类要requestWindowFeature,必须放在super.onCreate之前,因为父类SwipeBackActivity的onCreate方法已经获取到了DecorView
        //也即子类在调用super.onCreate后,其窗口装饰风格(即相应的根布局文件)已经确定好了(默认的),如:是否有标题、是否有icon等。
        //由于要求窗口装饰风格一经确定就不能再修改,否则直接抛异常!而requestWindowFeature的作用就是根据你的设置选择匹配的窗口装饰风格
        //所以requestWindowFeature必须在包括setContentView以及getWindow().getDecorView()等行为之前调用!
        //注意:以上规则只适合requestWindowFeature等方法,全屏设置可以放在【任何】位置,比如在点击某个View后调用也是可以的
        requestWindowFeature(Window.FEATURE_LEFT_ICON);//实际上这行代码没任何意义,因为会被super.onCreate中的设置覆盖掉
        super.onCreate(savedInstanceState);
        TextView tv_info = new TextView(this);
        tv_info.setTextColor(Color.BLACK);
        tv_info.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
        tv_info.setBackgroundColor(Color.RED);//必须设置背景色,否则是透明的
        tv_info.setGravity(Gravity.CENTER);
        switch (getIntent().getIntExtra("position", 0)) {
        case 0:
            tv_info.setText("默认显示指定的阴影\n对于普通的Activity,不必一定要在屏幕边缘开始滑才能退出");
            break;
        case 1:
            tv_info.setText("不显示阴影");
            rootLayout.setShowShadow(false);
            break;
        case 2:
            tv_info.setText("自定义阴影");
            rootLayout.setmShadowDrawable(getResources().getDrawable(R.drawable.ic_launcher));
            break;
        case 3:
            tv_info.setText("背景透明");
            tv_info.setBackgroundColor(Color.TRANSPARENT);
            break;
        case 4:
            tv_info.setText("Translucent主题");
            new AlertDialog.Builder(this).setTitle("Translucent主题").create().show();
            break;
        case 5:
            tv_info.setText("Holo_Light主题");
            setTheme(android.R.style.Theme_Holo_Light);
            new AlertDialog.Builder(this).setTitle("Holo_Light主题").create().show();
            break;
        }
        setContentView(tv_info);
    }

}

ListViewActivity

public class ListViewActivity extends SwipeBackActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        List<String> list = new ArrayList<String>();
        for (int i = 0; i <= 30; i++) {
            list.add("包含ListView时不会有手势冲突");
        }
        ListView mListView = new ListView(this);
        mListView.setBackgroundColor(Color.GREEN);//设置背景色
        mListView.setAdapter(new ArrayAdapter<String>(ListViewActivity.this, android.R.layout.simple_list_item_1, list));
        setContentView(mListView);
    }

}

ViewPagerActivity

public class ViewPagerActivity extends SwipeBackActivity {
    private List<View> list = new ArrayList<View>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ViewPager viewPager = new ViewPager(this);
        for (int i = 0; i < 5; i++) {
            TextView tv_info = new TextView(this);
            tv_info.setTextColor(Color.RED);
            tv_info.setTextSize(TypedValue.COMPLEX_UNIT_SP, 300);
            tv_info.setBackgroundColor(Color.GREEN);//必须设置背景色,否则是透明的
            tv_info.setGravity(Gravity.CENTER);
            tv_info.setText("" + i);
            list.add(tv_info);
        }
        viewPager.setAdapter(new Adapter(this, list));
        setContentView(viewPager);
        if (getIntent().getIntExtra("position", 0) == 8) rootLayout.setInterceptWhenTouchViewPagerIfNotFirst(true);
    }
    public class Adapter extends PagerAdapter {
        private List<View> list;
        public Adapter(Context context, List<View> list) {
            this.list = list;
        }
        @Override
        public int getCount() {
            return list.size();
        }
        @Override
        public boolean isViewFromObject(View arg0, Object arg1) {
            return arg0 == arg1;
        }
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView(list.get(position));
        }
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            View v = list.get(position);
            container.addView(v);
            return v;
        }
    }

}

清单文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.slidingfinish"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="17"
        android:targetSdkVersion="19" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.Translucent" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Holo.Light" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".ListViewActivity" />
        <activity android:name=".NormalActivity" />
        <activity android:name=".ViewPagerActivity" />
    </application>

</manifest>

附件列表

【右滑返回】滑动冲突 Scroller DecorView相关推荐

  1. 【转】iOS右滑返回手势全解和最佳实施方案

    序言 在ios7以后,苹果推出了手势滑动返回功能,也就是从屏幕左侧向右滑动可返回上一个界面.大大提高了APP在大屏手机和iPad上的操作体验,场景切换更加流畅.做右滑返回手势配置时,可能会遇到的 问题 ...

  2. 苹果侧边滑动返回_iOS系统右滑返回手势问题及解决方案

    在iOS7之后,苹果推出了手势滑动返回功能,也就是从屏幕左侧向右滑动可返回上一个界面.大大提高了APP在大屏手机和iPad上的操作体验,场景切换更加流畅. 常见的问题有: 1.右滑手势失效 2.右滑手 ...

  3. ios开发返回按钮消失_iOS开发之自定义导航栏返回按钮右滑返回手势失效的解决...

    我相信针对每一个iOS开发者来说~除了根视图控制器外~所有的界面通过导航栏push过去的界面都是可以通过右滑来返回上一个界面~其实~在很多应用和APP中~用户已经习惯了这个功能~然而~作为开发者的我们 ...

  4. 禁用导航栏的右滑返回实现全屏手势返回

    今天发现项目中push 的也面的右滑都无法pop 查阅相关资料发现 导航栏右滑手势失效基本有两种情况 1: self.navigationController.interactivePopGestur ...

  5. iOS 为自定义返回按钮的页面添加右滑返回

    2019独角兽企业重金招聘Python工程师标准>>> 苹果一直都在人机交互中尽力做到极致,在iOS7中,新增加了一个小小的功能,也就是这个api:self.navigationCo ...

  6. android右滑返回动画,Android仿微信右滑返回功能的实例代码

    先上效果图,如下: 先分析一下功能的主要技术点,右滑即手势判断,当滑到一直距离时才执行返回,并且手指按下的位置是在屏幕的最左边(这个也是有一定范围的),  这些可以实现onTouchEvent来实现. ...

  7. iOS 右滑返回失效问题终极解决方案

    iOS 的右滑返回是必不可少的一项功能,否则用户体验会大打折扣,但是会经常会碰到某些页面右滑返回失效的情况,下面记录一下解决各种情况下右滑返回失效的方法: 1. 自定义返回按钮 如果页面上是自定义的返 ...

  8. App开发流程之右滑返回手势功能续

    上一篇记录了利用系统私有变量和方法实现右滑返回手势功能:http://www.cnblogs.com/ALongWay/p/5893515.html 这篇继续记录另一种方案:利用UINavigatio ...

  9. ios7自定义返回按钮后,右滑返回功能失效解决方法

    ios7自定义返回按钮后,右滑返回功能失效解决方法 -(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; //开 ...

最新文章

  1. 用树莓派DIY车钥匙,开锁仅需90秒
  2. commons-lang3工具类学习(一)
  3. 方立勋_30天掌握JavaWeb_JSP
  4. VS2008下最新X264(svn 2009.0216)编译不过的解决办法(附编译通过+修改内存泄露版本)
  5. 大数据分析 es hive_使用Hive和iReport进行大数据分析
  6. 【HDU - 5881】Tea(思维,找规律)
  7. mvn 使用中的错误
  8. 好导师、选好题、保持创新; 然后坚定地执行: SCI论文是一切
  9. python学习笔记2018-9-18
  10. P1279 字串距离 (动态规划)
  11. 和nptf螺纹一样吗_常用数控加工计算公式和最全螺纹标准
  12. 小米air如何装linux双系统,小米笔记本安装Win10+Ubuntu16.04 LTS 双系统
  13. hdb3编码程序设计c语言,已知代码10000000001011,利用c语言程序编写AMI码跟HDB3码
  14. 认知升级:从首席架构师到CTO
  15. ROMS四维变分测试
  16. 全国电费余额查询API接口
  17. 网易MUMU模拟器怎么设置不卡?
  18. Benchmarking Learned Indexes(VLDB2021)
  19. MYSQL操作数据库-------查看、删除数据库
  20. 《金匮要略》试卷二(A)

热门文章

  1. jQuery之换肤与cookie插件
  2. 亲测能用的mysqli类,挺好用的
  3. Flex 布局 - 容器
  4. 检查本机显卡的cuda信息及适配cuda-sdk版本
  5. Javascript中的async await
  6. 计算二叉树叶子节点的数目
  7. 成员变量修饰词的作用
  8. PLSQL 执行 for update 语句被锁定的解决办法
  9. Laravel 5.0 - Middleware (中间件)
  10. 如何使用Photoshop制作真实的尺子