资讯专栏INFORMATION COLUMN

一种无需留坑为页面动态添加View方案

Alfred / 3312人阅读

摘要:在或页面动态添加,有其应用场景,比如配合运营在首页动态插入活动页如下图手淘的雪花例示在页面头部插入通知等。本文结合及使用,为类似需求提供一种解决方案。方案概述本文方案监听生命周期,在拿到指定后,获取,并在中。

在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 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] 利用动态加载实现手机淘宝的节日特效: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...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/13901.html

相关文章

  • 基于weex的有赞无线开发框架

    摘要:出于对开发效率和动态化的要求,无线端的开发框架也一直在更新,从结构化,再到现在正在大受关注的。最近,有赞移动选择了作为无线开发框架,搭建了从开发构建发布数据一个闭环的流程。年月日,阿里巴巴宣布将移动开源项目捐赠给基金会开始孵化。 出于对开发效率和动态化的要求,无线端的开发框架也一直在更新,从 Hybrid、结构化 Native View、React Native、Weex,再到现在正在...

    cloud 评论0 收藏0
  • 基于weex的有赞无线开发框架

    摘要:出于对开发效率和动态化的要求,无线端的开发框架也一直在更新,从结构化,再到现在正在大受关注的。最近,有赞移动选择了作为无线开发框架,搭建了从开发构建发布数据一个闭环的流程。年月日,阿里巴巴宣布将移动开源项目捐赠给基金会开始孵化。 出于对开发效率和动态化的要求,无线端的开发框架也一直在更新,从 Hybrid、结构化 Native View、React Native、Weex,再到现在正在...

    suosuopuo 评论0 收藏0
  • 前端的发展历程

    摘要:前端的发展历程什么是前端前端针对浏览器的开发,代码在浏览器运行后端针对服务器的开发,代码在服务器运行前端三剑客超文本标记语言是构成世界的基石。 前端的发展历程 什么是前端 前端:针对浏览器的开发,代码在浏览器运行 后端:针对服务器的开发,代码在服务器运行 前端三剑客 HTML CSS JavaScript HTML HTML(超文本标记语言——HyperText Markup ...

    刘明 评论0 收藏0
  • 有关Android插件化思考

    摘要:第五点更重要,做插件化需要控制两个地方。因此不符合插件化的需求,不作考虑。支持加载外部的或者文件,正好符合文件化的需求,所有的插件化方案都是使用来加载插件中的文件的。方案简单,适用于自身少量代码的插件化改造。年月是手机助手实现的一种插件化 最近几年移动开发业界兴起了「 插件化技术 」的旋风,各个大厂都推出了自己的插件化框架,各种开源框架都评价自身功能优越性,令人目不暇接。随着公司业务快...

    shmily 评论0 收藏0
  • 从头开始学习vue-router

    摘要:路由模块的本质就是建立起和页面之间的映射关系。这时候我们可以直接利用传值了使用来匹配路由,然后通过来传递参数跳转对应路由配置于是我们可以获取参数六配置子路由二级路由实际生活中的应用界面,通常由多层嵌套的组件组合而成。 一、前言 要学习vue-router就要先知道这里的路由是什么?为什么我们不能像原来一样直接用标签编写链接哪?vue-router如何使用?常见路由操作有哪些?等等这些问...

    tommego 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<