前面兩章實現了篩選控件的內容,今天要來實現篩選控件的容器,首先看效果:php
點擊上面的按鈕能夠展開隱藏菜單,須要有動畫效果,重複點擊同一個按鈕能夠toggle,菜單展開時點擊另外的按鈕須要先收起菜單再展開菜單,點擊半透明蒙層能夠收起菜單。java
不少初級開發者可能看到這個效果會馬上想到用PopupWindow去實現,然而PopupWindow去實現有不少坑,例如點擊隱藏的控制、代碼書寫麻煩,處理生命週期狀態保存麻煩,和現有的代碼控件協調使用等等。android
網上也也有一些實現,例如 dongjunkun/DropDownMenu,閱讀源碼發現其代碼陳舊,封裝過於嚴密而缺少靈活性,不建議用於生產環境。git
按照kiss原則,咱們的實現方式應該是簡單的,咱們採用的實現方式是移動View的方式實現菜單展開隱藏的效果。動畫採用Animatorgithub
注意咱們不採用動態修改View的高度去實現隱藏展開而是修改TranlationY是有緣由的,若是動態修改高度會致使View的從新測量,像咱們的View內部可能包含RecyclerView從新測量,會帶來嚴重的性能問題docker
public class YokoView extends FrameLayout {
public interface YokoAdapter {
void onCollapsed(YokoView view);
void onExpanded(YokoView view);
}
private View mView;
private YokoAdapter mAdapter;
private boolean animating;
public YokoView(@NonNull Context context) {
this(context, null);
}
public YokoView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public YokoView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ViewDataBinding initMenuView(@LayoutRes int resId) {
removeAllViews();
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(getContext()),
resId, this, true);
mView = binding.getRoot();
mView.setClickable(true);
sync();
setOnClickListener(v -> {
if (!animating) {
toggle();
}
});
return binding;
}
public View getMenuView() {
return mView;
}
public void sync() {
setVisibility(isCollapsed() ? View.GONE : View.VISIBLE);
}
public void setApdater(YokoAdapter adapter) {
this.mAdapter = adapter;
}
private void performCallback(int type) {
if (mAdapter == null) {
return;
}
if (type == 0) {
mAdapter.onCollapsed(this);
}
if (type == 1) {
mAdapter.onExpanded(this);
}
}
public void collapse() {
getMenuView().animate()
.translationY(-getMenuView().getHeight())
.withStartAction(() -> {
animating = true;
})
.withEndAction(() -> {
animating = false;
performCallback(0);
setVisibility(View.GONE);
})
.start();
}
public boolean isCollapsed() {
return getMenuView().getTranslationY() <= -getMenuView().getHeight();
}
public boolean isExpanded() {
return getMenuView().getTranslationY() >= 0;
}
public void expand(@Nullable Runnable beforeExpand) {
getMenuView().animate()
.translationY(0)
.withStartAction(() -> {
animating = true;
setVisibility(View.VISIBLE);
if (beforeExpand != null) {
beforeExpand.run();
}
})
.withEndAction(() -> {
animating = false;
performCallback(1);
})
.start();
}
public void collapseThenExpand(@Nullable Runnable beforeExpand) {
if (isCollapsed()) {
expand(beforeExpand);
} else {
getMenuView().animate()
.translationY(-getMenuView().getHeight())
.withStartAction(() -> {
animating = true;
})
.withEndAction(() -> {
animating = false;
expand(beforeExpand);
})
.start();
}
}
public void toggle() {
if (isCollapsed()) {
expand(null);
} else {
collapse();
}
}
}
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable name="checkedId" type="androidx.databinding.ObservableInt" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".yoko.YokoTestActivity">
<TextView android:id="@+id/textView" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:background="@color/colorPrimary" android:gravity="center" android:text="被覆蓋的區域" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/toggle" />
<github.hotstu.lib.hof.yokohama.YokoView android:id="@+id/yokoView" android:layout_width="match_parent" android:layout_height="0dp" android:background="#77000000" android:translationZ="1dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/toggle" app:layout_goneMarginTop="200dp">
</github.hotstu.lib.hof.yokohama.YokoView>
<Button android:id="@+id/toggle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:background="@drawable/hof_s_btn_bg" android:onClick="onClick" android:text="欄目0" app:checked="@{checkedId}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
<Button android:id="@+id/cte" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:background="@drawable/hof_s_btn_bg" android:onClick="onClick" android:text="欄目1" app:checked="@{checkedId}" app:layout_constraintStart_toEndOf="@+id/toggle" app:layout_constraintTop_toTopOf="parent" />
<Button android:id="@+id/cte2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:background="@drawable/hof_s_btn_bg" android:onClick="onClick" android:text="欄目2" app:checked="@{checkedId}" app:layout_constraintStart_toEndOf="@+id/cte" app:layout_constraintTop_toTopOf="parent" />
<Button android:id="@+id/cte3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:background="@drawable/hof_s_btn_bg" android:onClick="onClick" android:text="欄目3" app:checked="@{checkedId}" app:layout_constraintStart_toEndOf="@+id/cte2" app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
複製代碼
@Route(path = "/app/yoko", name = "抽屜菜單")
public class YokoTestActivity extends AppCompatActivity {
private YokoView yokoView;
private ObservableInt currentSelect = new ObservableInt();
private ViewDataBinding mBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewDataBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_yoko_test);
binding.setVariable(BR.checkedId, currentSelect);
yokoView = findViewById(R.id.yokoView);
yokoView.setApdater(new YokoView.YokoAdapter() {
@Override
public void onCollapsed(YokoView view) {
Log.d("hof", "onCollapsed");
}
@Override
public void onExpanded(YokoView view) {
Log.d("hof", "onExpanded");
}
});
mBinding = yokoView.initMenuView(R.layout.include_yoko_container_layout);
}
@BindingAdapter("bind:checked")
public static void setChecked(View v, int checkedId) {
if (v.getId() == checkedId) {
v.setSelected(true);
} else {
v.setSelected(false);
}
}
public void onClick(View view) {
if (currentSelect.get() == view.getId()) {
yokoView.toggle();
return;
}
if (view.getId() == R.id.toggle) {
yokoView.collapseThenExpand(() -> {
mBinding.setVariable(BR.text, "欄目0內容");
});
}
if (view.getId() == R.id.cte) {
yokoView.collapseThenExpand(() -> {
mBinding.setVariable(BR.text, "欄目1內容");
});
}
if (view.getId() == R.id.cte2) {
yokoView.collapseThenExpand(() -> {
mBinding.setVariable(BR.text, "欄目2內容");
});
}
if (view.getId() == R.id.cte3) {
yokoView.collapseThenExpand(() -> {
mBinding.setVariable(BR.text, "欄目3內容");
});
}
currentSelect.set(view.getId());
}
}
複製代碼
githubapp
使用Databinding輕鬆快速打造仿攜程app篩選控件(一)maven
使用Databinding輕鬆快速打造仿攜程app篩選控件(二)ide
使用Databinding輕鬆快速打造仿攜程app篩選控件(三)佈局
Github | 簡書 | 掘金 | JCenter | dockerHub |
---|---|---|---|---|
Github | 簡書 | 掘金 | JCenter | dockerHub |