Android仿网易严选底部弹出菜单效果
在网易严选的看东西的时候在商品详情页里看到他的底部弹出菜单,本能反应是想用dottomsheetdialog或者popupwindow来实现,可是发现实现不了他那种效果,于是就自己模仿一个像严选这样的底部弹出菜单。
不管是dottomsheetdialog或者popupwindow他们的阴影背景都是全部覆盖的,这就造成除了菜单内容的view之外其他都是阴影的,而严选不是这样的。唠叨到此,首先展示效果图如下:
是不是还可以呢,由于代码量不多却注释详细,所以先贴出代码再一一详说:
bottompopupwindowview类:
public class bottompopupwindowview extends linearlayout{ private animatorlistener animatorlistener; //底部内容的view private framelayout base_view; //内容的view private framelayout content_view; //背景的view private relativelayout popup_bg; //xml加载的view private view bottompopouview; //外部加载的内容view private view contentview; //外部加载的底部内容view private view baseview; //手势的最小值 private float minvelocity=0; //加载一次的判断值 private boolean mdrawable=true; public void setanimatorlistener(animatorlistener animatorlistener) { this.animatorlistener = animatorlistener; } public void setbaseview(view baseview){ this.baseview=baseview; } public void setcontextview(view view){ this.contentview=view; } public void setcontentview(int id){ this.contentview=layoutinflater.from(getcontext()).inflate(id,null); } public bottompopupwindowview(context context) { this(context,null); } public bottompopupwindowview(context context, @nullable attributeset attrs) { this(context,attrs,0); } public bottompopupwindowview(context context, @nullable attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); //初始化各种数值 minvelocity=viewconfiguration.get(getcontext()).getscaledtouchslop(); bottompopouview= layoutinflater.from(getcontext()).inflate(r.layout.layout_bottom_popup,null); base_view=(framelayout)bottompopouview.findviewbyid(r.id.bottom_view); content_view=(framelayout)bottompopouview.findviewbyid(r.id.content_view); popup_bg=(relativelayout)bottompopouview.findviewbyid(r.id.popup_bg); //把整个view都加载在linearlayout里以显示出来 addview(bottompopouview); //背景颜色监听 popup_bg.setonclicklistener(new onclicklistener() { @override public void onclick(view v) { dismisspopupview(); } }); //屏蔽内容区域点击事件 content_view.setonclicklistener(new onclicklistener() { @override public void onclick(view view){} }); //屏蔽底部内容区域点击事件 base_view.setonclicklistener(new onclicklistener() { @override public void onclick(view view){} }); //内容区域判断是否向下,手势向下就关闭弹框 content_view.setontouchlistener(new ontouchlistener() { @override public boolean ontouch(view view, motionevent motionevent) { float y1=0,y2=0; if(motionevent.getaction() == motionevent.action_down) { y1 = motionevent.gety(); } if(motionevent.getaction() == motionevent.action_up){ y2 = motionevent.gety(); if((y2-y1)>minvelocity){ dismisspopupview(); } } return false; } }); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); if(mdrawable&&baseview!=null){ //刚开始加载底部内容区域,只需一次就行,多次报错 base_view.addview(baseview); mdrawable=false; } } public void showpopouview(){ if(contentview!=null){ //开始动画数据 startanimation(); //开启背景颜色的渐变动画 popup_bg.setvisibility(view.visible); popup_bg.setanimation(animationutils.loadanimation(getcontext(), r.anim.bp_bottom_bg_in)); //把这个区域全部显示出来 ((bottompopupwindowview)this).setlayoutparams(new relativelayout.layoutparams( relativelayout.layoutparams.match_parent,relativelayout.layoutparams.match_parent)); //假如内容区域 content_view.addview(contentview,0); content_view.setvisibility(view.visible); //开启内容区域动画 content_view.setanimation(animationutils.loadanimation(getcontext(),r.anim.bp_bottom_view_in)); } } public void dismisspopupview(){ //开始关闭动画数据 endanimation(); //开启内容区域动画 content_view.setvisibility(view.gone); animation animation=animationutils.loadanimation(getcontext(),r.anim.bp_bottom_view_out); animation.setanimationlistener(new animation.animationlistener() { @override public void onanimationstart(animation animation) {} @override public void onanimationrepeat(animation animation) {} @override public void onanimationend(animation animation) { //等内容区域动画结束后,清楚所有view content_view.removeallviews(); //开启背景颜色的渐变动画 popup_bg.setvisibility(view.gone); popup_bg.setanimation(animationutils.loadanimation(getcontext(), r.anim.bp_bottom_bg_out)); //把整个控件的大小恢复到底部view区域的大小 relativelayout.layoutparams layoutparams=new relativelayout.layoutparams( relativelayout.layoutparams.match_parent,getviewheight((bottompopupwindowview)bottompopupwindowview.this)); layoutparams.addrule(relativelayout.align_parent_bottom,-1); ((bottompopupwindowview)bottompopupwindowview.this).setlayoutparams(layoutparams); } }); //开始动画 content_view.setanimation(animation); } //获取view的高度 public int getviewheight(view view){ int width =view.measurespec.makemeasurespec(0,view.measurespec.unspecified); int height =view.measurespec.makemeasurespec(0,view.measurespec.unspecified); view.measure(width,height); return view.getmeasuredheight(); } //开始动画数据变化 public void startanimation(){ valueanimator valueanimator = valueanimator.ofint(0,40); valueanimator.setduration(250); valueanimator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator valueanimator) { if(animatorlistener!=null){ animatorlistener.startvalue((int) valueanimator.getanimatedvalue()); } } }); valueanimator.start(); } //结束动画数值变化 public void endanimation() { valueanimator valueanimator = valueanimator.ofint(40,0); valueanimator.setduration(250); valueanimator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator valueanimator) { if(animatorlistener!=null){ animatorlistener.endvalue((int) valueanimator.getanimatedvalue()); } } }); valueanimator.start(); } }
对应的加载的xml布局是:
layout_bottom_popou.xml如下:
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#707a7a7a"> <relativelayout android:id="@+id/popup_bg" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#707a7a7a" android:layout_above="@+id/bottom_view"></relativelayout> <framelayout android:id="@+id/content_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@+id/bottom_view" android:orientation="horizontal"> </framelayout> <framelayout android:id="@+id/bottom_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignparentbottom="true"></framelayout> </relativelayout>
1.在bottompopupwindowview是继承linearlayout,而layout_bottom_popou.xml是这整个bottompopupwindowview里的骨架,然后在bottompopupwindowview初始化的时候通过addview()来加载整个骨架布局。在ondraw()里只需加载一次baseview就可以了,不然后重复加载导致报错。这样就初始化成功了,刚开始只会加载baseview的界面,就相当于严选最下面的购物车立即购买等界面。
2.当调用showpopouview()时显示菜单的。startanimation()方法只是为了产生动画的数据。
popup_bg.setvisibility(view.visible); popup_bg.setanimation(animationutils.loadanimation(getcontext(), r.anim.bp_bottom_bg_in));
只是为了开启背景渐变的动画没什么说的。最重要的是显示菜单实现是把bottompopupwindowview的大小扩展到全屏,所以设置((bottompopupwindowview)this).setlayoutparams(new relativelayout.layoutparams(relativelayout.layoutparams.match_parent,relativelayout.layoutparams.match_parent));,然后把弹出菜单的view即contentview装进content_view即可,然后开启弹出动画就实现了。
3.最后是dismisspopupview()方法关闭弹窗。endanimation()方法只是为了产生动画的数据。再启动内容域view即content_view的退出动画,在动画结束后用content_view.removeallviews();
起初菜单内容,再像上面一样开启背景颜色渐变动画,最后只需使bottompopupwindowview恢复原来的baseview的大小及可以了,具体如下:
relativelayout.layoutparams layoutparams=new relativelayout.layoutparams( relativelayout.layoutparams.match_parent,getviewheight((bottompopupwindowview)bottompopupwindowview.this)); layoutparams.addrule(relativelayout.align_parent_bottom,-1); ((bottompopupwindowview)bottompopupwindowview.this).setlayoutparams(layoutparams);
这就是核心的代码功能了,代码量不多具体细节看上面的源码。
有人或许会问返回动画的数据有什么用,很简单就是为了实现严选菜单框出来时整个上面详情的缩放。具体看如下demo,首先给出界面xml,如下:
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <linearlayout android:id="@+id/main_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/coloraccent" android:orientation="vertical"> <imageview android:id="@+id/banner_img" android:layout_width="match_parent" android:layout_height="300dp" android:scaletype="fitxy" android:src="@mipmap/banner"/> <view android:layout_width="match_parent" android:layout_height="0.1dp" android:background="@color/colorprimary"></view> <relativelayout android:id="@+id/guige" android:layout_width="match_parent" android:layout_height="50dp" android:background="#ffffff"> <textview android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centervertical="true" android:layout_marginleft="15dp" android:textsize="15dp" android:text="规格数量选择"/> <imageview android:layout_width="20dp" android:layout_height="20dp" android:layout_alignparentright="true" android:layout_centervertical="true" android:layout_marginright="15dp" android:src="@mipmap/ic_jiantou"/> </relativelayout> <view android:layout_width="match_parent" android:layout_height="0.1dp" android:background="@color/colorprimary"></view> </linearlayout> <com.jack.bottompopupwindowview.bottompopupwindowview android:id="@+id/bottom_popup" android:layout_width="match_parent" android:layout_height="50dp" android:background="@android:color/transparent" android:layout_alignparentbottom="true"> </com.jack.bottompopupwindowview.bottompopupwindowview> </relativelayout>
这就是上面效果图的界面布局,没什么可以说的,再看事例代码如下:
public class mainactivity extends appcompatactivity implements view.onclicklistener, animatorlistener { private bottompopupwindowview bottompopupwindowview; private view contentview; private view bottomview; private linearlayout mainview; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mainview=(linearlayout)findviewbyid(r.id.main_view); bottomview=layoutinflater.from(this).inflate(r.layout.layout_bottom_view,null); (bottomview.findviewbyid(r.id.promptly_buy)).setonclicklistener(this); (findviewbyid(r.id.guige)).setonclicklistener(this); bottompopupwindowview=(bottompopupwindowview)findviewbyid(r.id.bottom_popup); bottompopupwindowview.setonclicklistener(this); bottompopupwindowview.setbaseview(bottomview); contentview=layoutinflater.from(this).inflate(r.layout.layout_content_view,null); bottompopupwindowview.setcontextview(contentview); (contentview.findviewbyid(r.id.ic_cancel)).setonclicklistener(this); bottompopupwindowview.setanimatorlistener(this); } @override public void onclick(view view) { switch(view.getid()){ case r.id.promptly_buy: case r.id.ic_cancel: bottompopupwindowview.dismisspopupview(); break; case r.id.guige: bottompopupwindowview.showpopouview(); break; } } @override public void startvalue(int value) { setmargins (mainview,value-10,value,value-10,value); } @override public void endvalue(int value) { setmargins (mainview,value,value,value,value); } public static void setmargins (view v, int l, int t, int r, int b) { if (v.getlayoutparams() instanceof viewgroup.marginlayoutparams) { viewgroup.marginlayoutparams p = (viewgroup.marginlayoutparams) v.getlayoutparams(); p.setmargins(l, t, r, b); v.requestlayout(); } } }
其中设置内容菜单的view
bottompopupwindowview.setcontextview(bottomview);
设置没有显示菜单时候显示的view(注:bottomview的高度要和bottompopupwindowview的高度一样,具体看demo)
bottompopupwindowview.setbaseview(bottomview);
而回调的public void startvalue(int value)和public void endvalue(int value)设置动画监听放回的数据,以便根据数据实现动画,严选的弹出和显示商品详情动画很简单就是不断设设置view的间距就可以了。
最后附上demo和源码链接https://github.com/jack921/bottompopupwindowdemo
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读