博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一种无需留坑为页面动态添加View方案
阅读量:6979 次
发布时间:2019-06-27

本文共 9851 字,大约阅读时间需要 32 分钟。

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

clipboard.png

方案概述

本文方案监听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。

clipboard.png

在此简单例出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. } } }

参考

[1] 利用动态加载实现手机淘宝的节日特效:

[2] ActivityLifecycleCallbacks:
[3] setContentView 背后那些事儿:
[4] android.R.id.content指什么以及一个实例:
[5] snackbar:

转载地址:http://uejpl.baihongyu.com/

你可能感兴趣的文章
CSS - 修改input - placeholder 和 readonly 的样式
查看>>
常用UI布局
查看>>
在多线程情况下 局部变量与全局变量 哪个比较安全呢
查看>>
算法评测
查看>>
40款非常酷的国外创意名片设计欣赏
查看>>
RadioGroup单选按钮用法
查看>>
POJ 2773 Happy 2006
查看>>
UBIFS介绍 - MTD网站
查看>>
如何使用ITEXTSHARP将HTML代码字符串写进PDF
查看>>
Oracle SQL CPU占用高
查看>>
mongodb简介与增删该查
查看>>
Maya 2015 中英文切换
查看>>
C语言的字符串分割
查看>>
Arduino可穿戴开发入门教程Windows平台下安装Arduino IDE
查看>>
BpBinder 转换为 BpCameraService 流程
查看>>
李洪强经典面试题150-设计模式
查看>>
使用maven的profile切换项目各环境的参数
查看>>
XML基础知识
查看>>
如何用C#写一个简单的Login窗口
查看>>
Sharepoint学习笔记—习题系列--70-576习题解析 -(Q141-Q143)
查看>>