内容简介:前面两章实现了筛选控件的内容,今天要来实现筛选控件的容器,首先看效果:点击上面的按钮可以展开隐藏菜单,需要有动画效果,重复点击同一个按钮可以toggle,菜单展开时点击另外的按钮需要先收起菜单再展开菜单,点击半透明蒙层可以收起菜单。很多初级开发者可能看到这个效果会立刻想到用PopupWindow去实现,然而PopupWindow去实现有很多坑,例如点击隐藏的控制、代码书写麻烦,处理生命周期状态保存麻烦,和现有的代码控件协调使用等等。
前面两章实现了筛选控件的内容,今天要来实现筛选控件的容器,首先看效果:
需求分析
点击上面的按钮可以展开隐藏菜单,需要有动画效果,重复点击同一个按钮可以toggle,菜单展开时点击另外的按钮需要先收起菜单再展开菜单,点击半透明蒙层可以收起菜单。
实现思路
很多初级开发者可能看到这个效果会立刻想到用PopupWindow去实现,然而PopupWindow去实现有很多坑,例如点击隐藏的控制、代码书写麻烦,处理生命周期状态保存麻烦,和现有的代码控件协调使用等等。
网上也也有一些实现,例如 dongjunkun / DropDownMenu ,阅读源码发现其代码陈旧,封装过于严密而缺乏灵活性,不建议用于生产环境。
按照kiss原则,我们的实现方式应该是简单的,我们采用的实现方式是移动View的方式实现菜单展开隐藏的效果。动画采用Animator
注意我们不采用动态修改View的高度去实现隐藏展开而是修改TranlationY是有原因的,如果动态修改高度会导致View的重新测量,像我们的View内部可能包含RecyclerView重新测量,会带来严重的性能问题
实现
- 首先定义一个自定义的ViewGroup,这个ViewGroup的背景色为灰色透明,相当于一个蒙层,他的子view我们通过DataBinding在调用的时候动态添加,这样保证了最大的灵活性:要呈现的内容完全开放给调用方,我们只负责展示和隐藏。
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()); } } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- JS中的数组过滤,从简单筛选到多条件筛选
- 记一次筛选重构
- python素数筛选法浅析
- python如何在列表、字典中筛选数据
- iOS – tableView类型的筛选框实现
- 深度学习在封面图筛选中的应用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。