Android 如何动态添加 View 并显示在指定位置。Android DecorView 与 Activity 绑定原理分析

Android 如何动态添加 View 并显示在指定位置。Android DecorView 与 Activity 绑定原理分析,第1张

概述引子 最近,在做产品的需求的时候,遇到 PM 要求在某个按钮上添加一个新手引导动画,引导用户去点击。作为 RD,我哗啦啦的就写好相关逻辑了。自测完成后,提测,PM Review 效果。 看完后,PM 引子

最近,在做产品的需求的时候,遇到 PM 要求在某个按钮上添加一个新手引导动画,引导用户去点击。作为 RD,我哗啦啦的就写好相关逻辑了。自测完成后,提测,PM RevIEw 效果。

看完后,PM 提了个问题,这个动画效果范围能不能再大一点?PM 解释到按钮本身大小不是很大,会导致引导效果不够明显,也会导致用户的点击欲望不够。我想了想,似乎很有道理啊,但是这个能做到吗?

答案是当然可以呢。如果单纯从现在的布局上去将动画的尺寸去扩大,得改变原本的布局。这个引导只出现几次,为了引导,而去改动原有的布局,个人觉得改动还是蛮大的。不值得!

于是想用 clipChildren 属性来试着让 子 vIEw 突破父布局,但是这样同样会影响其他子 vIEw,也不好去与按钮的中心进行定位。

那还有没有其他尽可能不去改动原有布局就可以实现的方案呢?

有的!

准备知识

相信大家都对下面这段代码会很熟悉:

protected voID onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentVIEw(R.layout.activity_main);}

 这段代码执行后,将 activity_main 这个布局添加到了 DecorVIEw 。对于 activity 与 DecorVIEw 之间的关系,大家可以看这篇文章:Android DecorView 与 Activity 绑定原理分析

DecorVIEw 是一个应用窗口的根容器,它本质上是一个 FrameLayout。DecorVIEw 有唯一一个子 VIEw,它是一个垂直 linearLayout,包含两个子元素,一个是 TitleVIEw( Actionbar 的容器),另一个是 ContentVIEw(窗口内容的容器)也是一个 FrameLayout(androID.R.ID.content),平常用的 setContentVIEw 就是设置它的子 VIEw 。后面我们就是在 ContentVIEw 上做文章。

另外,对于 FrameLayout,他的子 vIEw 如果没有指定 Gravity 的话,那么就会堆积再左上角,谁是后面添加的谁在上面。其实使用也可以下面两个方法来决定放置的位置:

         public voID setX(float x) {        setTranslationX(x - mleft);    }    voID setY( y) {        setTranslationY(y - mtop);    }

 可以发现这两个方法其实是都通过设置平移的偏移的量来实现的。这样我们就可以指定 VIEw 所显示的位置的。

那如何去获取 PM 需求中所要求的位置呢?如果这个按钮是 wrap_content 的,按钮的宽度是无法确定的?那就只能拿到按钮对应的 VIEw 实例,通过该实例就可以获取到按钮的宽高。

获取 vIEw 的显示位置

