Android 通用页面滑动退出库,集成简单只要一行代码

在Android应用中很多应用都已经由集成滑动退出的效果,比如QQ,比如UC,又比如微信。QQ的滑动退出仅仅监听手势,没有任何动画效果,在手势触发成功后结束当前的页面。UC效果会好一些,会有根据手势慢慢移除的效果,但之前页面没有联动效果,这一点微信做的相对而言好些。

微信的滑动退出,在当前页面滑动,之前的页面也会有个跟随的效果,想到的方案有在当前页面保持之前页面的引用,滑动过程中一起做联动效果,这种实现方案,我尝试了下,在Android4.4及以上的手机下可以实现,但是之前的版本却无法产生效果,主要原因是,之前版本在调用OnPause后就不在继续接受刷新请求,这是一种效率上的保护。

而且在高API手机上,性能表现也不是很好,因为同一帧要刷新两个界面,系统负担较重。

目前的实现方案是,在当前页面启动时候,取上一个页面的快照,这样在滑动退出的时候,可以以快照为背景,达到联动的效果。标准截图API如下

public Bitmap myShot(Activity activity) {
// 获取windows中最顶层的view
View view = activity.getWindow().getDecorView();
view.buildDrawingCache();


// 获取状态栏高度
Rect rect = new Rect();
view.getWindowVisibleDisplayFrame(rect);
int statusBarHeights = rect.top;
Display display = activity.getWindowManager().getDefaultDisplay();


// 获取屏幕宽和高
int widths = display.getWidth();
int heights = display.getHeight();


// 允许当前窗口保存缓存信息
view.setDrawingCacheEnabled(true);


// 去掉状态栏
Bitmap bmp = Bitmap.createBitmap(view.getDrawingCache(), 0,
statusBarHeights, widths, heights - statusBarHeights);


// 销毁缓存信息
view.destroyDrawingCache();


return bmp;
}



不过这种方式导致,在截图时会阻塞主线程,造成卡顿。我大概测试了下每个快照在100到300毫秒间,大概会阻塞10到20帧的样子。这会带来很不好的用户体验。

那主线程不行,子线程截图呢?这也是之后采用的方式,不过后来发现了问题,如果之前页面是有动画,便会造成线程冲突,使当前View被废弃,不再能接收到事件。只能在页面失去焦点的时候,关闭动画,不过这也不是一个很好的方法,会使得框架使用起来太过于麻烦。

只能想办法让快照的时间缩短,分析view.getDrawingCache()代码,发现每一次截图会重新创建bitmap对象,创建Canvas,手动调用draw方法获得截图,而创建bitmap对象是相当费性能的。复用bitmap一定是个不错的注意,因为全局我只需要一张图片,这种方式保证了效率,也保证了内存的稳定。


 

Canvas canvas = new CacheCanvas(bitmap);
        canvas.clipRect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        canvas.drawColor(Color.WHITE);
        cacheBitmap.clear();
        cacheBitmap.put(layout.hashCode(), bitmap);
        layout.dispatchDraw(canvas);

   最后大概每次快照会花费20毫秒左右,这个时间用户一般感觉不出来了。解决了功能上的实现,要考虑让集成更简单,在每次Activity设置视图的时候开启?太麻烦了,而且以后相关也太费事。不过我们可以定义一个Activity的基类,在基类里面做,还是太麻烦。

大概说下,视图是如何绘制到窗口的,以及窗口如何管理View;


   public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }



Activity的setContentView最终是调用Window的setContentView,而Android中的具体实现类是PhoneWindow


 

@Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }


        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

这部分代码比较长,有兴趣的可以自己看下,我大概来讲解下,如果窗口的dectorView为空,会创建一个  installDecor(),里面会根据主题创建不同的ContentParent ,逻辑简单,代码比较长就不贴了。这一块主要是为了说明,每一个Window其实都会有一个DectorView,这是根View。如果我们可以拿到根View就可以达到我们想要的目的了。

幸运的是,我们的根视图都是由WindowManagerGlobal统一管理的,创建的DectorView会保存在 private final ArrayList mViews = new ArrayList(); 这个变量中。那这么可以监听到ArrayList的改变呢?

 class HoldArrayList extends ArrayList {


    public interface OnRootViewChange {
        void onChange(List rootViews);
    }


    private OnRootViewChange onRootViewChange;
    private List activityRootViews;


    public HoldArrayList(OnRootViewChange onRootViewChange) {
        this.onRootViewChange = onRootViewChange;
        this.activityRootViews = new ArrayList<>();
    }


    @Override
    public boolean add(View t) {
        boolean add = super.add(t);
        onChange();
        return add;
    }


    @Override
    public boolean remove(Object o) {
        boolean remove = super.remove(o);
        onChange();
        return remove;
    }


    @Override
    public View remove(int index) {
        View remove = super.remove(index);
        onChange();
        return remove;
    }


    private void onChange() {
        activityRootViews.clear();
        for (View view : this) {
            if (view.getContext() instanceof Activity) {
                activityRootViews.add(view);
            }
        }
        if (onRootViewChange != null) {
            onRootViewChange.onChange(activityRootViews);
        }
    }
}


这是我创建的holdArrayList替换系统的ArrayList,便可以监听到View的添加和移除。


github地址:https://github.com/long8313002/FastSlidingExit


你可能感兴趣的:(Android 通用页面滑动退出库,集成简单只要一行代码)