Android仿網(wǎng)易嚴(yán)選底部彈出菜單效果
在網(wǎng)易嚴(yán)選的看東西的時(shí)候在商品詳情頁里看到他的底部彈出菜單,本能反應(yīng)是想用DottomSheetDialog或者PopupWindow來實(shí)現(xiàn),可是發(fā)現(xiàn)實(shí)現(xiàn)不了他那種效果,于是就自己模仿一個(gè)像嚴(yán)選這樣的底部彈出菜單。
不管是DottomSheetDialog或者PopupWindow他們的陰影背景都是全部覆蓋的,這就造成除了菜單內(nèi)容的View之外其他都是陰影的,而嚴(yán)選不是這樣的。嘮叨到此,首先展示效果圖如下:

是不是還可以呢,由于代碼量不多卻注釋詳細(xì),所以先貼出代碼再一一詳說:
BottomPopupWindowView類:
public class BottomPopupWindowView extends LinearLayout{
private AnimatorListener animatorListener;
//底部內(nèi)容的View
private FrameLayout base_view;
//內(nèi)容的View
private FrameLayout content_view;
//背景的View
private RelativeLayout popup_bg;
//xml加載的View
private View bottomPopouView;
//外部加載的內(nèi)容View
private View contentView;
//外部加載的底部內(nèi)容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);
//初始化各種數(shù)值
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);
//把整個(gè)View都加載在LinearLayout里以顯示出來
addView(bottomPopouView);
//背景顏色監(jiān)聽
popup_bg.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
disMissPopupView();
}
});
//屏蔽內(nèi)容區(qū)域點(diǎn)擊事件
content_view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view){}
});
//屏蔽底部內(nèi)容區(qū)域點(diǎn)擊事件
base_view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view){}
});
//內(nèi)容區(qū)域判斷是否向下,手勢向下就關(guān)閉彈框
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){
//剛開始加載底部內(nèi)容區(qū)域,只需一次就行,多次報(bào)錯(cuò)
base_view.addView(baseView);
mDrawable=false;
}
}
public void showPopouView(){
if(contentView!=null){
//開始動(dòng)畫數(shù)據(jù)
startAnimation();
//開啟背景顏色的漸變動(dòng)畫
popup_bg.setVisibility(View.VISIBLE);
popup_bg.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.bp_bottom_bg_in));
//把這個(gè)區(qū)域全部顯示出來
((BottomPopupWindowView)this).setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,RelativeLayout.LayoutParams.MATCH_PARENT));
//假如內(nèi)容區(qū)域
content_view.addView(contentView,0);
content_view.setVisibility(View.VISIBLE);
//開啟內(nèi)容區(qū)域動(dòng)畫
content_view.setAnimation(AnimationUtils.loadAnimation(getContext(),R.anim.bp_bottom_view_in));
}
}
public void disMissPopupView(){
//開始關(guān)閉動(dòng)畫數(shù)據(jù)
endAnimation();
//開啟內(nèi)容區(qū)域動(dòng)畫
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) {
//等內(nèi)容區(qū)域動(dòng)畫結(jié)束后,清楚所有View
content_view.removeAllViews();
//開啟背景顏色的漸變動(dòng)畫
popup_bg.setVisibility(View.GONE);
popup_bg.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.bp_bottom_bg_out));
//把整個(gè)控件的大小恢復(fù)到底部View區(qū)域的大小
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);
}
});
//開始動(dòng)畫
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();
}
//開始動(dòng)畫數(shù)據(jù)變化
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();
}
//結(jié)束動(dòng)畫數(shù)值變化
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();
}
}
對應(yīng)的加載的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是這整個(gè)BottomPopupWindowView里的骨架,然后在BottomPopupWindowView初始化的時(shí)候通過addView()來加載整個(gè)骨架布局。在onDraw()里只需加載一次baseView就可以了,不然后重復(fù)加載導(dǎo)致報(bào)錯(cuò)。這樣就初始化成功了,剛開始只會加載baseView的界面,就相當(dāng)于嚴(yán)選最下面的購物車立即購買等界面。

