hi,在项目开发中,有时候需要仿照ios的底部弹框做效果,比如我们在iPhone上面关闭定位的时候,就会弹出ios特有的底部弹框:

屏幕快照 2017-10-09 08.20.30 PM.png

弹框布局:

我们可以来看下这个弹框有哪些显示:

标题(一个标题)

选项(N个选项,此处图片只有关闭这一个选项)

底部一个取消按钮(一个取消按钮)

所以我们先考虑这个弹框的布局就需要:

因为中间的菜单是一个列表,所以根据这个图我们可以想到我们所要写的弹框的布局大致为:

我们已经规划好了弹框的布局,现在我们要开始实现弹框了。

实现弹框:

因为后来谷歌推荐使用的是DialogFragment,所以我们此处弹框也是使用DialogFragment。

我们一步步来看如何使用DialogFragment来实现我们想要的弹框:

我们按照上面的布局写了具体的弹框布局代码

fragment_ios_dialog.xml:

xmlns:tool="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical"

android:background="@android:color/transparent"

>

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:background="@drawable/circle_bg"

android:orientation="vertical">

android:id="@+id/tv_title"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:gravity="center"

android:padding="20dp"

android:text="标题内容" />

android:layout_width="match_parent"

android:layout_height="1dp"

android:background="#bbbbbb" />

android:id="@+id/lv_menu"

android:scrollbars="none"

android:layout_width="match_parent"

android:layout_height="200dp" />

android:id="@+id/btn_cancle"

android:layout_width="match_parent"

android:layout_height="50dp"

android:layout_marginTop="20dp"

android:background="@drawable/circle_bg"

android:text="取消"

/>

在这里,我们先假设中间的菜单ListView的高度写成50dp,主要是先来看效果,实际使用的时候可以写成wrap_content,根据传入的item数量决定高度。

再继承DialogFragment来实现我们的IOSDialogFragment:

IOSDialogFragment.java:

public class IOSDialogFragment extends DialogFragment {

private View rootView;

@Nullable

@Override

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {

rootView = inflater.inflate(R.layout.fragment_ios_dialog, container, false);

return rootView;

}

}

我们就是单纯的引入我们写的布局,不做其他处理,我们运行后发现界面效果如下图所示:

标题内容的上方有一块区域

我们弹框布局的底部的背景色默认是灰色

我们针对这二个先做处理:

其实我们上方的一块区域是弹框的标题,

我们在IOSDialogFragment中添加:

@Override

public void onStart() {

super.onStart();

getDialog().setTitle("我是标题");

}

我们再看下弹框的效果:

我们可以看到标题头了。所以我们要去掉上面一块区域,只需要把弹框默认的标题头给去掉即可,只需要添加:

getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);

我们可以改变DecorView的背景色,设置它的背景色为透明即可:

View decorView = getDialog().getWindow().getDecorView();

decorView.setBackground(new ColorDrawable(Color.TRANSPARENT));

(PS:Window -> DecorView -> FrameLayout -> FrameLayout -> 我们的自定义View) 这个逻辑大家应该都知道的,所以我们只需要改变底部的DecorView的背景色即可。

经过上面二步的修改,我们可以看到了效果变成了这样:

那接下去如何让弹框变成在底部呢??????

我们知道最后我们的View是在window下面的,我们只需要让window的Grivaty属性是Bottom,这样,里面的元素都是居于底部即可。

Window window = getDialog().getWindow();

WindowManager.LayoutParams layoutParams = window.getAttributes();

layoutParams.gravity = Gravity.BOTTOM;

window.setAttributes(layoutParams);

我们再看下效果:

的确是在底部了,但是这时候可能有人会有疑问,我们这个自定义View的布局fragment_ios_dialog.xml里面,明明layout_width是match_parent,可是左右二边是间隙的,

这时候比如我想要按照自己的项目要求调整二边的间隙岂不是单纯的在自己的fragment_ios_dialog.xml就无法实现了。

我们就来看看到底是为什么二边有间隙,然后再来看如何自己处理:

我们知道我们的View都是被包含在window里面,虽然我们的自己的View的宽度已经设置成了match_parent,但是我们并没有对window设置宽度为最大。所以我们先来改变window的宽度。

改变window的宽度:

Window window = getDialog().getWindow();

WindowManager.LayoutParams layoutParams = window.getAttributes();

layoutParams.gravity = Gravity.BOTTOM;

layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;

window.setAttributes(layoutParams);

我们在前面修改弹框位置的代码处,多添加一句:

layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;

我们发现,果然二边的间隙变小了很多。但是还是有间隙,既然我们都已经把window的宽度变为match_parent,还是没填充,说明应该是有padding值。那我们马上就想到了,难道是DecorView里面有padding值。毕竟我们的View也是被包含在DecorView里面。废话不多说,我们马上实验:

decorView.setPadding(0,0,0,0);

然后我们再看效果,果不其然:

PS:这里还有另外一种方法,不写这句decorView.setPadding(0,0,0,0);而是直接设置window的背景颜色,window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));源码中其实也给DecorView设置了padding值。所以效果相同。

弹框从下而上显示:

我们看过ios的弹框效果,是从底部从下而上升起,然后消失的时候也是从上而下消失。所以消失的时候我们不能单纯的让DialogFragment执行dismiss(),而是先让弹框执行下移的动画效果,然后再dismiss()。

既然谈到了上下的移动,大家肯定马上想到了用TranslateAnimation动画来做,我们就一步步来看如何用这个来实现:

弹框出现动画:

Animation slide = new TranslateAnimation(

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF, 1.0f,

Animation.RELATIVE_TO_SELF, 0.0f

);

slide.setDuration(400);

slide.setFillAfter(true);

slide.setFillEnabled(true);

view.startAnimation(slide);

我们来看TranslateAnimation,这里我们传了八个参数,一般大家用到的是只传四个参数:

TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)

也就是从坐标(fromXDelta,fromYDelta)到(toXDelta,toYDelta)。

我们可以点进去这个构造函数查看:

public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) {

mFromXValue = fromXDelta;

mToXValue = toXDelta;

mFromYValue = fromYDelta;

mToYValue = toYDelta;

mFromXType = ABSOLUTE;

mToXType = ABSOLUTE;

mFromYType = ABSOLUTE;

mToYType = ABSOLUTE;

}

之所以我们以前用的只传了四个参数,是因为他给我们把另外四个参数以及赋了默认值,也就是ABSOLUTE。我们继续看有哪几种可以选择:

/**

* The specified dimension is an absolute number of pixels.

*/

public static final int ABSOLUTE = 0;

/**

* The specified dimension holds a float and should be multiplied by the

* height or width of the object being animated.

*/

public static final int RELATIVE_TO_SELF = 1;

/**

* The specified dimension holds a float and should be multiplied by the

* height or width of the parent of the object being animated.

*/

public static final int RELATIVE_TO_PARENT = 2;

通过字面意思我们也能理解:

ABSOLUTE是绝对坐标,RELATIVE_TO_SELF是相对于自身,RELATIVE_TO_PARENT是相对于父View。

而我们只需要我们的弹框显示的位置,让的起始位置如下图所示:

刚开始超过屏幕,并且高度为弹框自身的高度,然后再回到原始位置,所以我们就用:

Animation slide = new TranslateAnimation(

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF, 1.0f,

Animation.RELATIVE_TO_SELF, 0.0f

);

从原来的位置,增加了自身高度的距离为起始点,开始移动,然后再回到原来的位置。

消失动画:

只要跟上面反过来就可以了。同时这里我们要额外增加监听动画结束事件,因为我们让弹框往下移动结束后,要让这个弹框dismiss掉:

Animation slide = new TranslateAnimation(

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF, 1.0f

);

slide.setAnimationListener(new Animation.AnimationListener() {

@Override

public void onAnimationStart(Animation animation) {}

@Override

public void onAnimationEnd(Animation animation) {

IOSDialogFragment.this.dismiss();

}

@Override

public void onAnimationRepeat(Animation animation) {}

});

所以我们的动画的代码总结下就是:

@Nullable

@Override

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {

getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);

rootView = inflater.inflate(R.layout.fragment_ios_dialog, container, false);

slideToUp(rootView);

return rootView;

}

public void slideToUp(View view){

Animation slide = new TranslateAnimation(

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF,1.0f, Animation.RELATIVE_TO_SELF, 0.0f);

slide.setDuration(400);

slide.setFillEnabled(true);

slide.setFillAfter(true);

view.startAnimation(slide);

}

