android 动态生成网页,一种无需留坑为页面动态添加View方案

在Activity或Fragment页面动态添加View,有其应用场景,比如配合运营在首页动态插入H5活动页(如下图手淘的雪花例示[1]),在页面头部插入通知View等。本文结合ActivityLifecycleCallbacks[2]及DecorView使用,为类似需求提供一种解决方案。

bVUdJN?w=600&h=1067

方案概述

本文方案监听Activity生命周期,在拿到指定Activity后,获取PhoneWindow.mContentParent,并在mContentParent中addView。

监听Activity生命周期

在android API 14+ ,Application.registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback)提供了一套回调方法,用于对Activity的生命周期事件进行集中处理。我们可用ActivityLifecycleCallbacks玩出很多花样,比如页面埋点,router预处理,限制Activity实例个数,swipeback等等。在本文它keep当前activity引用。

public interface ActivityLifecycleCallbacks {

void onActivityCreated(Activity activity, Bundle savedInstanceState);

void onActivityStarted(Activity activity);

void onActivityResumed(Activity activity);

void onActivityPaused(Activity activity);

void onActivityStopped(Activity activity);

void onActivitySaveInstanceState(Activity activity, Bundle outState);

void onActivityDestroyed(Activity activity);

}

PhoneWindow.mContentParent获取

Android的页面组成[3]如下图(注:在sdk 14+或在19+AppCompat,mContentParent不包含Actionbar,既标题栏[4]),我们关心如何获取mContentParent。

bVUdR2?w=465&h=749

在此简单例出mContentParent的相关代码

/**

*Activity.setContentView,此处的getWindow返回PhoneWindow,在

*activity.attach方法中实例化

**/

public void setContentView(@LayoutRes int layoutResID) {

getWindow().setContentView(layoutResID);

initWindowDecorActionBar();

}

/**

*PhoneWindow.setContentView

**/

public void setContentView(int layoutResID) {

...

if (mContentParent == null) {

mContentParent = generateLayout(mDecor);

}

...

}

/**

* mContentParent 从DecorView中ID为ID_ANDROID_CONTENT中赋值;ID_ANDROID_CONTENT在PhoneWindow中是一个int常量

* public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

**/

protected ViewGroup generateLayout(DecorView decor) {

...

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

if (contentParent == null) {

throw new RuntimeException("Window couldn't find content container view");

}

...

return contentParent;

}

/**

*Activity.findViewById

**/

public View findViewById(@IdRes int id) {

return getWindow().findViewById(id);

}

/**

*PhoneWindow.findViewById

**/

public View findViewById(@IdRes int id) {

return getDecorView().findViewById(id);

}

从上述代码我们可以发现,mContentParent可以通过activity.findViewById(android.R.id.content)获取。

方案实现

此方案两个关键:keep Activity实例和获取id为android.R.id.content的View。下面列出核心代码。

public class Background implements Application.ActivityLifecycleCallbacks {

/**

* 用于检测当前APP是否运行于前台

*/

private int appCount = 0;

private WeakReference mActivity;

@Override

public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

}

@Override

public void onActivityStarted(Activity activity) {

appCount++;

}

@Override

public void onActivityResumed(Activity activity) {

mActivity = new WeakReference<>(activity);

}

@Override

public void onActivityPaused(Activity activity) {

}

@Override

public void onActivityStopped(Activity activity) {

appCount--;

}

@Override

public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

}

@Override

public void onActivityDestroyed(Activity activity) {

}

/**

* 判断当前APP是否在后台

*

* @param context

* @return

*/

public boolean inBackRunning(final Context context) {

PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);

boolean isScreenOn = true;

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

isScreenOn = pm.isInteractive();

} else {

isScreenOn = pm.isScreenOn();

}

boolean isOnForeground = appCount > 0;

return !isScreenOn || !isOnForeground;

}

/**

* 获取当前页面的Fragment

*

* @return

*/