2.當(dāng)調(diào)用showPopouView()時(shí)顯示菜單的。startAnimation()方法只是為了產(chǎn)生動(dòng)畫的數(shù)據(jù)。
popup_bg.setVisibility(View.VISIBLE); popup_bg.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.bp_bottom_bg_in));
只是為了開啟背景漸變的動(dòng)畫沒什么說的。最重要的是顯示菜單實(shí)現(xiàn)是把BottomPopupWindowView的大小擴(kuò)展到全屏,所以設(shè)置((BottomPopupWindowView)this).setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,RelativeLayout.LayoutParams.MATCH_PARENT));,然后把彈出菜單的View即contentView裝進(jìn)content_view即可,然后開啟彈出動(dòng)畫就實(shí)現(xiàn)了。
3.最后是disMissPopupView()方法關(guān)閉彈窗。endAnimation()方法只是為了產(chǎn)生動(dòng)畫的數(shù)據(jù)。再啟動(dòng)內(nèi)容域View即content_View的退出動(dòng)畫,在動(dòng)畫結(jié)束后用content_view.removeAllViews();
起初菜單內(nèi)容,再像上面一樣開啟背景顏色漸變動(dòng)畫,最后只需使BottomPopupWindowView恢復(fù)原來的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);
這就是核心的代碼功能了,代碼量不多具體細(xì)節(jié)看上面的源碼。
有人或許會問返回動(dòng)畫的數(shù)據(jù)有什么用,很簡單就是為了實(shí)現(xiàn)嚴(yán)選菜單框出來時(shí)整個(gè)上面詳情的縮放。具體看如下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="規(guī)格數(shù)量選擇"/>
<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();
}
}
}
其中設(shè)置內(nèi)容菜單的View
BottomPopupWindowView.setContextView(bottomView);
設(shè)置沒有顯示菜單時(shí)候顯示的View(注:bottomView的高度要和BottomPopupWindowView的高度一樣,具體看demo)
BottomPopupWindowView.setBaseView(bottomView);
而回調(diào)的public void startValue(int value)和public void endValue(int value)設(shè)置動(dòng)畫監(jiān)聽放回的數(shù)據(jù),以便根據(jù)數(shù)據(jù)實(shí)現(xiàn)動(dòng)畫,嚴(yán)選的彈出和顯示商品詳情動(dòng)畫很簡單就是不斷設(shè)設(shè)置View的間距就可以了。
最后附上demo和源碼鏈接https://github.com/jack921/BottomPopupWindowDemo
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android使用CoordinatorLayout實(shí)現(xiàn)底部彈出菜單
- Android底部菜單欄實(shí)現(xiàn)的實(shí)例代碼
- android SectorMenuView底部導(dǎo)航扇形菜單的實(shí)現(xiàn)代碼
- android實(shí)現(xiàn)上滑屏幕隱藏底部菜單欄的示例
- Android仿微信底部菜單欄效果
- Android使用Activity實(shí)現(xiàn)從底部彈出菜單或窗口的方法
- Android自定義控件實(shí)現(xiàn)底部菜單(下)
- Android自定義控件實(shí)現(xiàn)底部菜單(上)
- Android如何實(shí)現(xiàn)底部菜單固定到底部
相關(guān)文章
Android AccessibilityService實(shí)現(xiàn)微信搶紅包插件
這篇文章主要介紹了Android AccessibilityService實(shí)現(xiàn)微信搶紅包插件的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Android監(jiān)控和阻斷InputDispatching ANR的方法
如何在Java層實(shí)現(xiàn)異步監(jiān)控和阻斷InputDispatching ANR?我相信這是很多開發(fā)者都想要的功能,本篇,我們會通過“探索”兩種方案來實(shí)現(xiàn)在Java層監(jiān)控&阻斷的方法,需要的朋友可以參考下2024-04-04
Android中隱藏標(biāo)題欄和狀態(tài)欄的方法
Android中隱藏標(biāo)題欄和狀態(tài)欄的方法,需要的朋友可以參考一下2013-05-05
android串口開發(fā)入門之搭建ndk開發(fā)環(huán)境及第一個(gè)jni調(diào)用程序
這篇文章主要給大家介紹了關(guān)于android串口開發(fā)入門之搭建ndk開發(fā)環(huán)境及第一個(gè)jni調(diào)用程序的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01
Android基礎(chǔ)入門之dataBinding的簡單使用教程
DataBinding 是谷歌官方發(fā)布的一個(gè)框架,顧名思義即為數(shù)據(jù)綁定,下面這篇文章主要給大家介紹了關(guān)于Android基礎(chǔ)入門之dataBinding的簡單使用,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
Android自定義日歷Calender代碼實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Android自定義日歷Calender實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09