按钮的宽高知道后,结合前面介绍的两个设置显示位置方法,有些人应该已经猜到要怎么做了。如果能够知道按钮的显示位置,这时候只要调用这两个方法,就可以将动画 vIEw 显示位置确定下来。那我要怎么去获取按钮的显示位置呢。下面就得介绍另一个方法呢。

    final boolean getLocalVisibleRect(Rect r) {        final Point offset = mAttachInfo != null ? mAttachInfo.mPoint : new Point();        if (getGlobalVisibleRect(r,offset)) {            r.offset(-offset.x,-offset.y); // make r local            return true;        }        false;    }

 在来看看 getGlobalVisibleRect 的实现,

    getGlobalVisibleRect(Rect r,Point globalOffset) {        int wIDth = mRight - mleft;        int height = mBottom - mtop;        if (wIDth > 0 && height > 0) {            r.set(0,0,wIDth,height);            if (globalOffset != null) {                globalOffset.set(-mScrollX,-mScrollY);            }            return mParent == null || mParent.getChildVisibleRect(this;    }

 

简单来说,就是 rect 是 VIEw 的宽高和 VIEw 的偏移量综合的结果,具体计算过程咱就不纠结了,下面说下每个数字代表的含义:

其中对于 getLocalVisibleRect 来说:

@H_301_161@

rect.left 大于0,表示左边已经处于不可见,否则是等于0;

rect.top 大于0,表示上边已经处于不可见,否则是等于0;

rect.right 小于 VIEw 的宽度,表是处于不可见,否则是等于 VIEw 的宽度;

rect.bottom 小于 VIEw 的高度,表是处于不可见,否则是等于 VIEw 的高度;

VIEw 的可见高度 = rect.bottom - rect.top;VIEw 的可见宽度 = rect.right - rect.left;

对于 getGlobalVisibleRect 来说:就是其在屏幕当中的位置。具体可见下面的 gif 图

相信大家在有了上述知识基础之后,就知道要怎么做了。下一步就是实战。

实践

目标:将一个 imageVIEw 居中显示在一个 TextVIEw 上面。

步骤:

获取锚点 TextVIEw 实例对象;

根据实例对象获取 ContentVIEw;

根据 ContentVIEw 和 TextVIEw 的显示位置确定 TextVIEw 在 ContentVIEw 中的位置;

将 imageVIEw 添加到 ContentVIEw 上,根据位置调整位置。

经过上面四步即可将一个 vIEw 添加到任何一个位置呢。

最终实现效果:

 

 源码

下面是具体实现代码,为了便于该逻辑的重复利用,我稍微进行了封装。采用的是 builder 模式,虽然我的变量比较少,但是真的当封装的功能足够强大的时候,需要用到属性就会很多,这时候就能体会到 builder 模式的强大呢。比如可以支持设置 Gravity,支持传入不同的 targetVIEw。现在我是直接 imageVIEw 写死的。

     onCreate(Bundle savedInstanceState) {        .onCreate(savedInstanceState);        setContentVIEw(R.layout.activity_main);               mText = findVIEwByID(R.ID.text);        mText.setClickable();        mText.setonClickListener( VIEw.OnClickListener() {            @OverrIDe             onClick(VIEw v) {                showCenterVIEw(mText);            }        });   }    showCenterVIEw(VIEw vIEw) {        floatingManager.Builder builder = floatingManager.getBuilder();        builder.setAnchorVIEw(vIEw);        floatingManager manager = builder.build();        manager.showCenterVIEw();    }

 下面是 采用的是 builder 模式简单封装的一个管理类:

class floatingManager {    private VIEw mAnchorVIEw;     String mTitle;     VIEwGroup mRootVIEw;    static Builder getBuilder() {         Builder();    }    static  Builder {         floatingManager mManager;        public floatingManager build() {            return mManager;        }         Builder() {            mManager =  floatingManager();        }         Builder setAnchorVIEw(VIEw vIEw) {            mManager.setAnchorVIEw(vIEw);            ;        }         Builder setTitle(String Title) {            mManager.setTitle(Title);            ;        }    }     setAnchorVIEw(VIEw vIEw) {        mAnchorVIEw = vIEw;    }     setTitle(String Title) {        this.mTitle = Title;    }     showCenterVIEw() {        if (mAnchorVIEw == ) {            ;        }        Activity activity = (Activity) mAnchorVIEw.getContext();        mRootVIEw = activity.findVIEwByID(androID.R.ID.content);        Rect anchorRect =  Rect();        Rect rootVIEwRect =  Rect();        mAnchorVIEw.getGlobalVisibleRect(anchorRect);        mRootVIEw.getGlobalVisibleRect(rootVIEwRect);         创建 imageVIEw        ImageVIEw imageVIEw =  ImageVIEw(activity);        imageVIEw.setimageDrawable(activity.getResources().getDrawable(R.drawable.ic_launcher));        mRootVIEw.addVIEw(imageVIEw);         调整显示区域大小        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) imageVIEw.getLayoutParams();        params.wIDth = 100;        params.height = 100;        imageVIEw.setLayoutParams(params);         设置居中显示        imageVIEw.setY(anchorRect.top - rootVIEwRect.top + (mAnchorVIEw.getHeight() - 100) / 2);        imageVIEw.setX(anchorRect.left + (mAnchorVIEw.getWIDth()  - 100) / 2);    }}

其实添加以后,还得考虑事件的点击之类的,比如可以通过设置回调,当点击引导动画的时候,先隐藏动画,再去主动促发按钮的点击逻辑等。

还有就是上面写的管理类存在重复添加 imageVIEw 的逻辑漏洞,应该在每次添加前都做一个检查,确保不会重复添加。

到这里,整个知识点就讲完了。 

总结

以上是内存溢出为你收集整理的Android 如何动态添加 View 并显示在指定位置。 Android DecorView 与 Activity 绑定原理分析全部内容,希望文章能够帮你解决Android 如何动态添加 View 并显示在指定位置。 Android DecorView 与 Activity 绑定原理分析所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1121326.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-29
下一篇 2022-05-29

发表评论

登录后才能评论

评论列表(0条)

保存