欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android仿网易严选底部弹出菜单效果

程序员文章站 2022-05-02 21:07:17
在网易严选的看东西的时候在商品详情页里看到他的底部弹出菜单,本能反应是想用dottomsheetdialog或者popupwindow来实现,可是发现实现不了他那种效果,于...

在网易严选的看东西的时候在商品详情页里看到他的底部弹出菜单,本能反应是想用dottomsheetdialog或者popupwindow来实现,可是发现实现不了他那种效果,于是就自己模仿一个像严选这样的底部弹出菜单。

不管是dottomsheetdialog或者popupwindow他们的阴影背景都是全部覆盖的,这就造成除了菜单内容的view之外其他都是阴影的,而严选不是这样的。唠叨到此,首先展示效果图如下:

Android仿网易严选底部弹出菜单效果

是不是还可以呢,由于代码量不多却注释详细,所以先贴出代码再一一详说:

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的界面,就相当于严选最下面的购物车立即购买等界面。

Android仿网易严选底部弹出菜单效果

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。