android home侧滑清除app干净,Android 仿iOS 侧滑关闭Activity框架透底问题解决

背景

问题描述

在项目中使用 SwipeBackLayout 或 SlidingMenu 侧滑关闭Activity框架时,由于windowIsTranslucent这个属性设置为了true,导致按home键退到桌面在返回App时会出现两个问题。

先显示上层的Activity,再显示当前交互的Activity。(感觉闪一下)

概率出现当前Activity整个页面为透明,屏幕显示的是上一个界面的Activity,但是当前Activity并没有销毁,并且可以交互

这个是比较严重的用户体验问题,特别在小米手机上会特别明显。

过程

问题猜想

之前就出现过首页透底显示桌面的情况,是因为Theme中windowIsTranslucent = true导致这个问题,通过修改windowIsTranslucent = false属性,彻底解决了首页透底问题。

实施

方案A: 修改所有Activity Theme windowIsTranslucent = true 属性

同样的配方同样的味道

替换所有所有Activity Theme

将window 改为不透明,背景颜色改为透明

false

@color/transparent

复制代码

运行后的效果图:

c1358720680f78e28f9773e6e6e8c0d9.png

闪烁透底的问题是解决了,但是侧滑框架出现了侧滑后看不到底部内容,方案A失败;

方案B:动态设置Activity Theme

在当前App退到后台时替换Activity为非透明主题,在Activity恢复到前台被点击时替换为透明主题;

如何动态修改Activity Theme?

@Override

protected void onCreate(Bundle savedInstanceState){

if (current_theme!= -1){

this.setTheme(current_theme);

}

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

findViewById(R.id.bt_theme).setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v){

changeTheme(GREEN_THEME);

}

});

}

public void changeTheme(int index){

switch (index) {

case DEFAULT_THEME:

current_theme = R.style.DefaultTheme;

break;

case GREEN_THEME:

current_theme = R.style.GreenTheme;

break;

case ORANGE_THEME:

current_theme = R.style.OrangeTheme;

break;

default:

break;

}

}

protected void reload(){

Intent intent = getIntent();

overridePendingTransition(0, 0);

intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);

finish();

overridePendingTransition(0, 0);

startActivity(intent);

}

复制代码

其实设置主题必须在任何view创建之前,所以我们不可能在activity的onCreate之后来更改主题,如果一定要做,就只能调用setTheme(),然后调用recreate(),重新创建一个activity,并且销毁上一个activity;

所以这个方案并不可行,整个界面必须销毁重建。

已知的Android theme修改方式

AndroidManifest 设置Activity Theme

在Activity setContentView执行前 调用setTheme

可以通过其他方式修改Activity windowIsTranslucent 属性吗?

方案B+:反射动态设置Activity windowIsTranslucent

查阅Activity源码,看一下他是如何变成透明的

/**

* Convert a translucent themed Activity {@link android.R.attr#windowIsTranslucent} back from

* opaque to translucent following a call to {@link #convertFromTranslucent()}.

*

* Calling this allows the Activity behind this one to be seen again. Once all such Activities

* have been redrawn {@link TranslucentConversionListener#onTranslucentConversionComplete} will

* be called indicating that it is safe to make this activity translucent again. Until

* {@link TranslucentConversionListener#onTranslucentConversionComplete} is called the image

* behind the frontmost Activity will be indeterminate.

*

* This call has no effect on non-translucent activities or on activities with the

* {@link android.R.attr#windowIsFloating} attribute.

*

* @param callback the method to call when all visible Activities behind this one have been

* drawn and it is safe to make this Activity translucent again.

* @param options activity options delivered to the activity below this one. The options

* are retrieved using {@link #getActivityOptions}.

* @return true if Window was opaque and will become translucent or

* false if window was translucent and no change needed to be made.

*

* @see #convertFromTranslucent()

* @see TranslucentConversionListener

*

* @hide

*/

@SystemApi

public boolean convertToTranslucent(TranslucentConversionListener callback,

ActivityOptions options){

boolean drawComplete;

try {

mTranslucentCallback = callback;

mChangeCanvasToTranslucent = ActivityManager.getService().convertToTranslucent(

mToken, options == null ? null : options.toBundle());

WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, false);

drawComplete = true;

} catch (RemoteException e) {

// Make callback return as though it timed out.

mChangeCanvasToTranslucent = false;

drawComplete = false;

}

if (!mChangeCanvasToTranslucent && mTranslucentCallback != null) {

// Window is already translucent.

mTranslucentCallback.onTranslucentConversionComplete(drawComplete);

}

return mChangeCanvasToTranslucent;

}