public void slideToDown(View view){

Animation slide = new TranslateAnimation(

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF,0.0f, Animation.RELATIVE_TO_SELF, 1.0f);

slide.setDuration(400);

slide.setFillEnabled(true);

slide.setFillAfter(true);

view.startAnimation(slide);

slide.setAnimationListener(new Animation.AnimationListener() {

@Override

public void onAnimationStart(Animation animation) {

}

@Override

public void onAnimationEnd(Animation animation) {

IOSDialogFragment.this.dismiss();//弹框消失

}

@Override

public void onAnimationRepeat(Animation animation) {

}

});

}

弹框的点击事件:

相关的点击事件就很简单了。只需要在onViewCreated中,通过findViewByid获取View实例,然后设置点击事件即可。

@Override

public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {

super.onViewCreated(view, savedInstanceState);

TextView titleView = (TextView) view.findViewById(R.id.tv_title);

titleView.setText("标题内容");

ListView listView = (ListView) view.findViewById(R.id.lv_menu);

listView.setAdapter(new ArrayAdapter(getActivity(),R.layout.menu_item,R.id.item_text,items));

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

@Override

public void onItemClick(AdapterView> parent, View view, int position, long id) {

//点击执行相关的事件

......

......

}

});

Button cancel = (Button) view.findViewById(R.id.cancel);

cancel.setText(mCancel);

cancel.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

//执行相关事件

........

}

});

}

具体的结束事件:

比如上面的cancel点击事件执行的肯定是弹框向下移动的动画。所以我们可以自己写个方法:

private boolean isAnimation = false;//用来判断是否多次点击。防止多次执行

private void dialogfinish(){

if (isAnimation) {

return;

}

isAnimation = true;

slideToDown(rootView);

}

public void slideToDown(View view){

.....

.....

slide.setAnimationListener(new Animation.AnimationListener() {

@Override

public void onAnimationStart(Animation animation) {}

@Override

public void onAnimationEnd(Animation animation) {

isAnimation = false;//用来判断是否多次点击。防止多次执行

IOSDialogFragment.this.dismiss();//弹框消失

}

@Override

public void onAnimationRepeat(Animation animation) {}

});

}

}

又或者不想再加新的方法,也可以直接复写dismiss方法:

private boolean isAnimation = false;//用来判断是否多次点击。防止多次执行

@Override

public void dismiss() {

if (isAnimation) {

return;

}

isAnimation = true;

slideToDown(rootView)

}

//然后再更换IOSDialogFragment.this.dismiss() -> IOSDialogFragment.super.dismiss()

public void slideToDown(View view){

.....

.....

slide.setAnimationListener(new Animation.AnimationListener() {

@Override

public void onAnimationStart(Animation animation) {}

@Override

public void onAnimationEnd(Animation animation) {

isAnimation = false;//用来判断是否多次点击。防止多次执行

IOSDialogFragment.super.dismiss();//弹框消失

}

@Override

public void onAnimationRepeat(Animation animation) {}

});

}

}

点击空白让弹框消失问题:

当点击上方一些空白处,我们会发现我们的弹框会直接消失,而不会像我们上面点击按钮点击事件那样,弹框先往下移动再消失,因为DialogFragment默认点击弹框外的时候,会直接dismiss,而不走我们的方法:

我们可以这么解决,直接对DecorView设置onTouchListener:

window.getDecorView().setOnTouchListener(new View.OnTouchListener() {

public boolean onTouch(View v, MotionEvent event) {

if (event.getAction() == MotionEvent.ACTION_UP) {

//弹框消失的动画执行相关代码

....

....

}

return true;

}

});

这样就会执行我们自己写的弹框消失的相关事件的了。

最后结语

希望大家不要喷我,哈哈。如果哪里写错了。可以下面评论回复,谢谢大家了。O(∩_∩)O~

最后附上Demo

