美文网首页android开源项目Android开发
Android 仿Ios 滑动返回上一目录

Android 仿Ios 滑动返回上一目录

作者: sirai | 来源:发表于2016-10-17 16:14 被阅读1785次
    IMG_0026.JPG SwipeBackLayout是一个在Android平台上实现了Activity滑动返回的库.

    实现了左,右,上,下四种手势返回的功能,在ios里滑动返回是系统自带可以配置的功能,而在我们Android上并没有系统级别的提供。

    主流应用比如微信,今日头条,就带有滑动返回功能,而且滑动返回是一个非常容易培养用户使用习惯的操作,用惯了滑动返回再用没有滑动返回的应用简直不能好好用了。

    SwipeBackLayout应该算是使用范围最广的滑动返回的库了 okay我们就来分析一下这个库是如何实现的:

    • build.gradle 文件添加类库
    compile 'me.imid.swipebacklayout.lib:library:1.0.0'
    
    • 设置需要滑动返回Activity的Theme style.xml里添加:
    <item name="android:windowIsTranslucent">true</item>
    
    • 将Activity继承SwipeBackActivity然后将我们的Activity继承SwipeBackActivity就集成完毕,我们的Activity就默认带有左划返回的功能了,
    ThreeActivity extends SwipeBackActivity 
    
    //是否允许滑动返回
    setSwipeBackEnable(false);
    //滑动并关闭activity
    scrollToFinishActivity();
    //获得SwipeBackLayout对象
    getSwipeBackLayout();
    
    • 当然我们super.onCreate(savedInstanceState);执行之后,调用下面的方法做一些自定义设置:
      mSwipeBackLayout.setEdgeTrackingEnabled(SwipeBackLayout.EDGE_RIGHT);//设定从哪个方向可以滑动
       mSwipeBackLayout.setEdgeSize(200);//来设置滑动触发的范围等等
      mSwipeBackLayout.setScrimColor(R.color.colorAccent);//来设置滑动返回的背景色
    
    • MainActivity 全码 如果你单单的只是像我一样学习用法 那看到这 基本就可以ok了 下面的 都是一些源码的分析 那些我也只是作为了解
    public class MainActivity extends SwipeBackActivity {
        private SwipeBackLayout mSwipeBackLayout;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mSwipeBackLayout=getSwipeBackLayout();
            mSwipeBackLayout.setEdgeTrackingEnabled(SwipeBackLayout.EDGE_RIGHT);//设定从哪个方向可以滑动
            mSwipeBackLayout.setEdgeSize(200);//来设置滑动触发的范围等等
    
            Button button=(Button)findViewById(R.id.button);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    startActivity(new Intent(MainActivity.this,FirstActivity.class));
                }
            });
    
            mSwipeBackLayout.addSwipeListener(new SwipeBackLayout.SwipeListener() {
                @Override
                public void onScrollStateChange(int state, float scrollPercent) {
    
                }
    
                @Override
                public void onEdgeTouch(int edgeFlag) {
                    vibrate(20);
                }
    
                @Override
                public void onScrollOverThreshold() {
                    vibrate(20);
                }
            });
        }
    
    
        /**
         *  //手机的振动器,是通过this.getSystemService(Service.VIBRATOR_SERVICE)获取的,然后通过用vibrate来
         *      实现震动,
         * @param duration
         */
        private void vibrate(long duration) {
    
            Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
            long[] pattern = {
                    0, duration
            };
            vibrator.vibrate(pattern, -1);
        }
    }
    
    

    类关系图

    SwipeBackLayout的类关系图非常的清晰简单,SwipeBackActivity继承自Activity并且实现了SwipeBackActivityBase接口,SwipeBackActivity,SwipeBackActivityHelper和SwipeBackLayout相互引用,类图上来看还是比较简单的,下面我们来看具体实现:

    源码分析

    一句话概括SwipeBackLayout的实现原理就是:通过在DecorView和其包含的子View之间添加一个ViewGroup也就是SwipeBackLayout,通过在SwipeBackLayout里处理触摸事件与位移来实现滑动返回的效果。

    • SwipeBackActivityBase的实现
      我们以前说过阅读一个框架的时候,先从它定义的一些接口开始,如果是小项目其实也没有太多的规定,因为代码量本身就不多,所以也可以整体来看,不过我们还是先来看看SwipeBackActivityBase
      接口是怎么定义的:
    public interface SwipeBackActivityBase {
    
        //得到SwipeBackLayout对象
        public abstract SwipeBackLayout getSwipeBackLayout();
    
        //设置是否可以滑动返回
        public abstract void setSwipeBackEnable(boolean enable);
    
        //自动滑动返回并关闭Activity
        public abstract void scrollToFinishActivity();
    }
    
    • SwipeBackActivityBase的实现
      我们以前说过阅读一个框架的时候,先从它定义的一些接口开始,如果是小项目其实也没有太多的规定,因为代码量本身就不多,所以也可以整体来看,不过我们还是先来看看SwipeBackActivityBase接口是怎么定义的:
    public class SwipeBackActivity extends AppCompatActivity implements SwipeBackActivityBase {
        private SwipeBackActivityHelper mHelper;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //初始化mHelper
            mHelper = new SwipeBackActivityHelper(this);
            mHelper.onActivityCreate();
        }
    
        @Override
        protected void onPostCreate(Bundle savedInstanceState) {
            super.onPostCreate(savedInstanceState);
            mHelper.onPostCreate();
        }
    
        @Override
        public SwipeBackLayout getSwipeBackLayout() {
            return mHelper.getSwipeBackLayout();
        }
    
        @Override
        public void setSwipeBackEnable(boolean enable) {
            getSwipeBackLayout().setEnableGesture(enable);
        }
    
        @Override
        public void scrollToFinishActivity() {
            Utils.convertActivityToTranslucent(this);
            getSwipeBackLayout().scrollToFinishActivity();
        }
    }
    

    可以看到在onCreate()里创建了一个SwipeBackActivityHelper对象,而在getSwipeBackLayout()方法里通过mHelper得到了对应的SwipeBackLayout对象,所以mHelper应该是负责创建SwipeBackLayout并将SwipeBackLayout添加到Activity里的。
    在mHelper的onActivityCreate()方法里创建了SwipeBackLayout对象,并在onPostCreate()方法里将SwipeBackLayout添加到Activity里,逻辑很简单,这里就不贴代码了,最终是调用了SwipeBackLayout的attachToActivity()方法:

     public void attachToActivity(Activity activity) {
            //获得activity对象
            mActivity = activity;
            TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{
                    android.R.attr.windowBackground
            });
            //得到窗口背景
            int background = a.getResourceId(0, 0);
            a.recycle();
            //得到DecorView对象,并先将decorChild移除并添加到
            //SwipeBackLayout里,再添加进DecorView
            ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
            ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
            decorChild.setBackgroundResource(background);
            decor.removeView(decorChild);
            addView(decorChild);
            setContentView(decorChild);
            decor.addView(this);
        }
    

    这就是整个添加过程,至此我们的Activity里就包含了SwipeBackLayout了,下面我们就来看看SwipeBackLayout的具体实现。

    • SwipeBackLayout的实现
      public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs);
            mDragHelper = ViewDragHelper.create(this, new ViewDragCallback());
    
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeBackLayout, defStyle,
                    R.style.SwipeBackLayout);
    
            int edgeSize = a.getDimensionPixelSize(R.styleable.SwipeBackLayout_edge_size, -1);
            ...
            a.recycle();
            final float density = getResources().getDisplayMetrics().density;
            final float minVel = MIN_FLING_VELOCITY * density;
            mDragHelper.setMinVelocity(minVel);
            mDragHelper.setMaxVelocity(minVel * 2f);
        }
    
     @Override
        public boolean onInterceptTouchEvent(MotionEvent event) {
            if (!mEnable) {
                return false;
            }
            try {
                return mDragHelper.shouldInterceptTouchEvent(event);
            } catch (ArrayIndexOutOfBoundsException e) {
                // FIXME: handle exception
                // issues #9
                return false;
            }
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (!mEnable) {
                return false;
            }
            mDragHelper.processTouchEvent(event);
            return true;
        }
    

    可以看到是完全委托ViewDragHelper来处理,我们可以看到SwipeBackLayout中的ViewDragHelper并不是直接引用的support包中的ViewDragHelper而是将代码拷贝出来,是因为需要添加一些support中ViewDragHelper并不存在的方法,例如mDragHelper.setEdgeSize(size);,mDragHelper.setMaxVelocity(minVel * 2f);

    
    @Override
        protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
            Log.d(TAG, "drawChild");
            final boolean drawContent = child == mContentView;
    
            boolean ret = super.drawChild(canvas, child, drawingTime);
            if (mScrimOpacity > 0 && drawContent
                    && mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) {
                //绘制边缘垂直阴影
                drawShadow(canvas, child);
                //绘制背景遮罩
                drawScrim(canvas, child);
            }
            return ret;
        }
    
    

    可以看到阴影和遮罩就是在这个方法里绘制的了

    存在的问题

    1.部分Android版本不兼容:
    在实际使用的时候发现当一个Activity栈内的所有Activity的Theme里都添加了<item name="android:windowIsTranslucent">true</item>参数时,在某些Android 4.4.x的手机上或者4.4.x的MIUI上,滑动返回的时候不会看到前面一个Activity而会把桌面显示出来,这应该是这些系统的bug.所以为了避免这些问题,我们需要在最底层的Activity的Theme里设置<item name="android:windowIsTranslucent">false</item>来避免这个问题,通常都是我们的MainActivity而且一般这个Activity我们并不需要左划返回的功能,所以这个问题也算是找到了解决办法。

    2.性能问题:
    由于被设置了<item name="android:windowIsTranslucent">true</item>的Activity无法进入onStop()生命周期,所以导致Activity的Window无法回收,所以在多个Activity叠加时会出现明显的卡顿现象,目前并没有特别好的解决办法。

    相关文章

      网友评论

      • KeyboardLife:用这个侧滑框架,报错 Binary XML file line #0: Error inflating class me.imid.swipebacklayout.lib.SwipeBackLayout,你遇到过吗,跳转这个界面就崩溃
      • 孤独行者ll:有一个很蛋疼的bug,不知道能不能解决,就是有edittext输入框的界面,弹起软键盘时的短暂间隙,会看到上一个activity界面一闪的情况. 由于根布局是透明的,所以能看到上一个界面.
        sirai:@孤独行者ll 我现在已经不用了左滑关闭了 确实有你说的这个问题 还有其他的问题
      • 夜客小虾_8cc8:这个确实不错,但是有一个无法忍受的bug让我不得不弃用,那就是它无法改变状态栏颜色,沉浸式布局做不了
      • sirai:@dongjunkun 哦? 没有问题是最好 部分的会出现卡顿吧
      • dongjunkun:https://github.com/r0adkll/Slidr 这个用了蛮久,还没有你所说的明显卡顿问题
      • SaiWu:之前也尝试用这个,但最后还是放弃了滑动返回。如你所说的兼容问题,性能问题,注定无法走的长远。
        sirai:@SaiWu 我现在也只是 小型app 上尝试使用:blush:

      本文标题:Android 仿Ios 滑动返回上一目录

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