/**

* Convert a translucent themed Activity {@link android.R.attr#windowIsTranslucent} to a

* fullscreen opaque Activity.

*

* Call this whenever the background of a translucent Activity has changed to become opaque.

* Doing so will allow the {@link android.view.Surface} of the Activity behind to be released.

*

* This call has no effect on non-translucent activities or on activities with the

* {@link android.R.attr#windowIsFloating} attribute.

*

* @see #convertToTranslucent(android.app.Activity.TranslucentConversionListener,

* ActivityOptions)

* @see TranslucentConversionListener

*

* @hide

*/

@SystemApi

public void convertFromTranslucent(){

try {

mTranslucentCallback = null;

if (ActivityManager.getService().convertFromTranslucent(mToken)) {

WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, true);

}

} catch (RemoteException e) {

// pass

}

}

复制代码

可以看到这个两个Api就是将Activity转化为投透明和非透明通过 ActivityManager.getService() 和 WindowManagerGlobal.getInstance().changeCanvasOpacity()修改Window透明属性;

convertToTranslucent //将当前Activity Window 设置为透明

convertFromTranslucent //将当前 Activity Window 设置为非透明

由于是系统Api 并有 @hide 标注 正常是无法调用的,可以通过反射来调用;

反射调用如下:

/**

* Convert a translucent themed Activity

* 将Activity 改为透明

*/

public static void convertActivityToTranslucent(Activity activity){

long timeMillis = SystemClock.currentThreadTimeMillis();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

convertActivityToTranslucentAfterL(activity);

} else {

convertActivityToTranslucentBeforeL(activity);

}

FxLog.d("convertActivity : convertActivityToTranslucent time = " + (SystemClock.currentThreadTimeMillis() - timeMillis));

}

/**

* Calling the convertToTranslucent method on platforms before Android 5.0

*/

public static void convertActivityToTranslucentBeforeL(Activity activity){

try {

Class>[] classes = Activity.class.getDeclaredClasses();

Class> translucentConversionListenerClazz = null;

for (Class clazz : classes) {

if (clazz.getSimpleName().contains("TranslucentConversionListener")) {

translucentConversionListenerClazz = clazz;

}

}

Method method = Activity.class.getDeclaredMethod("convertToTranslucent",

translucentConversionListenerClazz);

method.setAccessible(true);

method.invoke(activity, new Object[] {

null

});

} catch (Throwable t) {

}

}

/**

* Calling the convertToTranslucent method on platforms after Android 5.0

*/

private static void convertActivityToTranslucentAfterL(Activity activity){

try {

Method getActivityOptions = Activity.class.getDeclaredMethod("getActivityOptions");

getActivityOptions.setAccessible(true);

Object options = getActivityOptions.invoke(activity);

Class>[] classes = Activity.class.getDeclaredClasses();

Class> translucentConversionListenerClazz = null;

for (Class clazz : classes) {

if (clazz.getSimpleName().contains("TranslucentConversionListener")) {

translucentConversionListenerClazz = clazz;

}

}

Method convertToTranslucent = Activity.class.getDeclaredMethod("convertToTranslucent",

translucentConversionListenerClazz, ActivityOptions.class);

convertToTranslucent.setAccessible(true);

convertToTranslucent.invoke(activity, null, options);

} catch (Throwable t) {

}

}

复制代码性能问题的思考

这样的反射是否对性能有损耗呢?在调用时做了耗时测试

在日志打印中可以看到性能完全不会受影响;6844904122169163784

为了进一步优化并减少反射调用,仅在用户触发侧滑、侧滑完全闭合时修改Activity透明属性

public void setWindowToTranslucent(boolean translucent){

if (isTranslucentWindow == translucent || !isSlidingEnabled()){

return;

}

isTranslucentWindow = translucent;

if (isTranslucentWindow) {

convertActivityToTranslucent(((Activity) getContext()));

} else {

convertActivityFromTranslucent(((Activity) getContext()));

}

}

复制代码稳定性问题的思考

由于是系统Api 在不同版本会略有不同,做了版本区分。并对反射Api做了try/catch保护,在反射Api调用异常的情况下,不会对App功能有影响。原Activity windowIsTranslucent 属性不变

总结

设置windowIsTranslucent =true 后,退后台再打开App时上层的Activity 会被再次绘制

Activity 替换主题的两种方式

AndroidManifest 设置Activity Theme

在Activity setContentView执行前 调用setTheme

Activity 源码分析

convertToTranslucent //将当前Activity Window 设置为透明

convertFromTranslucent //将当前 Activity Window 设置为非透明

反射调用

思考

1.在9.0后 @hide Api 通过反射是无法调用,后续是解决方案

2.除了修改windowIsTranslucent 还没有有其他的解决方案?

3.如何从根源思考、解决问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值