android 仿ios 底部弹出,项目需求讨论-仿ios底部弹框实现及分析相关推荐

  1. 高仿163网站广告弹出层(每天定时24小时弹出一次)

    高仿163网站广告弹出层(每天定时24小时弹出一次) 这里和京东首页定时弹出广告功能一样:用JS实现网站首页弹出广告:超级炫酷的定时弹出图片广告:淘宝网站广告弹出层实现. 高仿163网站广告弹出层(每 ...

  2. 排版 项目 html,项目需求讨论: 文字显示排版— Html 格式

    嗨,各位,今天来个小技巧,估计很多人都知道,我也就重复提下罢了.. 比如 升级更新框 通知提示框 我们看到,我用红框框出来的地方 1.直接使用系统自带的AlertDialog的提示框,我们看到了我们更 ...

  3. php layui弹出修改功能,非常好用的弹出层 layer,常用功能demo,快速上手!

    功能强大,实用,操作方便,文档齐全. 参数灵活,丰富.可以作为开发项目的公共模块,多处使用. 老文档地址:http://layer.layui.com/api.html 已经停止维护 常用功能代码de ...

  4. element-UI 弹出组件dialog的遮罩层在弹出层的上面 - 解决篇

    element-UI 弹出组件的遮罩层在弹出层dialog模态框的上面? bug演示: 代码逻辑调整之后,页面就正常了(代码和效果图 · 见下文): 解决办法: 将dialog组件剪贴到最父级div元 ...

  5. 开机弹出“今日看点”和屏幕右下角弹出小广告的解决方法

    <开机弹出"今日看点"和屏幕右下角弹出小广告的解决方法!> 最近几天,我家电脑屏幕右下角经常弹出广告窗口.<腾讯QQ>和<迅雷看看>弹出来的广告 ...

  6. html 弹出层 边框半透明,js+CSS实现弹出居中背景半透明div层的方法

    本文实例讲述了js+CSS实现弹出居中背景半透明div层的方法.分享给大家供大家参考.具体实现方法如下: js+CSS弹出居中的背景半透明div层 body{margin:0px;} #bg{widt ...

  7. 关于百度地图最大层级聚合点鼠标覆盖弹出对话框需求实现

    需要的开源库 1.MarkerClusterer.js :计算聚合点的生成 2.TextIconOverlay.js : 聚合点的图标 需求: 1. 点击聚合点 判断是不是最大层级:如果不是最大层级, ...

  8. Js实现点击超链接弹出层,效果仿Discuz登录!

    主要应用到的是dispaly:none 和 dispaly:block;来控制实现的: <a id="link" href="#" onclick=&qu ...

  9. 项目需求讨论-标题栏上的搜索功能

    今天讲的就是一个很简单的具体开始时候遇到的需求,在标题栏中实现搜索功能,而且美工要求需要实现下面GIF图的效果,我就实现了下,可能不是最好的,有哪里可以更方便请大家指出.正好仔细的讲解了下Search ...

最新文章

  1. XML读取 java
  2. 运行 vue 报node错
  3. Objective-C NSSetNSMutableSet以及CountedSet
  4. 欲善其事,先利其器 | IDCF第6期DevOps案例研究回顾(附视频)
  5. linux php自动执行_linux下实现定时执行php脚本
  6. php 微信实时更新,微信小程序修改data使页面数据实时更新的代码示例
  7. Java 学习多态笔记
  8. Ubuntu 20.10 每日构建镜像已经可以下载测试
  9. System Center 2012各组件介绍
  10. 简单Java类和数据库操作及javafx的结合小项目(二)
  11. ASP.NET 批量更新
  12. 项目部署服务器 visio图,云服务器visio图标
  13. 哪个大佬有c#三层架构写的餐饮管理系统源代码
  14. java ssm Vue高校奖学金管理系统毕业设计项目介绍
  15. Java对象的GC内存分配和回收策略
  16. ps添加的阴影怎么去除_去除人物照片脸部阴影的PS技巧
  17. 我的2019归零,2020走你
  18. 每分钟54万多条数据更新,商品系统性能如何优化?
  19. CentOS 7 从本地 ISO 映像文件安装 Gnome GUI
  20. 开发一个电商网站大概多少钱

热门文章

  1. 电视hdmi接口在哪_变废为宝!把旧笔记本电脑当电视盒子用
  2. 【清华夏令营2016模拟5.31】图森破
  3. 计算机体系结构实验三 指令调度和延迟分支
  4. 模态逻辑(1)——从命题逻辑开始
  5. 拼车网源码php,拼车网源码-thinkphp PHP微信拼车源码 环境要求: 服务器系统:服务器系统 推荐Linux系统 - 下载 - 搜珍网...
  6. 钉钉机器人关键词应答_Gmail 邮件推送到钉钉群机器人(详细)教程|国内接收 Gmail 邮件...
  7. 第六章 利用深度Q学习来实现最优控制的智能体
  8. Vim编辑器基本操作
  9. 卷积的拉普拉斯变换等于拉普拉斯变换的乘积
  10. Django——创建数据库和表