一、簡(jiǎn)單介紹
這三個(gè)不同普通的Dialog誓竿,它們都可以通過(guò)手勢(shì)拖動(dòng)來(lái)進(jìn)行隱藏或顯示磅网;
各自的特點(diǎn):
- bottomDialog
依賴于CoordinatorLayout和BottomSheetBehavior,需要將底部菜單作為CoordinatorLayout的子View,并且需要設(shè)置app:layout_behavior="@string/bottom_sheet_behavior",適合固定的底部菜單筷屡,但是不夠靈活涧偷,需要依賴父布局和behavior;
- bottomDialog
- 2.bottomSheetDialog
使用方式類似dialog,布局可以使動(dòng)態(tài)的布局毙死,比如是個(gè)Recycler嫂丙; - 3.bottomSheetDialogFragment
通過(guò)繼承與BottomSheetFragment來(lái)實(shí)現(xiàn)底部菜單布局,適用于動(dòng)態(tài)指定的布局规哲,并且根據(jù)Fragment的生命周期做較多邏輯操作的情況跟啤;
下面介紹下幾種狀態(tài)值:
- 1.STATE_EXPANDED:展開狀態(tài),顯示完整布局唉锌;
- 2.STATE_COLLAPED:折疊狀態(tài)隅肥,顯示設(shè)置的peekHeight高度,如果peekHeight的高度為0袄简,則全部隱藏腥放,與STATE_HIDDEN狀態(tài)一樣;
- 3.STATE_HIDDEN:隱藏狀態(tài)绿语,隱藏全部布局秃症;
- 4.STATE_DRAGGING:拖拽時(shí)的狀態(tài);是個(gè)中間狀態(tài)吕粹;
- 5.STATE_SETLLING:釋放時(shí)的狀態(tài)种柑;是個(gè)中間狀態(tài);
前三個(gè)狀態(tài)值時(shí)穩(wěn)定狀態(tài)匹耕,也就是說(shuō):釋放穩(wěn)定后聚请,最終會(huì)達(dá)到前三個(gè)某個(gè)狀態(tài)之一;后兩種類似ViewPager的SCROLL_STATE_DRAGGING和SCROLL_STATE_SETTLING兩種狀態(tài)值
需要注意區(qū)別的是折疊和隱藏: - 1.折疊
當(dāng)我們?cè)O(shè)置收起的高度app:behavior_peekHeight稳其,在折疊的穩(wěn)定狀態(tài)時(shí)驶赏,不會(huì)完全隱藏,可以通過(guò)拖動(dòng)這部分布局使它進(jìn)入展開狀態(tài) - 2.隱藏
意味著整個(gè)底部菜單完全不可見既鞠,但是默認(rèn)情況下是沒(méi)有這種狀態(tài)的煤傍,需要設(shè)置:app:behavior_hidbehavior_hideable = “true"才會(huì)出現(xiàn)這種狀態(tài);
圖解如下:
二嘱蛋、BottomDialog的詳解
bottomDialog依賴于CoordinatorLayout和behavior蚯姆,我們一般布局如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.androidwanga.serenitynanian.serenityproject.BottomDialogActivity">
<!--底部菜單布局-->
<include layout="@layout/layout_bottom_sheet_linear" />
</android.support.design.widget.CoordinatorLayout>
底部菜單布局如下:父布局必須為CoordinatorLayout椅寺,如上布局所示
- 1.使用LinearLayout;
- 2.使用RecyclerView
2.1 使用LinearLayout實(shí)現(xiàn)BottomDialog
layout_bottom_sheet_linear.xml布局如下:
**注意:
1.底部菜單必須設(shè)置:app:layout_behavior,只有設(shè)置了才能拖拽打開或隱藏,否則和普通的布局沒(méi)什么差別蒋失;
2.必須設(shè)置peekHeight高度返帕,否則在底部菜單直接用自己的默認(rèn)的高度;
3.可選設(shè)置:是否支持隱藏篙挽,如果設(shè)置了app:behavior_hideable 為false或者不設(shè)置荆萤,那么調(diào)用etState(BottomSheetBehavior.STATE_HIDDEN)沒(méi)有效果;
**
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/bottom_sheet_behavior"
app:behavior_hideable = "true"
app:behavior_peekHeight="66dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:text="@string/app_name" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#999999" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:text="@string/app_name" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#999999" />
</LinearLayout>
2.2 使用Recycler實(shí)現(xiàn)BottomDialog
布局如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.androidwanga.serenitynanian.serenityproject.BottomDialogActivity">
<!--底部菜單布局-->
<!--LinearLayout底部布局菜單-->
<!--<include layout="@layout/layout_bottom_sheet_linear" />-->
<!--RecyclerView底部布局菜單-->
<include layout="@layout/layout_bottom_dialog_recycler"/>
</android.support.design.widget.CoordinatorLayout>
底部菜單的布局如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/bottom_dialog_recyclerview"
xmlns:app = "http://schemas.android.com/apk/res-auto"
app:layout_behavior="@string/bottom_sheet_behavior"
app:behavior_peekHeight = "100dp"
app:behavior_hideable = "true"
>
</android.support.v7.widget.RecyclerView>
當(dāng)使用這種方式铣卡,如果初始時(shí)候處于收起狀態(tài)链韭,那么當(dāng)手指上滑時(shí),會(huì)優(yōu)先讓底部菜單慢慢進(jìn)入展開狀態(tài)煮落,當(dāng)完全進(jìn)入展開狀態(tài)之后敞峭,開始讓列表向底部滾動(dòng)。而當(dāng)手指下滑時(shí)蝉仇,優(yōu)先讓列表向頂部滾動(dòng)旋讹,當(dāng)滾動(dòng)到頂部之后,讓菜單從展開狀態(tài)慢慢進(jìn)入到收起狀態(tài)轿衔。
使用和上面的LinearLayout一樣沉迹,不過(guò)注意一點(diǎn)是,在RecyclerView的adapter中的onCreateViewHolder方法中害驹,在inflater item布局時(shí)鞭呕,第二個(gè)parent參數(shù)必須設(shè)置null,否則只會(huì)顯示一個(gè)item項(xiàng)宛官;
@Override
public BottomDialogViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//注意這里的第二個(gè)參數(shù)必須為null葫松,如果為parent時(shí),由于嵌套的作用底洗,只會(huì)顯示一個(gè)item項(xiàng)
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.bottom_sheet_item, null, false);
return new BottomDialogViewHolder(view);
}
除此之外腋么,我們還可以通過(guò)BottomSheetBehavior監(jiān)聽底部菜單各種狀態(tài)的變化:
//監(jiān)聽底部菜單的狀態(tài)變化
recyclerViewBottomSheetBehavior = BottomSheetBehavior.from(mRecyclerView);
recyclerViewBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
System.out.println("bottomSheet = [" + bottomSheet + "], newState = [" + newState + "]");
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
System.out.println("bottomSheet = [" + bottomSheet + "], slideOffset = [" + slideOffset + "]");
}
});
第一個(gè)回調(diào)函數(shù)用來(lái)監(jiān)聽BottomSheet狀態(tài)的改變,也就是我們上面所說(shuō)到的五種狀態(tài)枷恕,而onSlide回調(diào)當(dāng)中的slideOffset則用來(lái)監(jiān)聽底部菜單的偏移量:
- 當(dāng)處于展開狀態(tài)時(shí)党晋,偏移量為1
- 當(dāng)處于收起狀態(tài)時(shí),偏移量為0
- 當(dāng)處于隱藏狀態(tài)時(shí)徐块,偏移量為-1
二、BottomSheetDialog詳解
從繼承關(guān)系看到BottomSheetDialog是support.v7下的擴(kuò)展類灾而,是Dialog的子類胳喷;
當(dāng)我們通過(guò)setContentView方法傳入自定義布局的時(shí)候产镐,它會(huì)將這個(gè)布局使用CoordinatorLayout包裹起來(lái),所以當(dāng)使用BottomSheetDialog的時(shí)候,底部菜單和根布局并不屬于同一個(gè)window呛梆;Dialog的根節(jié)點(diǎn)其實(shí)并不是通過(guò)setContentView()傳入的View衷畦,它實(shí)際上是用CoordinatorLayout把它包裝了起來(lái),這才實(shí)現(xiàn)了拖動(dòng)展開和隱藏的行為。
二瞧掺、BottomSheetDialog類簡(jiǎn)單的使用方式
直接看下代碼清晰明了:
private void showSharedDialog() {
if (bottomSheetDialog == null) {
bottomSheetDialog = new BottomSheetDialog(this);
bottomSheetDialog.setCancelable(true);
bottomSheetDialog.setCanceledOnTouchOutside(true);
//這里的layout是要顯示的布局內(nèi)容,里面可以放RecyclerView等
View view = LayoutInflater.from(this).inflate(R.layout.bottom_sheet_share_dialog, null);
bottomSheetDialog.setContentView(view);
bottomSheetDialog.show();
//以下設(shè)置是為了解決:下滑隱藏dialog后凡傅,再次調(diào)用show方法顯示時(shí)辟狈,不能彈出Dialog----在真機(jī)測(cè)試時(shí)不寫下面的方法也未發(fā)現(xiàn)問(wèn)題
View delegateView = bottomSheetDialog.getDelegate().findViewById(android.support.design.R.id.design_bottom_sheet);
final BottomSheetBehavior<View> sheetBehavior = BottomSheetBehavior.from(delegateView);
sheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
//在下滑隱藏結(jié)束時(shí)才會(huì)觸發(fā)
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
bottomSheetDialog.dismiss();
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
//每次滑動(dòng)都會(huì)觸發(fā)
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
System.out.println("onSlide = [" + bottomSheet + "], slideOffset = [" + slideOffset + "]");
}
});
} else {
bottomSheetDialog.show();
}
}
在代碼中能夠通過(guò) BottomSheetBehavior.from(delegateView);獲得與布局相關(guān)聯(lián)的BottomSheetBehavior,它有五種狀態(tài):
- 1.STATE_EXPANDED:展開狀態(tài)夏跷,顯示完整布局哼转;
- 2.STATE_COLLAPSED:折疊狀態(tài),顯示設(shè)置peekHeight的高度槽华,如果peekHeight的設(shè)置為0壹蔓,則全部隱藏,與STATE_HIDDEN狀態(tài)一樣猫态;
- 3.STATE_HIDDEN:隱藏狀態(tài)佣蓉,隱藏全部布局;
- 4.STATE_DRAGGING:拖拽時(shí)的狀態(tài)亲雪;是個(gè)中間狀態(tài)偏螺;
- 5.STATE_SETLLING:釋放后的狀態(tài);是個(gè)中間狀態(tài)匆光;
前三個(gè)是穩(wěn)定的狀態(tài)套像,也就是釋放后,肯定會(huì)達(dá)到前三個(gè)某個(gè)狀態(tài)终息;
下面是bottom_sheet_share_dialog.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="?attr/actionBarSize">
<ImageView
android:layout_width="wrap_content"
android:src="@mipmap/ic_launcher_round"
android:layout_height="match_parent" />
<TextView
android:layout_width="match_parent"
android:gravity="center"
android:text="條目一"
android:layout_height="match_parent" />
</LinearLayout>
</LinearLayout>
三夺巩、BottomSheetDialog類復(fù)雜用法--里面使用RecyclerView
具體的看代碼:
private void showBottomDialog() {
BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(this);
View view = LayoutInflater.from(this).inflate(R.layout.bottom_sheet_layout, null);
handleList(view);
bottomSheetDialog.setContentView(view);
bottomSheetDialog.setCancelable(true);
bottomSheetDialog.setCanceledOnTouchOutside(true);
bottomSheetDialog.show();
}
private void handleList(View view) {
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
MyRecyclerAdapter adapter = new MyRecyclerAdapter();
recyclerView.setAdapter(adapter);
adapter.setData(getDatas());
adapter.notifyDataSetChanged();
}
private List<String> getDatas() {
List<String> list = new ArrayList<>();
for (int i = 0 ;i<30 ;i++) {
list.add("android:"+i);
}
return list;
}
public static class MyRecyclerAdapter extends RecyclerView.Adapter{
private List<String> mData ;
public void setData(List<String> list) {
mData = list ;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.bottom_sheet_item,null));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
MyViewHolder myViewHolder = (MyViewHolder) holder;
myViewHolder.index.setText(mData.get(position)+"");
myViewHolder.name.setText(mData.get(position)+"");
}
@Override
public int getItemCount() {
return mData == null ? 0 :mData.size();
}
public static class MyViewHolder extends RecyclerView.ViewHolder{
private TextView name ;
private TextView index ;
public MyViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.bottom_sheet_dialog_item_name);
index = (TextView) itemView.findViewById(R.id.bottom_sheet_dialog_item_index);
}
}
}
//下面是recycler.xml布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:gravity="center_vertical"
android:layout_height="?attr/actionBarSize">
<TextView
android:text="收藏全部"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
<TextView
android:text="播放列表(20)"
android:gravity="center"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
<TextView
android:text="清空"
android:gravity="center"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:background="@android:color/black"
android:layout_height="1dp"/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:id="@+id/recycler"
android:layout_height="match_parent"></android.support.v7.widget.RecyclerView>
</LinearLayout>
三、BottomSheetDialogFragment詳解
首先看下系統(tǒng)源碼:繼承與AppCompatDialogFragment周崭,而AppCompatDialogFragment繼承與DialogFragment柳譬;系統(tǒng)重寫了onCreateDialog方法,返回一個(gè)與上面分析的BottomSheetDialog一樣续镇,具體的使用用法和DialogFragment一樣美澳;
/**
* Modal bottom sheet. This is a version of {@link DialogFragment} that shows a bottom sheet
* using {@link BottomSheetDialog} instead of a floating dialog.
*/
public class BottomSheetDialogFragment extends AppCompatDialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new BottomSheetDialog(getContext(), getTheme());
}
}
下面是具體使用:
首先定義一個(gè)Fragment繼承BottomSheetDialogFragment,如下:
/**
* Created by serenitynanian on 2017/5/26.
*/
public class DemoBottomSheetDialogFragment extends BottomSheetDialogFragment {
public static DemoBottomSheetDialogFragment newInstance() {
Bundle args = new Bundle();
DemoBottomSheetDialogFragment fragment = new DemoBottomSheetDialogFragment();
fragment.setArguments(args);
return fragment;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//填充自己的想要的布局
View view = inflater.inflate(R.layout.layout_bottom_sheet_linear, container, false);
return view;
}
}
彈出和隱藏的的實(shí)現(xiàn):
public class BottomDialogFragmentActivity extends AppCompatActivity {
private DemoBottomSheetDialogFragment demoBottomSheetDialogFragment ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bottom_dialog_fragment);
demoBottomSheetDialogFragment = DemoBottomSheetDialogFragment.newInstance();
//顯示dialogFragment
showBottomSheetDialogFragment();
}
public void showdialog(View view) {
showBottomSheetDialogFragment();
}
public void hidedialog(View view) {
//隱藏dialogFragment
hideBottomSheetDialogFragment();
}
/**
* 顯示BottomSheetDialogFragment
*/
private void hideBottomSheetDialogFragment() {
if (demoBottomSheetDialogFragment == null) {
demoBottomSheetDialogFragment.dismiss();
}
}
/**
* 顯示BottomSheetDialogFragment
*/
private void showBottomSheetDialogFragment() {
demoBottomSheetDialogFragment.show(getSupportFragmentManager(),"bottomSheetDialogFragment");
}
}
四 、總結(jié)
1.其實(shí)核心就是使用CoordinatorLayout+bottom_sheet_behavior來(lái)實(shí)現(xiàn)拖拽摸航;
2.使用BottomDialog時(shí)制跟,必須使用CoordinatorLayout作為其父布局,并且需要設(shè)置bottom_sheet_behavior;
3.BottomSheetDialog和BottomSheetDialogFragment酱虎,只需要我們提供一個(gè)底部菜單的布局雨膨,在它們內(nèi)部的實(shí)現(xiàn)當(dāng)中,它再把我們傳入的布局放入到CoordinatorLayout當(dāng)中读串,之后再把這一整個(gè)包裝好的布局作為Dialog的布局聊记。