public Fragment getCurFragment() {

if (mActivity == null ||mActivity.get()==null|| !(mActivity.get() instanceof FragmentActivity)) {

return null;

}

FragmentManager fragManager = ((FragmentActivity) mActivity.get()).getSupportFragmentManager();

if (fragManager.getFragments() != null) {

List fragments = fragManager.getFragments();

for (Fragment fragment : fragments) {

if (fragment != null && fragment.isVisible())

return fragment;

}

return null;

}

return null;

}

/**

* 获取当前运行的Activity

*

* @return

*/

public Activity getCurActivity() {

return mActivity.get();

}

}

/**

* 页面顶部显示通知View实例 `[5]`

**/

public class MessageBar extends FrameLayout {

private Runnable mDismissRunnable = new Runnable() {

@Override

public void run() {

dismiss();

}

};

private AppMessage mAppMessage = new AppMessage.Builder().build();

public MessageBar(Context context) {

super(context);

}

public MessageBar(Context context, AttributeSet attrs) {

super(context, attrs);

}

public MessageBar(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

}

public static MessageBar with(Context context) {

return new MessageBar(context);

}

public MessageBar setAppMessage(AppMessage appMessage) {

if (appMessage != null) {

mAppMessage = appMessage;

}

return this;

}

public void dismiss() {

finish();

}

private void finish() {

clearAnimation();

ViewGroup parent = (ViewGroup) getParent();

if (parent != null) {

parent.removeView(this);

}

}

/**

* Displays the {@link MessageBar} at the bottom of the

* {@link Activity} provided.

*

* @param targetActivity

*/

public void show(Activity targetActivity) {

ViewGroup root = (ViewGroup) targetActivity.findViewById(android.R.id.content);

MarginLayoutParams params = init(targetActivity, root);

showInternal(targetActivity, params, root);

MediaUtil.getInstance(targetActivity).playSound(R.raw.im_notification, targetActivity);

VibratorUtil.vibrator(targetActivity);

}

private MarginLayoutParams init(Activity targetActivity, ViewGroup parent) {

FrameLayout layout = (FrameLayout) LayoutInflater.from(targetActivity)

.inflate(R.layout.mercury_template, this, true);

customUI(layout);

MarginLayoutParams params;

params = createMarginLayoutParams(

parent, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);

return params;

}

private void customUI(FrameLayout layout) {

//TODO init custom view

}

private static MarginLayoutParams createMarginLayoutParams(ViewGroup viewGroup, int width, int height) {

if (viewGroup instanceof FrameLayout) {

LayoutParams params = new LayoutParams(width, height);

params.gravity = TOP;

return params;

} else if (viewGroup instanceof RelativeLayout) {

RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);

params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);

return params;

} else if (viewGroup instanceof LinearLayout) {

LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width, height);

params.gravity = TOP;

return params;

} else {

throw new IllegalStateException("Requires FrameLayout or RelativeLayout for the parent of Snackbar");

}

}

private void showInternal(Activity targetActivity, MarginLayoutParams params, ViewGroup parent) {

parent.removeView(this);

// We need to make sure the MessageBar elevation is at least as high as

// any other child views, or it will be displayed underneath them

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

for (int i = 0; i < parent.getChildCount(); i++) {

View otherChild = parent.getChildAt(i);

float elvation = otherChild.getElevation();

if (elvation > getElevation()) {

setElevation(elvation);

}

}

}

parent.addView(this, params);

bringToFront();

// As requested in the documentation for bringToFront()

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {

parent.requestLayout();

parent.invalidate();

}

focusForAccessibility(this);

startTimer();

}

private void startTimer() {

postDelayed(mDismissRunnable, 3000);

}

private void focusForAccessibility(View view) {

final AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);

AccessibilityEventCompat.asRecord(event).setSource(view);

try {

view.sendAccessibilityEventUnchecked(event);

} catch (IllegalStateException e) {

// accessibility is off.

}

}

}

参考

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值