android开源项目Android开发

Android 仿Ios 滑动返回上一目录

2016-10-17  本文已影响1785人  sirai
IMG_0026.JPG SwipeBackLayout是一个在Android平台上实现了Activity滑动返回的库.

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

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

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

compile 'me.imid.swipebacklayout.lib:library:1.0.0'
<item name="android:windowIsTranslucent">true</item>
ThreeActivity extends SwipeBackActivity 

//是否允许滑动返回
setSwipeBackEnable(false);
//滑动并关闭activity
scrollToFinishActivity();
//获得SwipeBackLayout对象
getSwipeBackLayout();
  mSwipeBackLayout.setEdgeTrackingEnabled(SwipeBackLayout.EDGE_RIGHT);//设定从哪个方向可以滑动
   mSwipeBackLayout.setEdgeSize(200);//来设置滑动触发的范围等等
  mSwipeBackLayout.setScrimColor(R.color.colorAccent);//来设置滑动返回的背景色
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里处理触摸事件与位移来实现滑动返回的效果。

public interface SwipeBackActivityBase {

    //得到SwipeBackLayout对象
    public abstract SwipeBackLayout getSwipeBackLayout();

    //设置是否可以滑动返回
    public abstract void setSwipeBackEnable(boolean enable);

    //自动滑动返回并关闭Activity
    public abstract void scrollToFinishActivity();
}
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的具体实现。

  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叠加时会出现明显的卡顿现象,目前并没有特别好的解决办法。

上一篇 下一篇

猜你喜欢

热点阅读