概述
在Activity或Fragment页面动态添加View,有其应用场景,比如配合运营在首页动态插入H5活动页(如下图手淘的雪花例示[1]
),在页面头部插入通知View等。本文结合ActivityLifecycleCallbacks[2]
及DecorView使用,为类似需求提供一种解决方案。
方案概述
本文方案监听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。
在此简单例出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<Activity> 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<Fragment> 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.
}
}
}
参考
[1] 利用动态加载实现手机淘宝的节日特效:http://www.jianshu.com/p/195e...
[2] ActivityLifecycleCallbacks: https://developer.android.com...
[3] setContentView 背后那些事儿:http://www.jianshu.com/p/0219...
[4] android.R.id.content指什么以及一个实例:http://lingnanlu.github.io/20...
[5] snackbar:https://github.com/nispok/sna...
最后
以上就是纯真飞机为你收集整理的 一种无需留坑为页面动态添加View方案的全部内容,希望文章能够帮你解决 一种无需留坑为页面动态添加View方案所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复