Android 如何动态添加 View 并显示在指定位置。
引子
最近,在做产品的需求的时候,遇到 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);
}
public void setY(float y) {
setTranslationY(y - mTop);
}
可以发现这两个方法其实是都通过设置平移的偏移的量来实现的。这样我们就可以指定 View 所显示的位置的。
那如何去获取 PM 需求中所要求的位置呢?如果这个按钮是 wrap_content 的,按钮的宽度是无法确定的?那就只能拿到按钮对应的 View 实例,通过该实例就可以获取到按钮的宽高。
获取 view 的显示位置
按钮的宽高知道后,结合前面介绍的两个设置显示位置方法,有些人应该已经猜到要怎么做了。如果能够知道按钮的显示位置,这时候只要调用这两个方法,就可以将动画 view 显示位置确定下来。那我要怎么去获取按钮的显示位置呢。下面就得介绍另一个方法呢。
public 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;
}
return false;
}
在来看看 getGlobalVisibleRect 的实现,
public boolean 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, r, globalOffset);
}
return false;
}
简单来说,就是 rect 是 View 的宽高和 View 的偏移量综合的结果,具体计算过程咱就不纠结了,下面说下每个数字代表的含义:
其中对于 getLocalVisibleRect 来说:
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 写死的。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mText = findViewById(R.id.text);
mText.setClickable(true);
mText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showCenterView(mText);
}
});
}
public void showCenterView(View view) {
FloatingManager.Builder builder = FloatingManager.getBuilder();
builder.setAnchorView(view);
FloatingManager manager = builder.build();
manager.showCenterView();
}
下面是 采用的是 builder 模式简单封装的一个管理类:
public class FloatingManager {
private View mAnchorView;
private String mTitle;
private ViewGroup mRootView;
public static Builder getBuilder() {
return new Builder();
}
static class Builder {
private FloatingManager mManager;
public FloatingManager build() {
return mManager;
}
public Builder() {
mManager = new FloatingManager();
}
public Builder setAnchorView(View view) {
mManager.setAnchorView(view);
return this;
}
public Builder setTitle(String title) {
mManager.setTitle(title);
return this;
}
}
public void setAnchorView(View view) {
mAnchorView = view;
}
public void setTitle(String title) {
this.mTitle = title;
}
public void showCenterView() {
if (mAnchorView == null) {
return;
}
Activity activity = (Activity) mAnchorView.getContext();
mRootView = activity.findViewById(android.R.id.content);
Rect anchorRect = new Rect();
Rect rootViewRect = new Rect();
mAnchorView.getGlobalVisibleRect(anchorRect);
mRootView.getGlobalVisibleRect(rootViewRect);
// 创建 imageView
ImageView imageView = new 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 并显示在指定位置。的更多相关文章
- Mono for android 如何动态添加View,线程内部如何更新UI.
貌似所有设计到UI的程序原理都是一样的,子线程是不能够更新UI状态的,所以就必须使用UI自身或者第三方来更新UI. 如 在WinForm 中 就可以使用Control.Invoke(Action ac ...
- 动态添加布局、动态添加View、LinearLayout动态添加View;
LinearLayout提供了几个方法,用作动态添加View特别好用: 可以添加View.删除View.删除指定位置View.删除全部View: 看代码: public class MainActiv ...
- 微信小程序之一:动态添加view(view包含picker,input)
<view wx:for="{{array}}" wx:key="this" class="borderContainer"> ...
- Android 在程序中动态添加 View 布局或控件
有时我们需要在程序中动态添加布局或控件等,下面用程序来展示一下相应的方法: 1.addView 添加View到布局容器 2.removeView 在布局容器中删掉已有的View 3.LayoutPar ...
- 往Layout中动态添加View
需要注意几个方法:基本上所有的方法参数单位是px 1.设置View的宽高: LinearLayout.LayoutParams params = new LinearLayout().LayoutPa ...
- vue-element-admin登录逻辑,以及动态添加路由,显示侧边栏
这段时间在研究element-admin,感觉这个库有许多值得学习的地方,我学习这个库的方法是,先看它的路由,顺着路由,摸清它的逻辑,有点像顺藤摸瓜. 这个库分的模块非常清晰,适合多人合作开发项目,但 ...
- Android Fragment动态添加 FragmentTransaction FragmentManager
Fragment常用的三个类:android.app.Fragment 主要用于定义Fragmentandroid.app.FragmentManager 主要用于在Activity中操作Fragme ...
- GridView动态添加View
activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout ...
- BootStrap-select插件动态添加option无法显示
问题描述: 在使用bootstrap-select插件时出现下拉框无法显示动态追加的option,经过查看element元素发现,select标签已经append进去了所需的option选项,但是页面 ...
随机推荐
- javascript严格模式的影响
针对js严格模式会报错的编码操作进行了整理,避免不严谨的代码习惯,面向未来编程(es6+) 变量 1.定义八进制格式的数字 2.不带var的变量定义--包括函数体内的 3.对变量的delete操作-- ...
- React Ref 和 React forwardRef
Ref 和Dom,Ref是reference(引用)的简写. 能力:大多数情况下,props前递可以解决一切问题,但是依然有需要触达React实例或者Dom节点的情况,这时候应该使用React Ref ...
- TCP连接的关闭
原文地址:http://lib.csdn.net/article/computernetworks/17264 TCP连接的关闭有两个方法close和shutdown,这篇文章将尽量精简的说明它们 ...
- 【网络安全】HTTPS为什么比较安全
目录 HTTP和HTTPS简介 SSL协议 SSL协议的主要功能 SSL协议加密数据的原理 用户和服务器的认证流程 TLS 参考 HTTP和HTTPS简介 1. HTTP协议为什么是不安全的 http ...
- ES6对象简洁语法
对象(object)是 JavaScript 最重要的数据结构.ES6 对它进行了重大升级,本章介绍数据结构本身的改变及语法应用细节. 1.属性的简洁表示法 ◆ ES6 允许直接写入变量和函数,作为对 ...
- 学习笔记66_DBSCAN聚类算法
- 千与千寻主题曲beep函数版
在出代码之前,我们向来了解一下Beep函数. 例: Beep(,); 这个表示575Hz响100ms. 下面给出代码: #include <bits/stdc++.h> #include ...
- day 1 堆 hash 线段树 树状数组 冰茶姬 字典树 二叉查找树
来郑州的第二天,早上开始也没说什么就说了些注意安全,各种各样的注意安全... 冰茶姬: 原来再打食物链时看了一下冰茶姬,只注意了路径压缩,没想到还有什么按秩排序但确实快了不少... int find( ...
- EFK教程 - ElasticSearch高性能高可用架构
通过将elasticsearch的data.ingest.master角色进行分离,搭建起高性能+高可用的ES架构 作者:"发颠的小狼",欢迎转载与投稿 目录 ▪ 用途 ▪ 架构 ...
- P中值选址问题的整数规划求解
P中值选址问题的整数规划求解 一 .P-中值问题 p-中值选址问题是一个常见的选址问题. 问题是给定I个需求结点和J个待选设施地点, 要求选择p个地点建立设施, 使得运输成本最低. 下面是个英文的问题 ...