From dc55d3fde59141bc0ff75cff2a8abd52ac432aaf Mon Sep 17 00:00:00 2001 From: lvmeng <848755140@qq.com> Date: Thu, 30 Apr 2026 10:34:44 +0800 Subject: [PATCH] =?UTF-8?q?refactor(swipe):=20=E8=BF=81=E7=A7=BB=E4=BE=A7?= =?UTF-8?q?=E6=BB=91=E5=88=A0=E9=99=A4=E8=87=B3=20SwipeRecyclerView?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E9=99=A4=20SwipeRevealLayout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 SwipeRecyclerView.aar 及使用说明文档 - 删除 SwipeRevealLayout.java / ViewBinderHelper.java - DishListFragment、SamplingModeActivity、PrepareFoodActivity 改用 setSwipeMenuCreator API - FoodListAdapter、DishPartAdapter 移除 ViewBinderHelper 侧滑状态管理代码 - 布局文件将 RecyclerView 替换为 SwipeRecyclerView,item 布局移除 SwipeRevealLayout 根节点 --- SwipeRecyclerView.md | 356 ++++++ app/libs/SwipeRecyclerView.aar | Bin 0 -> 66439 bytes .../dish/match/adapter/DishPartAdapter.kt | 32 +- .../dish/match/adapter/FoodListAdapter.kt | 56 +- .../dish/match/ui/PrepareFoodActivity.kt | 70 +- .../dish/match/ui/SamplingModeActivity.kt | 79 +- .../match/ui/fragment/DishListFragment.kt | 102 +- .../swipereveallayout/SwipeRevealLayout.java | 1133 ----------------- .../swipereveallayout/ViewBinderHelper.java | 271 ---- .../main/res/layout/activity_prepare_food.xml | 6 +- .../res/layout/activity_sampling_mode.xml | 2 +- .../main/res/layout/fragment_dish_list.xml | 2 +- .../main/res/layout/list_item_dish_cook.xml | 167 ++- .../main/res/layout/list_item_food_list.xml | 124 +- 14 files changed, 643 insertions(+), 1757 deletions(-) create mode 100644 SwipeRecyclerView.md create mode 100644 app/libs/SwipeRecyclerView.aar delete mode 100644 app/src/main/java/com/shuwei/dish/match/view/swipereveallayout/SwipeRevealLayout.java delete mode 100644 app/src/main/java/com/shuwei/dish/match/view/swipereveallayout/ViewBinderHelper.java diff --git a/SwipeRecyclerView.md b/SwipeRecyclerView.md new file mode 100644 index 0000000..cea2b4b --- /dev/null +++ b/SwipeRecyclerView.md @@ -0,0 +1,356 @@ +# SwipeRecyclerView + +作者的主页:[https://www.yanzhenjie.com](https://www.yanzhenjie.com) +技术交流群:[46505645](https://jq.qq.com/?_wv=1027&k=5wY8UWl) + +---- + +本库是基于RecyclerView的封装,提供了Item侧滑菜单、Item滑动删除、Item长按拖拽、添加HeaderView/FooterView、加载更多、Item点击监听等基本功能。 + +## 特性 +1. Item侧滑菜单,支持水平分布、垂直分布 +2. Item长按拖拽、侧滑删除 +3. 添加/移除HeaderView/FooterView +4. **自动/点击**加载更多的功能 +5. 支持二级列表,List形式、Grid形式、Staggered形式 +6. Sticky普通布局黏贴和ReyclerView分组黏贴 +7. 支持AndroidX + +> 使用本库只需要使用SwipeRecyclerView即可,用法和原生RecyclerView一模一样,本库比原生的RecyclerView多了几个扩展方法。 + +## 截图 +对上面提到的效果基本都有演示,但不是全部,更多效果可以下载Demo查看。 + +### Item侧滑菜单 + + +### Item侧滑删除、拖拽 + + +### 下拉刷新和加载更多 + + +### HeaderView和FooterView + + +### Sticky效果和Item分组 + + +### 和DrawerLayout嵌套 + + +## 如何使用 +如果你使用的是android support库,那么请添加下述依赖: +```groovy +implementation 'com.yanzhenjie.recyclerview:support:1.3.2' +``` + +如果你使用的是android x库,那么请添加下述依赖: +```groovy +implementation 'com.yanzhenjie.recyclerview:x:1.3.2' +``` + +> **1. SwipeRecyclerView从1.3.0版本开始支持AndroidX和二级列表,因此相对于低版本的包名和类名有所改动,从低版本升级的开发者需要考量是否要升级。** +**2. 为了让开发者方便切换support库和x库,SwipeRecyclerView的support库和x库除了依赖时的名称不一样外,包名、控件名和类名都是一样的,因此两个库不能共存。** + +### 加入布局 +在布局的xml中加入`SwipeRecyclerView`: +```xml + +``` + +### ItemDecoration +也就是分割线,支持Grid形式和Linear形式,可以选择某个ViewType不画分割线: +```java +// 默认构造,传入颜色即可。 +ItemDecoration itemDecoration = new DefaultDecoration(color); + +// 或者:颜色,宽,高,最后一个参数是不画分割线的ViewType,可以传入多个。 +itemDecoration = new DefaultDecoration(color, width, height, excludeViewType); + +// 或者:例如下面的123都是不画分割线的ViewType: +itemDecoration = new DefaultDecoration(color, width, height, 1, 2, 3); + +SwipeRecyclerView recyclerView = ...; +recyclerView.setDecoration(itemDecoration); +``` + +### Item点击监听 +```java +recyclerView.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + // TODO... + } +}); +``` + +### 侧滑菜单 +```java +// 设置监听器。 +swipeRecyclerView.setSwipeMenuCreator(mSwipeMenuCreator); + +// 创建菜单: +SwipeMenuCreator mSwipeMenuCreator = new SwipeMenuCreator() { + @Override + public void onCreateMenu(SwipeMenu leftMenu, SwipeMenu rightMenu, int position) { + SwipeMenuItem deleteItem = new SwipeMenuItem(mContext) + ...; // 各种文字和图标属性设置。 + leftMenu.addMenuItem(deleteItem); // 在Item左侧添加一个菜单。 + + SwipeMenuItem deleteItem = new SwipeMenuItem(mContext) + ...; // 各种文字和图标属性设置。 + leftMenu.addMenuItem(deleteItem); // 在Item右侧添加一个菜单。 + + // 注意:哪边不想要菜单,那么不要添加即可。 + } +}; + +// 菜单点击监听。 +swipeRecyclerView.setOnItemMenuClickListener(mItemMenuClickListener); + +OnItemMenuClickListener mItemMenuClickListener = new OnItemMenuClickListener() { + @Override + public void onItemClick(SwipeMenuBridge menuBridge, int position) { + // 任何操作必须先关闭菜单,否则可能出现Item菜单打开状态错乱。 + menuBridge.closeMenu(); + + // 左侧还是右侧菜单: + int direction = menuBridge.getDirection(); + // 菜单在Item中的Position: + int menuPosition = menuBridge.getPosition(); + } +}; +``` + +**注意**:菜单需要设置高度,关于菜单高度: +1. `MATCH_PARENT`,自动适应Item高度,保持和Item一样高,比较推荐; +2. 指定具体的高,比如80; +3. `WRAP_CONTENT`,自身高度,极不推荐; + +### 侧滑删除和拖拽 +拖拽和侧滑删除的功能默认关闭的,所以先要打开功能: +```java +recyclerView.setLongPressDragEnabled(true); // 拖拽排序,默认关闭。 +recyclerView.setItemViewSwipeEnabled(true); // 侧滑删除,默认关闭。 +``` + +只需要设置上面两个属性就可以进行相应的动作了,如果不需要哪个,不要打开就可以了。 + +然后监听拖拽和侧滑的动作,进行数据更新: +```java +recyclerView.setOnItemMoveListener(mItemMoveListener);// 监听拖拽,更新UI。 + +OnItemMoveListener mItemMoveListener = new OnItemMoveListener() { + @Override + public boolean onItemMove(ViewHolder srcHolder, ViewHolder targetHolder) { + // 此方法在Item拖拽交换位置时被调用。 + // 第一个参数是要交换为之的Item,第二个是目标位置的Item。 + + // 交换数据,并更新adapter。 + int fromPosition = srcHolder.getAdapterPosition(); + int toPosition = targetHolder.getAdapterPosition(); + Collections.swap(mDataList, fromPosition, toPosition); + adapter.notifyItemMoved(fromPosition, toPosition); + + // 返回true,表示数据交换成功,ItemView可以交换位置。 + return true; + } + + @Override + public void onItemDismiss(ViewHolder srcHolder) { + // 此方法在Item在侧滑删除时被调用。 + + // 从数据源移除该Item对应的数据,并刷新Adapter。 + int position = srcHolder.getAdapterPosition(); + mDataList.remove(position); + adapter.notifyItemRemoved(position); + } +}; +``` + +**特别注意**:如果`LayoutManager`是`List`形式,那么Item拖拽时只能从1-2-3-4这样走,如果你的`LayoutManager`是`Grid`形式的,那么Item可以从1直接到3或者5或者6...,这样数据就会错乱,所以**当`LayoutManager`是Grid形式时**这里要特别注意转换数据位置的算法: +```java +@Override +public boolean onItemMove(ViewHolder srcHolder, ViewHolder targetHolder) { + int fromPosition = srcHolder.getAdapterPosition(); + int toPosition = targetHolder.getAdapterPosition(); + if (fromPosition < toPosition) { + for (int i = fromPosition; i < toPosition; i++) { + Collections.swap(mDataList, i, i + 1); + } + } else { + for (int i = fromPosition; i > toPosition; i--) { + Collections.swap(mDataList, i, i - 1); + } + } + + mMenuAdapter.notifyItemMoved(fromPosition, toPosition); + return true; +} +``` + +我们还可以监听用户的侧滑删除和拖拽Item时的手指状态: +```java +recyclerView.setOnItemStateChangedListener(mStateChangedListener); + +... + +private OnItemStateChangedListener mStateChangedListener = (viewHolder, actionState) -> { + if (actionState == OnItemStateChangedListener.ACTION_STATE_DRAG) { + // 状态:正在拖拽。 + } else if (actionState == OnItemStateChangedListener.ACTION_STATE_SWIPE) { + // 状态:滑动删除。 + } else if (actionState == OnItemStateChangedListener.ACTION_STATE_IDLE) { + // 状态:手指松开。 + } +}; +``` + +想用户触摸到某个`Item`时就开始拖拽或者侧滑删除时,只需要调用`startDrag()`和`startSwipe()`并转入当前`Item`的`ViewHoler`即可。 + +触摸拖拽: +```java +swipeRecyclerView.startDrag(ViewHolder); +``` + +触摸侧滑删除: +```java +swipeRecyclerView.startSwipe(ViewHolder); +``` + +### HeaderView和FooterView +主要方法: +```java +addHeaderView(View); // 添加HeaderView。 +removeHeaderView(View); // 移除HeaderView。 +addFooterView(View); // 添加FooterView。 +removeFooterView(View); // 移除FooterView。 +getHeaderItemCount(); // 获取HeaderView个数。 +getFooterItemCount(); // 获取FooterView个数。 +getItemViewType(int); // 获取Item的ViewType,包括HeaderView、FooterView、普通ItemView。 +``` +添加/移除`HeaderView`/`FooterView`和`setAdapter()`的调用不分先后顺序。 + +**特别注意**: +1. 如果添加了`HeaderView`,凡是通过`ViewHolder`拿到的`position`都要减掉`HeaderView`的数量才能得到正确的`position`。 + +### 加载更多 +本库默认提供了加载更多的动画和View,开发者也可以自定义,默认支持`RecyclerView`自带的三种布局管理器。 + +默认加载更多: +```java +RecyclerView recyclerView = ...; +... + +recyclerView.useDefaultLoadMore(); // 使用默认的加载更多的View。 +recyclerView.setLoadMoreListener(mLoadMoreListener); // 加载更多的监听。 + +LoadMoreListener mLoadMoreListener = new LoadMoreListener() { + @Override + public void onLoadMore() { + // 该加载更多啦。 + + ... // 请求数据,并更新数据源操作。 + mMainAdapter.notifyDataSetChanged(); + + // 数据完更多数据,一定要调用这个方法。 + // 第一个参数:表示此次数据是否为空。 + // 第二个参数:表示是否还有更多数据。 + mRecyclerView.loadMoreFinish(false, true); + + // 如果加载失败调用下面的方法,传入errorCode和errorMessage。 + // errorCode随便传,你自定义LoadMoreView时可以根据errorCode判断错误类型。 + // errorMessage是会显示到loadMoreView上的,用户可以看到。 + // mRecyclerView.loadMoreError(0, "请求网络失败"); + } +}; +``` + +自定义加载更多View也很简单,自定义一个View,并实现一个接口即可: +```java +public class DefineLoadMoreView extends LinearLayout + implements SwipeRecyclerView.LoadMoreView, + View.OnClickListener { + + private LoadMoreListener mLoadMoreListener; + + public DefineLoadMoreView(Context context) { + super(context); + ... + setOnClickListener(this); + } + + /** + * 马上开始回调加载更多了,这里应该显示进度条。 + */ + @Override + public void onLoading() { + // 展示加载更多的动画和提示信息。 + ... + } + + /** + * 加载更多完成了。 + * + * @param dataEmpty 是否请求到空数据。 + * @param hasMore 是否还有更多数据等待请求。 + */ + @Override + public void onLoadFinish(boolean dataEmpty, boolean hasMore) { + // 根据参数,显示没有数据的提示、没有更多数据的提示。 + // 如果都不存在,则都不用显示。 + } + + /** + * 加载出错啦,下面的错误码和错误信息二选一。 + * + * @param errorCode 错误码。 + * @param errorMessage 错误信息。 + */ + @Override + public void onLoadError(int errorCode, String errorMessage) { + } + + /** + * 调用了setAutoLoadMore(false)后,在需要加载更多的时候,此方法被调用,并传入listener。 + */ + @Override + public void onWaitToLoadMore(SwipeRecyclerView.LoadMoreListener loadMoreListener) { + this.mLoadMoreListener = loadMoreListener; + } + + /** + * 非自动加载更多时mLoadMoreListener才不为空。 + */ + @Override + public void onClick(View v) { + if (mLoadMoreListener != null) mLoadMoreListener.onLoadMore(); + } +} +``` + +## 感谢与参考 +* [cube-sdk](https://github.com/liaohuqiu/cube-sdk) +* [SwipeMenu](https://github.com/TUBB/SwipeMenu/) +* [HeaderAndFooterWrapper](https://github.com/hongyangAndroid/baseAdapter/blob/master/baseadapter-recyclerview/src/main/java/com/zhy/adapter/recyclerview/wrapper/HeaderAndFooterWrapper.java) + +加载更多的灵感来自`cube-sdk`,侧滑菜单参考了`SwipeMenu`,添加`HeaderView`参考了`HeaderAndFooterWrapper`类,特别感谢上述开源库及其作者。 + +## License +```text +Copyright 2019 Zhenjie Yan + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` \ No newline at end of file diff --git a/app/libs/SwipeRecyclerView.aar b/app/libs/SwipeRecyclerView.aar new file mode 100644 index 0000000000000000000000000000000000000000..4f8f0a7f1fd639d6e0be871368bf5e7b627bc959 GIT binary patch literal 66439 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 I`yW^T2WGV) : BaseQuickAdapter(list) { - var onDeleteClick: ((position: Int) -> Unit)? = null + /** item 主体点击回调 */ var onItemClick: ((position: Int) -> Unit)? = null - private val viewBinderHelper = ViewBinderHelper().apply { - setOpenOnlyOne(true) - } - - fun closeAll() { - viewBinderHelper.closeAll() - } - inner class VH(var binding: ListItemDishCookBinding) : QuickViewHolder(binding.root) override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH { @@ -39,16 +30,7 @@ class DishPartAdapter(list: MutableList) : override fun onBindViewHolder(holder: VH, position: Int, item: CookFoodGoodsEntity?) { holder.binding.run { val data = item ?: return - val itemId = data.goodsId ?: "${position}_${data.goodsName ?: ""}" - viewBinderHelper.bind(swipeRevealLayout, itemId) - // 暂时禁用侧滑能力,后续需要放开时改回 unlockSwipe + layoutDelete.visible() - viewBinderHelper.lockSwipe(itemId) - swipeRevealLayout.close(false) - layoutDelete.gone() - layoutDelete.setOnClickListener { - onDeleteClick?.invoke(holder.bindingAdapterPosition) - } clBlock.setOnClickListener { onItemClick?.invoke(holder.bindingAdapterPosition) } @@ -76,12 +58,10 @@ class DishPartAdapter(list: MutableList) : if (data.isSetFinished) R.drawable.ic_dish_selected else R.drawable.ic_dish_unselected ) - clBlock.run { - setBackgroundResource( - if (data.isItemClicked) R.drawable.shape_item_cook_dish - else R.drawable.shape_white_fb_15_corners - ) - } + clBlock.setBackgroundResource( + if (data.isItemClicked) R.drawable.shape_item_cook_dish + else R.drawable.shape_white_fb_15_corners + ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/shuwei/dish/match/adapter/FoodListAdapter.kt b/app/src/main/java/com/shuwei/dish/match/adapter/FoodListAdapter.kt index e811a15..e2126d0 100644 --- a/app/src/main/java/com/shuwei/dish/match/adapter/FoodListAdapter.kt +++ b/app/src/main/java/com/shuwei/dish/match/adapter/FoodListAdapter.kt @@ -9,7 +9,6 @@ import com.chad.library.adapter4.viewholder.QuickViewHolder import com.shuwei.dish.match.R import com.shuwei.dish.match.databinding.ListItemFoodListBinding import com.shuwei.dish.match.entity.FoodRecord -import com.shuwei.dish.match.view.swipereveallayout.ViewBinderHelper import com.shuwei.dish.match.utils.ext.gone import com.shuwei.dish.match.utils.ext.visible import java.text.DecimalFormat @@ -35,35 +34,16 @@ class FoodListAdapter( SAMPLING_MODE } - /** 删除按钮点击回调,由外部(Activity/Fragment)设置 */ - var onDeleteClick: ((position: Int) -> Unit)? = null - - /** - * item 主体点击回调,绑定在 mainView(clBlock)上,绕过 SwipeRevealLayout 的触摸拦截。 - * 外部使用此回调替代 setOnDebouncedItemClick。 - */ + /** item 主体点击回调 */ var onItemClick: ((position: Int) -> Unit)? = null - /** - * ViewBinderHelper 负责: - * - 同一时间只允许一个 item 展开(setOpenOnlyOne) - * - RecyclerView 复用时恢复开合状态 - */ - private val viewBinderHelper = ViewBinderHelper().apply { - setOpenOnlyOne(true) - } - - /** 关闭所有已展开的侧滑 item,供外部在点击空白区域时调用 */ - fun closeAll() { - viewBinderHelper.closeAll() - } - inner class VH(var binding: ListItemFoodListBinding) : QuickViewHolder(binding.root) override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH { val binding = ListItemFoodListBinding.inflate(LayoutInflater.from(context), parent, false) return VH(binding) } + /** * - 烹饪中:黑色文字、实线背景、显示"烹饪中" * - 有累计重量:绿色文字、实线背景、显示累计统计 @@ -75,36 +55,12 @@ class FoodListAdapter( val totalWeight = item.totalWeight ?: 0.0 val foodWeight = item.foodWeight ?: 0.0 - // 用 foodId 作为唯一 key 恢复开合状态;不可滑动时锁定 - val itemId = item.foodId ?: position.toString() - viewBinderHelper.bind(swipeRevealLayout, itemId) - if (isCooking) { - viewBinderHelper.unlockSwipe(itemId) - layoutDelete.visible() - } else { - viewBinderHelper.lockSwipe(itemId) - // 非烹饪中不允许侧滑,关闭可能的复用残留状态并隐藏删除按钮 - swipeRevealLayout.close(false) - layoutDelete.gone() - } - // 绑定删除按钮回调 - layoutDelete.setOnClickListener { onDeleteClick?.invoke(holder.bindingAdapterPosition) } - // 点击主内容区域: - // 1) 非烹饪中:先收起所有侧滑,再继续触发点击 - // 2) 烹饪中:当前 item 若已展开则只收起,不触发点击 clBlock.setOnClickListener { - if (!isCooking) { - viewBinderHelper.closeAll() - onItemClick?.invoke(holder.bindingAdapterPosition) - } else if (!swipeRevealLayout.isClosed) { - viewBinderHelper.closeAll() - } else { - onItemClick?.invoke(holder.bindingAdapterPosition) - } + onItemClick?.invoke(holder.bindingAdapterPosition) } tvDishName.text = item.foodName - //烹饪中 + // 烹饪中 if (isCooking) { tvDishName.setTextColor(ContextCompat.getColor(holder.itemView.context, R.color.black)) clBlock.setBackgroundResource(R.drawable.shape_white_fb_15_corners) @@ -113,7 +69,7 @@ class FoodListAdapter( tvShowState.text = "烹饪中" return@run } - //非烹饪中,制作模式 + // 非烹饪中,制作模式 if (mode == DisplayMode.COOKING_MODE) { if (totalWeight > 0.0) { tvDishName.setTextColor(ContextCompat.getColor(holder.itemView.context, R.color.dish_green)) @@ -129,7 +85,7 @@ class FoodListAdapter( } return@run } - //非烹饪中,采样模式 + // 非烹饪中,采样模式 val realTotalWeight = if (totalWeight > 0.0) totalWeight else foodWeight val showTotalWeight = if (realTotalWeight == 0.0) "-" else df.format(realTotalWeight / 1000.0F) tvDishCount.text = "累计统计:${showTotalWeight}kg(${item.count ?: "-"}次)" diff --git a/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt index 2140939..6562272 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt @@ -36,6 +36,7 @@ import com.shuwei.dish.match.utils.ext.startActivity import com.shuwei.dish.match.utils.ext.toJsonString import com.shuwei.dish.match.utils.ext.toast import com.shuwei.dish.match.utils.ext.visible +import com.yanzhenjie.recyclerview.SwipeMenuItem import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -359,22 +360,6 @@ class PrepareFoodActivity : BaseActivity() { } notifyDataSetChanged() } - onDeleteClick = onDeleteClick@{ positon -> - Log.d(TAG, "onFoodItemClick: ${list[positon].toJsonString()}") - if (list[positon].isNewDishType.not()) { - toast("只能删除刚添加的食材") - return@onDeleteClick - } - CommonDialog(this@PrepareFoodActivity) - .setTitle("删除确认") - .setContent("确定删除食材「${list[positon].goodsName}」吗?") - .setNegativeButton("取消") - .setPositiveButton("删除") { - adapter.removeAt(positon) - toast("已删除") - } - .show() - } addOnItemChildClickListener(R.id.ivClearIcon) { _, _, positon -> Log.d(TAG, "onFoodItemClick: ${list[positon].toJsonString()}") if (list[positon].isOriginalData) { @@ -398,21 +383,52 @@ class PrepareFoodActivity : BaseActivity() { binding.rvDishPartList.run { layoutManager = LinearLayoutManager(this@PrepareFoodActivity, LinearLayoutManager.VERTICAL, false) - adapter = dishPartAdapter - addOnItemTouchListener(object : androidx.recyclerview.widget.RecyclerView.SimpleOnItemTouchListener() { - override fun onInterceptTouchEvent( - rv: androidx.recyclerview.widget.RecyclerView, - e: android.view.MotionEvent - ): Boolean { - if (e.action == android.view.MotionEvent.ACTION_DOWN) { - dishPartAdapter.closeAll() - } - return false + // 仅 isNewDishType=true 的 item 显示侧滑删除菜单(必须在 setAdapter 之前调用) + setSwipeMenuCreator { _, rightMenu, position -> + if (list.getOrNull(position)?.isNewDishType == true) { + rightMenu.addMenuItem(buildDeleteMenuItem()) } - }) + } + // 点击侧滑菜单项:先关闭菜单,再弹窗确认删除 + setOnItemMenuClickListener { menuBridge, position -> + menuBridge.closeMenu() + val item = list.getOrNull(position) ?: return@setOnItemMenuClickListener + CommonDialog(this@PrepareFoodActivity) + .setTitle("删除确认") + .setContent("确定删除食材「${item.goodsName}」吗?") + .setNegativeButton("取消") + .setPositiveButton("删除") { + dishPartAdapter.removeAt(position) + toast("已删除") + }.show() + } + // item 点击事件 + setOnItemClickListener { _, position -> + dishPartAdapter.onItemClick?.invoke(position) + } + adapter = dishPartAdapter } } + /** + * 构建侧滑删除菜单项 + */ + private fun buildDeleteMenuItem(): SwipeMenuItem { + return SwipeMenuItem(this).apply { + setImage(R.drawable.ic_trash_white) + setBackground(R.drawable.bg_swipe_delete) + width = dp2px(160) + height = ViewGroup.LayoutParams.MATCH_PARENT + } + } + + /** + * dp 转 px + */ + private fun dp2px(dp: Int): Int { + return (dp * resources.displayMetrics.density + 0.5f).toInt() + } + override fun onResume() { super.onResume() pageVisible = true diff --git a/app/src/main/java/com/shuwei/dish/match/ui/SamplingModeActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/SamplingModeActivity.kt index ef5074b..06bf4ab 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/SamplingModeActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/SamplingModeActivity.kt @@ -4,7 +4,7 @@ import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import android.view.LayoutInflater -import android.view.MotionEvent +import android.view.ViewGroup import androidx.activity.addCallback import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope @@ -18,14 +18,15 @@ import com.shuwei.dish.match.databinding.ActivitySamplingModeBinding import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding import com.shuwei.dish.match.dialog.CommonDialog import com.shuwei.dish.match.entity.FoodRecord +import com.shuwei.dish.match.net.UiState import com.shuwei.dish.match.utils.DateTimeUtil import com.shuwei.dish.match.utils.ext.gone import com.shuwei.dish.match.utils.ext.startActivity import com.shuwei.dish.match.utils.ext.toast import com.shuwei.dish.match.utils.ext.visible +import com.yanzhenjie.recyclerview.SwipeMenuItem import java.io.Serializable import kotlinx.coroutines.launch -import com.shuwei.dish.match.net.UiState /** * 采样模式 */ @@ -161,23 +162,6 @@ class SamplingModeActivity : BaseActivity() { private val dishAdapter by lazy { FoodListAdapter(list = list, mode = FoodListAdapter.DisplayMode.SAMPLING_MODE).apply { isStateViewEnable = true - onDeleteClick = { position -> - CommonDialog(this@SamplingModeActivity) - .setTitle("删除确认") - .setContent("确定要删除「${list[position].foodName}」吗?") - .setNegativeButton("取消") - .setPositiveButton("删除") { - deleteCookFoodAndGoods(foodId = list[position].foodId ?: "") { - if (list.size > 1) { - removeAt(position) - } else { - loadEmptyView() - } - toast("已删除") - } - } - .show() - } onItemClick = { position -> judgeDeviceConfig { onItemClick(position) @@ -207,19 +191,58 @@ class SamplingModeActivity : BaseActivity() { binding.rvSamplingList.run { layoutManager = LinearLayoutManager(this@SamplingModeActivity, LinearLayoutManager.VERTICAL, false) - adapter = dishAdapter - // 点击列表空白区域时关闭已展开的侧滑 item - addOnItemTouchListener(object : androidx.recyclerview.widget.RecyclerView.SimpleOnItemTouchListener() { - override fun onInterceptTouchEvent(rv: androidx.recyclerview.widget.RecyclerView, e: MotionEvent): Boolean { - if (e.action == MotionEvent.ACTION_DOWN) { - dishAdapter.closeAll() - } - return false + // 根据 isCooking 决定是否显示侧滑删除菜单(必须在 setAdapter 之前调用) + setSwipeMenuCreator { _, rightMenu, position -> + if (list.getOrNull(position)?.isCooking == true) { + rightMenu.addMenuItem(buildDeleteMenuItem()) } - }) + } + // 点击侧滑菜单项:先关闭菜单,再弹窗确认删除 + setOnItemMenuClickListener { menuBridge, position -> + menuBridge.closeMenu() + val item = list.getOrNull(position) ?: return@setOnItemMenuClickListener + CommonDialog(this@SamplingModeActivity) + .setTitle("删除确认") + .setContent("确定要删除「${item.foodName}」吗?") + .setNegativeButton("取消") + .setPositiveButton("删除") { + deleteCookFoodAndGoods(foodId = item.foodId ?: "") { + if (list.size > 1) { + dishAdapter.removeAt(position) + } else { + loadEmptyView() + } + toast("已删除") + } + }.show() + } + // item 点击事件 + setOnItemClickListener { _, position -> + judgeDeviceConfig { onItemClick(position) } + } + adapter = dishAdapter } } + /** + * 构建侧滑删除菜单项 + */ + private fun buildDeleteMenuItem(): SwipeMenuItem { + return SwipeMenuItem(this).apply { + setImage(R.drawable.ic_trash_white) + setBackground(R.drawable.bg_swipe_delete) + width = dp2px(160) + height = ViewGroup.LayoutParams.MATCH_PARENT + } + } + + /** + * dp 转 px + */ + private fun dp2px(dp: Int): Int { + return (dp * resources.displayMetrics.density + 0.5f).toInt() + } + @SuppressLint("ClickableViewAccessibility") private fun addViewListener() { binding.btnAddSampling.setOnClickListener { diff --git a/app/src/main/java/com/shuwei/dish/match/ui/fragment/DishListFragment.kt b/app/src/main/java/com/shuwei/dish/match/ui/fragment/DishListFragment.kt index 19b6a9b..89c58a2 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/fragment/DishListFragment.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/fragment/DishListFragment.kt @@ -1,28 +1,30 @@ package com.shuwei.dish.match.ui.fragment import android.annotation.SuppressLint +import android.content.Context import android.os.Bundle import android.view.LayoutInflater -import android.view.MotionEvent import android.view.ViewGroup import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager +import com.shuwei.dish.match.R import com.shuwei.dish.match.adapter.FoodListAdapter import com.shuwei.dish.match.base.BaseApp import com.shuwei.dish.match.base.BaseFragment import com.shuwei.dish.match.databinding.FragmentDishListBinding import com.shuwei.dish.match.databinding.LayoutEmptyViewBinding +import com.shuwei.dish.match.dialog.CommonDialog import com.shuwei.dish.match.entity.CookFoodEntity import com.shuwei.dish.match.entity.FoodRecord -import com.shuwei.dish.match.dialog.CommonDialog import com.shuwei.dish.match.net.UiState -import com.shuwei.dish.match.ui.PrepareFoodActivity import com.shuwei.dish.match.ui.CookingModeActivity +import com.shuwei.dish.match.ui.PrepareFoodActivity import com.shuwei.dish.match.ui.SubmitFoodActivity import com.shuwei.dish.match.utils.ext.startActivity import com.shuwei.dish.match.utils.ext.toast +import com.yanzhenjie.recyclerview.SwipeMenuItem import java.io.Serializable import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -53,20 +55,6 @@ class DishListFragment : BaseFragment() { private val dishAdapter by lazy { FoodListAdapter(list = list).apply { isStateViewEnable = true - onDeleteClick = { position -> - CommonDialog(activity) - .setTitle("删除确认") - .setContent("确定要删除「${list[position].foodName}」吗?") - .setNegativeButton("取消") - .setPositiveButton("删除") { - activity.deleteCookFoodAndGoods(foodId = list[position].foodId ?: "") { - activity.toast("已删除") - pageNo = 1 - getDishList() - } - } - .show() - } onItemClick = { position -> if (isAdded && isVisible) { activity.judgeDeviceConfig { @@ -80,21 +68,15 @@ class DishListFragment : BaseFragment() { private fun onItemClick(position: Int) { val item = list[position] if (item.isCooking) { - //烹饪中,跳到称熟重页面 + // 烹饪中,跳到称熟重页面 activity.startActivity { - putExtra( - SubmitFoodActivity.FOOD_ITEM, - item as Serializable - ) + putExtra(SubmitFoodActivity.FOOD_ITEM, item as Serializable) } return } item.dinnerType = dinnerType activity.startActivity { - putExtra( - SubmitFoodActivity.FOOD_ITEM, - item as Serializable - ) + putExtra(SubmitFoodActivity.FOOD_ITEM, item as Serializable) } } @@ -109,18 +91,37 @@ class DishListFragment : BaseFragment() { activity = requireActivity() as CookingModeActivity dinnerType = arguments?.getString(DINNER_TYPE, "0") ?: "0" binding.rvDishList.run { - layoutManager = - LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) - adapter = dishAdapter - // 点击列表空白区域时关闭已展开的侧滑 item - addOnItemTouchListener(object : androidx.recyclerview.widget.RecyclerView.SimpleOnItemTouchListener() { - override fun onInterceptTouchEvent(rv: androidx.recyclerview.widget.RecyclerView, e: MotionEvent): Boolean { - if (e.action == MotionEvent.ACTION_DOWN) { - dishAdapter.closeAll() - } - return false + layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) + // 侧滑菜单:仅烹饪中的 item 显示删除按钮(必须在 setAdapter 之前调用) + setSwipeMenuCreator { _, rightMenu, position -> + if (list.getOrNull(position)?.isCooking == true) { + rightMenu.addMenuItem(buildDeleteMenuItem(requireContext())) } - }) + } + // 菜单点击:先关闭菜单,再弹确认弹窗 + setOnItemMenuClickListener { menuBridge, position -> + menuBridge.closeMenu() + val item = list.getOrNull(position) ?: return@setOnItemMenuClickListener + CommonDialog(activity) + .setTitle("删除确认") + .setContent("确定要删除「${item.foodName}」吗?") + .setNegativeButton("取消") + .setPositiveButton("删除") { + activity.deleteCookFoodAndGoods(foodId = item.foodId ?: "") { + activity.toast("已删除") + pageNo = 1 + getDishList() + } + } + .show() + } + // item 点击 + setOnItemClickListener { _, position -> + if (isAdded && isVisible) { + activity.judgeDeviceConfig { onItemClick(position) } + } + } + adapter = dishAdapter } addViewListener() initObserver() @@ -131,6 +132,22 @@ class DishListFragment : BaseFragment() { binding.refreshLayout.setEnableRefresh(true) } + /** + * 构建统一样式的删除菜单项 + */ + private fun buildDeleteMenuItem(context: Context): SwipeMenuItem { + return SwipeMenuItem(context).apply { + setImage(R.drawable.ic_trash_white) + setBackground(R.drawable.bg_swipe_delete) + width = dp2px(context, 160) + height = ViewGroup.LayoutParams.MATCH_PARENT + } + } + + private fun dp2px(context: Context, dp: Int): Int { + return (dp * context.resources.displayMetrics.density + 0.5f).toInt() + } + /** * 用 combine 同时监听网络结果与本地数据,网络成功时统一合并渲染 * pageNo > 1(加载更多)时 cookFoodListState 不参与合并,直接追加网络数据 @@ -156,7 +173,6 @@ class DishListFragment : BaseFragment() { finishRefresh() activity.delayDismissLoading() if (pageNo == 1) { - // 网络异常或接口不通时,降级使用本地数据;本地也为空才显示空视图 if (localList.isNullOrEmpty()) { loadEmptyView() } else { @@ -209,7 +225,6 @@ class DishListFragment : BaseFragment() { "placeId" to BaseApp.canteenId, "dinnerType" to getDinnerTypeText() ) - // 网络与本地并行触发,pageNo > 1 时不重复查询本地数据 activity.getFoodList(param = param) if (pageNo == 1) activity.getCookFoodList() } @@ -233,7 +248,6 @@ class DishListFragment : BaseFragment() { emptyViewBinding!!.tvContent.text = "暂无数据" emptyViewBinding!!.tvSubContent.text = "获取菜品数据失败,请检查网络设置或稍后重试" dishAdapter.stateView = emptyViewBinding!!.root - //dishAdapter.setStateViewLayout(requireContext(), R.layout-sw800dp.layout_empty_view) } catch (e: Exception) { e.printStackTrace() } @@ -249,7 +263,6 @@ class DishListFragment : BaseFragment() { /** * 将网络数据与本地烹饪中数据合并后渲染列表 - * pageNo > 1 时忽略本地数据,直接追加网络数据,避免 combine 带入旧的本地状态 */ @SuppressLint("NotifyDataSetChanged") private fun loadAndMergeDishList( @@ -258,7 +271,6 @@ class DishListFragment : BaseFragment() { ) { try { if (isAdded.not()) return - // pageNo > 1 时不参与本地合并,避免 combine 带入旧的本地状态 val effectiveLocalList = if (pageNo == 1) localList else null if (records.isNullOrEmpty() && effectiveLocalList.isNullOrEmpty()) { if (pageNo == 1) loadEmptyView() @@ -268,7 +280,6 @@ class DishListFragment : BaseFragment() { val mergedList = records?.toMutableList() ?: mutableListOf() - // 仅 pageNo == 1 时才有本地数据参与合并 if (!effectiveLocalList.isNullOrEmpty()) { val cookingItems = mutableListOf() effectiveLocalList.forEachIndexed { index, entity -> @@ -280,7 +291,6 @@ class DishListFragment : BaseFragment() { food.dinnerType = entity.dinnerType ?: "0" cookingItems.add(food) } else { - // 当前网络数据不包含该本地记录,直接构造 cookingItems.add( FoodRecord( foodId = entity.foodId, @@ -296,7 +306,6 @@ class DishListFragment : BaseFragment() { } if (pageNo >= 2 && !localList.isNullOrEmpty()) { - //第2页起,从mergedList中移除localList中的同foodId数据 val localIds = localList.map { it.foodId }.toHashSet() mergedList.removeAll { it.foodId in localIds } } @@ -310,5 +319,4 @@ class DishListFragment : BaseFragment() { e.printStackTrace() } } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/shuwei/dish/match/view/swipereveallayout/SwipeRevealLayout.java b/app/src/main/java/com/shuwei/dish/match/view/swipereveallayout/SwipeRevealLayout.java deleted file mode 100644 index 1a24879..0000000 --- a/app/src/main/java/com/shuwei/dish/match/view/swipereveallayout/SwipeRevealLayout.java +++ /dev/null @@ -1,1133 +0,0 @@ -/** - The MIT License (MIT) - - Copyright (c) 2016 Chau Thai - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ - -package com.shuwei.dish.match.view.swipereveallayout; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Rect; -import androidx.core.view.GestureDetectorCompat; -import androidx.core.view.ViewCompat; -import androidx.customview.widget.ViewDragHelper; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; - -import com.shuwei.dish.match.R; - -@SuppressLint("RtlHardcoded") -public class SwipeRevealLayout extends ViewGroup { - // These states are used only for ViewBindHelper - protected static final int STATE_CLOSE = 0; - protected static final int STATE_CLOSING = 1; - protected static final int STATE_OPEN = 2; - protected static final int STATE_OPENING = 3; - protected static final int STATE_DRAGGING = 4; - - private static final int DEFAULT_MIN_FLING_VELOCITY = 300; // dp per second - private static final int DEFAULT_MIN_DIST_REQUEST_DISALLOW_PARENT = 1; // dp - - public static final int DRAG_EDGE_LEFT = 0x1; - public static final int DRAG_EDGE_RIGHT = 0x1 << 1; - public static final int DRAG_EDGE_TOP = 0x1 << 2; - public static final int DRAG_EDGE_BOTTOM = 0x1 << 3; - - /** - * The secondary view will be under the main view. - */ - public static final int MODE_NORMAL = 0; - - /** - * The secondary view will stick the edge of the main view. - */ - public static final int MODE_SAME_LEVEL = 1; - - /** - * Main view is the view which is shown when the layout is closed. - */ - private View mMainView; - - /** - * Secondary view is the view which is shown when the layout is opened. - */ - private View mSecondaryView; - - /** - * The rectangle position of the main view when the layout is closed. - */ - private Rect mRectMainClose = new Rect(); - - /** - * The rectangle position of the main view when the layout is opened. - */ - private Rect mRectMainOpen = new Rect(); - - /** - * The rectangle position of the secondary view when the layout is closed. - */ - private Rect mRectSecClose = new Rect(); - - /** - * The rectangle position of the secondary view when the layout is opened. - */ - private Rect mRectSecOpen = new Rect(); - - /** - * The minimum distance (px) to the closest drag edge that the SwipeRevealLayout - * will disallow the parent to intercept touch event. - */ - private int mMinDistRequestDisallowParent = 0; - - private boolean mIsOpenBeforeInit = false; - private volatile boolean mAborted = false; - private volatile boolean mIsScrolling = false; - private volatile boolean mLockDrag = false; - - private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY; - private int mState = STATE_CLOSE; - private int mMode = MODE_NORMAL; - - private int mLastMainLeft = 0; - private int mLastMainTop = 0; - - private int mDragEdge = DRAG_EDGE_LEFT; - - private float mDragDist = 0; - private float mPrevX = -1; - private float mPrevY = -1; - - private ViewDragHelper mDragHelper; - private GestureDetectorCompat mGestureDetector; - - private DragStateChangeListener mDragStateChangeListener; // only used for ViewBindHelper - private SwipeListener mSwipeListener; - - private int mOnLayoutCount = 0; - - interface DragStateChangeListener { - void onDragStateChanged(int state); - } - - /** - * Listener for monitoring events about swipe layout. - */ - public interface SwipeListener { - /** - * Called when the main view becomes completely closed. - */ - void onClosed(SwipeRevealLayout view); - - /** - * Called when the main view becomes completely opened. - */ - void onOpened(SwipeRevealLayout view); - - /** - * Called when the main view's position changes. - * @param slideOffset The new offset of the main view within its range, from 0-1 - */ - void onSlide(SwipeRevealLayout view, float slideOffset); - } - - /** - * No-op stub for {@link SwipeListener}. If you only want ot implement a subset - * of the listener methods, you can extend this instead of implement the full interface. - */ - public static class SimpleSwipeListener implements SwipeListener { - @Override - public void onClosed(SwipeRevealLayout view) {} - - @Override - public void onOpened(SwipeRevealLayout view) {} - - @Override - public void onSlide(SwipeRevealLayout view, float slideOffset) {} - } - - public SwipeRevealLayout(Context context) { - super(context); - init(context, null); - } - - public SwipeRevealLayout(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs); - } - - public SwipeRevealLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - mGestureDetector.onTouchEvent(event); - mDragHelper.processTouchEvent(event); - // 未发生拖动时不消费事件,让子 View 的点击监听正常触发 - return mIsScrolling || mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (isDragLocked()) { - return super.onInterceptTouchEvent(ev); - } - - mDragHelper.processTouchEvent(ev); - mGestureDetector.onTouchEvent(ev); - accumulateDragDist(ev); - - boolean couldBecomeClick = couldBecomeClick(ev); - boolean settling = mDragHelper.getViewDragState() == ViewDragHelper.STATE_SETTLING; - boolean idleAfterScrolled = mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE - && mIsScrolling; - - // must be placed as the last statement - mPrevX = ev.getX(); - mPrevY = ev.getY(); - - // return true => intercept, cannot trigger onClick event - return !couldBecomeClick && (settling || idleAfterScrolled); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - // get views - if (getChildCount() >= 2) { - mSecondaryView = getChildAt(0); - mMainView = getChildAt(1); - } - else if (getChildCount() == 1) { - mMainView = getChildAt(0); - } - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("ConstantConditions") - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - mAborted = false; - - for (int index = 0; index < getChildCount(); index++) { - final View child = getChildAt(index); - - int left, right, top, bottom; - left = right = top = bottom = 0; - - final int minLeft = getPaddingLeft(); - final int maxRight = Math.max(r - getPaddingRight() - l, 0); - final int minTop = getPaddingTop(); - final int maxBottom = Math.max(b - getPaddingBottom() - t, 0); - - int measuredChildHeight = child.getMeasuredHeight(); - int measuredChildWidth = child.getMeasuredWidth(); - - // need to take account if child size is match_parent - final LayoutParams childParams = child.getLayoutParams(); - boolean matchParentHeight = false; - boolean matchParentWidth = false; - - if (childParams != null) { - matchParentHeight = (childParams.height == LayoutParams.MATCH_PARENT) || - (childParams.height == LayoutParams.FILL_PARENT); - matchParentWidth = (childParams.width == LayoutParams.MATCH_PARENT) || - (childParams.width == LayoutParams.FILL_PARENT); - } - - if (matchParentHeight) { - measuredChildHeight = maxBottom - minTop; - childParams.height = measuredChildHeight; - } - - if (matchParentWidth) { - measuredChildWidth = maxRight - minLeft; - childParams.width = measuredChildWidth; - } - - switch (mDragEdge) { - case DRAG_EDGE_RIGHT: - left = Math.max(r - measuredChildWidth - getPaddingRight() - l, minLeft); - top = Math.min(getPaddingTop(), maxBottom); - right = Math.max(r - getPaddingRight() - l, minLeft); - bottom = Math.min(measuredChildHeight + getPaddingTop(), maxBottom); - break; - - case DRAG_EDGE_LEFT: - left = Math.min(getPaddingLeft(), maxRight); - top = Math.min(getPaddingTop(), maxBottom); - right = Math.min(measuredChildWidth + getPaddingLeft(), maxRight); - bottom = Math.min(measuredChildHeight + getPaddingTop(), maxBottom); - break; - - case DRAG_EDGE_TOP: - left = Math.min(getPaddingLeft(), maxRight); - top = Math.min(getPaddingTop(), maxBottom); - right = Math.min(measuredChildWidth + getPaddingLeft(), maxRight); - bottom = Math.min(measuredChildHeight + getPaddingTop(), maxBottom); - break; - - case DRAG_EDGE_BOTTOM: - left = Math.min(getPaddingLeft(), maxRight); - top = Math.max(b - measuredChildHeight - getPaddingBottom() - t, minTop); - right = Math.min(measuredChildWidth + getPaddingLeft(), maxRight); - bottom = Math.max(b - getPaddingBottom() - t, minTop); - break; - } - - child.layout(left, top, right, bottom); - } - - // taking account offset when mode is SAME_LEVEL - if (mMode == MODE_SAME_LEVEL) { - switch (mDragEdge) { - case DRAG_EDGE_LEFT: - mSecondaryView.offsetLeftAndRight(-mSecondaryView.getWidth()); - break; - - case DRAG_EDGE_RIGHT: - mSecondaryView.offsetLeftAndRight(mSecondaryView.getWidth()); - break; - - case DRAG_EDGE_TOP: - mSecondaryView.offsetTopAndBottom(-mSecondaryView.getHeight()); - break; - - case DRAG_EDGE_BOTTOM: - mSecondaryView.offsetTopAndBottom(mSecondaryView.getHeight()); - } - } - - initRects(); - - if (mIsOpenBeforeInit) { - open(false); - } else { - close(false); - } - - mLastMainLeft = mMainView.getLeft(); - mLastMainTop = mMainView.getTop(); - - mOnLayoutCount++; - } - - /** - * {@inheritDoc} - */ - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (getChildCount() < 2) { - throw new RuntimeException("Layout must have two children"); - } - - final LayoutParams params = getLayoutParams(); - - final int widthMode = MeasureSpec.getMode(widthMeasureSpec); - final int heightMode = MeasureSpec.getMode(heightMeasureSpec); - - int desiredWidth = 0; - int desiredHeight = 0; - - // first find the largest child - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - measureChild(child, widthMeasureSpec, heightMeasureSpec); - desiredWidth = Math.max(child.getMeasuredWidth(), desiredWidth); - desiredHeight = Math.max(child.getMeasuredHeight(), desiredHeight); - } - // create new measure spec using the largest child width - widthMeasureSpec = MeasureSpec.makeMeasureSpec(desiredWidth, widthMode); - heightMeasureSpec = MeasureSpec.makeMeasureSpec(desiredHeight, heightMode); - - final int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); - final int measuredHeight = MeasureSpec.getSize(heightMeasureSpec); - - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - final LayoutParams childParams = child.getLayoutParams(); - - if (childParams != null) { - if (childParams.height == LayoutParams.MATCH_PARENT) { - child.setMinimumHeight(measuredHeight); - } - - if (childParams.width == LayoutParams.MATCH_PARENT) { - child.setMinimumWidth(measuredWidth); - } - } - - measureChild(child, widthMeasureSpec, heightMeasureSpec); - desiredWidth = Math.max(child.getMeasuredWidth(), desiredWidth); - desiredHeight = Math.max(child.getMeasuredHeight(), desiredHeight); - } - - // taking accounts of padding - desiredWidth += getPaddingLeft() + getPaddingRight(); - desiredHeight += getPaddingTop() + getPaddingBottom(); - - // adjust desired width - if (widthMode == MeasureSpec.EXACTLY) { - desiredWidth = measuredWidth; - } else { - if (params.width == LayoutParams.MATCH_PARENT) { - desiredWidth = measuredWidth; - } - - if (widthMode == MeasureSpec.AT_MOST) { - desiredWidth = (desiredWidth > measuredWidth)? measuredWidth : desiredWidth; - } - } - - // adjust desired height - if (heightMode == MeasureSpec.EXACTLY) { - desiredHeight = measuredHeight; - } else { - if (params.height == LayoutParams.MATCH_PARENT) { - desiredHeight = measuredHeight; - } - - if (heightMode == MeasureSpec.AT_MOST) { - desiredHeight = (desiredHeight > measuredHeight)? measuredHeight : desiredHeight; - } - } - - setMeasuredDimension(desiredWidth, desiredHeight); - } - - @Override - public void computeScroll() { - if (mDragHelper.continueSettling(true)) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - /** - * Open the panel to show the secondary view - * @param animation true to animate the open motion. {@link SwipeListener} won't be - * called if is animation is false. - */ - public void open(boolean animation) { - mIsOpenBeforeInit = true; - mAborted = false; - - if (animation) { - mState = STATE_OPENING; - mDragHelper.smoothSlideViewTo(mMainView, mRectMainOpen.left, mRectMainOpen.top); - - if (mDragStateChangeListener != null) { - mDragStateChangeListener.onDragStateChanged(mState); - } - } else { - mState = STATE_OPEN; - mDragHelper.abort(); - - mMainView.layout( - mRectMainOpen.left, - mRectMainOpen.top, - mRectMainOpen.right, - mRectMainOpen.bottom - ); - - mSecondaryView.layout( - mRectSecOpen.left, - mRectSecOpen.top, - mRectSecOpen.right, - mRectSecOpen.bottom - ); - } - - ViewCompat.postInvalidateOnAnimation(SwipeRevealLayout.this); - } - - /** - * Close the panel to hide the secondary view - * @param animation true to animate the close motion. {@link SwipeListener} won't be - * called if is animation is false. - */ - public void close(boolean animation) { - mIsOpenBeforeInit = false; - mAborted = false; - - if (animation) { - mState = STATE_CLOSING; - mDragHelper.smoothSlideViewTo(mMainView, mRectMainClose.left, mRectMainClose.top); - - if (mDragStateChangeListener != null) { - mDragStateChangeListener.onDragStateChanged(mState); - } - - } else { - mState = STATE_CLOSE; - mDragHelper.abort(); - - mMainView.layout( - mRectMainClose.left, - mRectMainClose.top, - mRectMainClose.right, - mRectMainClose.bottom - ); - - mSecondaryView.layout( - mRectSecClose.left, - mRectSecClose.top, - mRectSecClose.right, - mRectSecClose.bottom - ); - } - - ViewCompat.postInvalidateOnAnimation(SwipeRevealLayout.this); - } - - /** - * Set the minimum fling velocity to cause the layout to open/close. - * @param velocity dp per second - */ - public void setMinFlingVelocity(int velocity) { - mMinFlingVelocity = velocity; - } - - /** - * Get the minimum fling velocity to cause the layout to open/close. - * @return dp per second - */ - public int getMinFlingVelocity() { - return mMinFlingVelocity; - } - - /** - * Set the edge where the layout can be dragged from. - * @param dragEdge Can be one of these - *

    - *
  • {@link #DRAG_EDGE_LEFT}
  • - *
  • {@link #DRAG_EDGE_TOP}
  • - *
  • {@link #DRAG_EDGE_RIGHT}
  • - *
  • {@link #DRAG_EDGE_BOTTOM}
  • - *
- */ - public void setDragEdge(int dragEdge) { - mDragEdge = dragEdge; - } - - /** - * Get the edge where the layout can be dragged from. - * @return Can be one of these - *
    - *
  • {@link #DRAG_EDGE_LEFT}
  • - *
  • {@link #DRAG_EDGE_TOP}
  • - *
  • {@link #DRAG_EDGE_RIGHT}
  • - *
  • {@link #DRAG_EDGE_BOTTOM}
  • - *
- */ - public int getDragEdge() { - return mDragEdge; - } - - public void setSwipeListener(SwipeListener listener) { - mSwipeListener = listener; - } - - /** - * @param lock if set to true, the user cannot drag/swipe the layout. - */ - public void setLockDrag(boolean lock) { - mLockDrag = lock; - } - - /** - * @return true if the drag/swipe motion is currently locked. - */ - public boolean isDragLocked() { - return mLockDrag; - } - - /** - * @return true if layout is fully opened, false otherwise. - */ - public boolean isOpened() { - return (mState == STATE_OPEN); - } - - /** - * @return true if layout is fully closed, false otherwise. - */ - public boolean isClosed() { - return (mState == STATE_CLOSE); - } - - /** Only used for {@link ViewBinderHelper} */ - void setDragStateChangeListener(DragStateChangeListener listener) { - mDragStateChangeListener = listener; - } - - /** Abort current motion in progress. Only used for {@link ViewBinderHelper} */ - protected void abort() { - mAborted = true; - mDragHelper.abort(); - } - - /** - * In RecyclerView/ListView, onLayout should be called 2 times to display children views correctly. - * This method check if it've already called onLayout two times. - * @return true if you should call {@link #requestLayout()}. - */ - protected boolean shouldRequestLayout() { - return mOnLayoutCount < 2; - } - - - private int getMainOpenLeft() { - switch (mDragEdge) { - case DRAG_EDGE_LEFT: - return mRectMainClose.left + mSecondaryView.getWidth(); - - case DRAG_EDGE_RIGHT: - return mRectMainClose.left - mSecondaryView.getWidth(); - - case DRAG_EDGE_TOP: - return mRectMainClose.left; - - case DRAG_EDGE_BOTTOM: - return mRectMainClose.left; - - default: - return 0; - } - } - - private int getMainOpenTop() { - switch (mDragEdge) { - case DRAG_EDGE_LEFT: - return mRectMainClose.top; - - case DRAG_EDGE_RIGHT: - return mRectMainClose.top; - - case DRAG_EDGE_TOP: - return mRectMainClose.top + mSecondaryView.getHeight(); - - case DRAG_EDGE_BOTTOM: - return mRectMainClose.top - mSecondaryView.getHeight(); - - default: - return 0; - } - } - - private int getSecOpenLeft() { - if (mMode == MODE_NORMAL || mDragEdge == DRAG_EDGE_BOTTOM || mDragEdge == DRAG_EDGE_TOP) { - return mRectSecClose.left; - } - - if (mDragEdge == DRAG_EDGE_LEFT) { - return mRectSecClose.left + mSecondaryView.getWidth(); - } else { - return mRectSecClose.left - mSecondaryView.getWidth(); - } - } - - private int getSecOpenTop() { - if (mMode == MODE_NORMAL || mDragEdge == DRAG_EDGE_LEFT || mDragEdge == DRAG_EDGE_RIGHT) { - return mRectSecClose.top; - } - - if (mDragEdge == DRAG_EDGE_TOP) { - return mRectSecClose.top + mSecondaryView.getHeight(); - } else { - return mRectSecClose.top - mSecondaryView.getHeight(); - } - } - - private void initRects() { - // close position of main view - mRectMainClose.set( - mMainView.getLeft(), - mMainView.getTop(), - mMainView.getRight(), - mMainView.getBottom() - ); - - // close position of secondary view - mRectSecClose.set( - mSecondaryView.getLeft(), - mSecondaryView.getTop(), - mSecondaryView.getRight(), - mSecondaryView.getBottom() - ); - - // open position of the main view - mRectMainOpen.set( - getMainOpenLeft(), - getMainOpenTop(), - getMainOpenLeft() + mMainView.getWidth(), - getMainOpenTop() + mMainView.getHeight() - ); - - // open position of the secondary view - mRectSecOpen.set( - getSecOpenLeft(), - getSecOpenTop(), - getSecOpenLeft() + mSecondaryView.getWidth(), - getSecOpenTop() + mSecondaryView.getHeight() - ); - } - - private boolean couldBecomeClick(MotionEvent ev) { - return isInMainView(ev) && !shouldInitiateADrag(); - } - - private boolean isInMainView(MotionEvent ev) { - float x = ev.getX(); - float y = ev.getY(); - - boolean withinVertical = mMainView.getTop() <= y && y <= mMainView.getBottom(); - boolean withinHorizontal = mMainView.getLeft() <= x && x <= mMainView.getRight(); - - return withinVertical && withinHorizontal; - } - - private boolean shouldInitiateADrag() { - float minDistToInitiateDrag = mDragHelper.getTouchSlop(); - return mDragDist >= minDistToInitiateDrag; - } - - private void accumulateDragDist(MotionEvent ev) { - final int action = ev.getAction(); - if (action == MotionEvent.ACTION_DOWN) { - mDragDist = 0; - return; - } - - boolean dragHorizontally = getDragEdge() == DRAG_EDGE_LEFT || - getDragEdge() == DRAG_EDGE_RIGHT; - - float dragged; - if (dragHorizontally) { - dragged = Math.abs(ev.getX() - mPrevX); - } else { - dragged = Math.abs(ev.getY() - mPrevY); - } - - mDragDist += dragged; - } - - private void init(Context context, AttributeSet attrs) { - if (attrs != null && context != null) { - TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, - R.styleable.SwipeRevealLayout, - 0, 0 - ); - - mDragEdge = a.getInteger(R.styleable.SwipeRevealLayout_dragEdge, DRAG_EDGE_LEFT); - mMinFlingVelocity = a.getInteger(R.styleable.SwipeRevealLayout_flingVelocity, DEFAULT_MIN_FLING_VELOCITY); - mMode = a.getInteger(R.styleable.SwipeRevealLayout_mode, MODE_NORMAL); - - mMinDistRequestDisallowParent = a.getDimensionPixelSize( - R.styleable.SwipeRevealLayout_minDistRequestDisallowParent, - dpToPx(DEFAULT_MIN_DIST_REQUEST_DISALLOW_PARENT) - ); - } - - mDragHelper = ViewDragHelper.create(this, 1.0f, mDragHelperCallback); - mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL); - - mGestureDetector = new GestureDetectorCompat(context, mGestureListener); - } - - private final GestureDetector.OnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { - boolean hasDisallowed = false; - - @Override - public boolean onDown(MotionEvent e) { - mIsScrolling = false; - hasDisallowed = false; - return true; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - mIsScrolling = true; - return false; - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - mIsScrolling = true; - - if (getParent() != null) { - // 判断滑动方向:水平分量大于垂直分量时才阻止父布局拦截 - // 避免垂直滑动时误触发 SmartRefreshLayout 的下拉刷新 - boolean isHorizontalScroll = Math.abs(distanceX) >= Math.abs(distanceY); - - boolean shouldDisallow; - if (!isHorizontalScroll) { - // 垂直滑动,允许父布局(SmartRefreshLayout/RecyclerView)正常处理 - shouldDisallow = false; - } else if (!hasDisallowed) { - shouldDisallow = getDistToClosestEdge() >= mMinDistRequestDisallowParent; - if (shouldDisallow) { - hasDisallowed = true; - } - } else { - shouldDisallow = true; - } - - // 阻止父布局拦截触摸事件,确保水平滑动正常工作 - getParent().requestDisallowInterceptTouchEvent(shouldDisallow); - } - - return false; - } - }; - - private int getDistToClosestEdge() { - switch (mDragEdge) { - case DRAG_EDGE_LEFT: - final int pivotRight = mRectMainClose.left + mSecondaryView.getWidth(); - - return Math.min( - mMainView.getLeft() - mRectMainClose.left, - pivotRight - mMainView.getLeft() - ); - - case DRAG_EDGE_RIGHT: - final int pivotLeft = mRectMainClose.right - mSecondaryView.getWidth(); - - return Math.min( - mMainView.getRight() - pivotLeft, - mRectMainClose.right - mMainView.getRight() - ); - - case DRAG_EDGE_TOP: - final int pivotBottom = mRectMainClose.top + mSecondaryView.getHeight(); - - return Math.min( - mMainView.getBottom() - pivotBottom, - pivotBottom - mMainView.getTop() - ); - - case DRAG_EDGE_BOTTOM: - final int pivotTop = mRectMainClose.bottom - mSecondaryView.getHeight(); - - return Math.min( - mRectMainClose.bottom - mMainView.getBottom(), - mMainView.getBottom() - pivotTop - ); - } - - return 0; - } - - private int getHalfwayPivotHorizontal() { - if (mDragEdge == DRAG_EDGE_LEFT) { - return mRectMainClose.left + mSecondaryView.getWidth() / 2; - } else { - return mRectMainClose.right - mSecondaryView.getWidth() / 2; - } - } - - private int getHalfwayPivotVertical() { - if (mDragEdge == DRAG_EDGE_TOP) { - return mRectMainClose.top + mSecondaryView.getHeight() / 2; - } else { - return mRectMainClose.bottom - mSecondaryView.getHeight() / 2; - } - } - - private final ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() { - @Override - public boolean tryCaptureView(View child, int pointerId) { - mAborted = false; - - if (mLockDrag) - return false; - - mDragHelper.captureChildView(mMainView, pointerId); - return false; - } - - @Override - public int clampViewPositionVertical(View child, int top, int dy) { - switch (mDragEdge) { - case DRAG_EDGE_TOP: - return Math.max( - Math.min(top, mRectMainClose.top + mSecondaryView.getHeight()), - mRectMainClose.top - ); - - case DRAG_EDGE_BOTTOM: - return Math.max( - Math.min(top, mRectMainClose.top), - mRectMainClose.top - mSecondaryView.getHeight() - ); - - default: - return child.getTop(); - } - } - - @Override - public int clampViewPositionHorizontal(View child, int left, int dx) { - switch (mDragEdge) { - case DRAG_EDGE_RIGHT: - return Math.max( - Math.min(left, mRectMainClose.left), - mRectMainClose.left - mSecondaryView.getWidth() - ); - - case DRAG_EDGE_LEFT: - return Math.max( - Math.min(left, mRectMainClose.left + mSecondaryView.getWidth()), - mRectMainClose.left - ); - - default: - return child.getLeft(); - } - } - - @Override - public void onViewReleased(View releasedChild, float xvel, float yvel) { - final boolean velRightExceeded = pxToDp((int) xvel) >= mMinFlingVelocity; - final boolean velLeftExceeded = pxToDp((int) xvel) <= -mMinFlingVelocity; - final boolean velUpExceeded = pxToDp((int) yvel) <= -mMinFlingVelocity; - final boolean velDownExceeded = pxToDp((int) yvel) >= mMinFlingVelocity; - - final int pivotHorizontal = getHalfwayPivotHorizontal(); - final int pivotVertical = getHalfwayPivotVertical(); - - switch (mDragEdge) { - case DRAG_EDGE_RIGHT: - if (velRightExceeded) { - close(true); - } else if (velLeftExceeded) { - open(true); - } else { - if (mMainView.getRight() < pivotHorizontal) { - open(true); - } else { - close(true); - } - } - break; - - case DRAG_EDGE_LEFT: - if (velRightExceeded) { - open(true); - } else if (velLeftExceeded) { - close(true); - } else { - if (mMainView.getLeft() < pivotHorizontal) { - close(true); - } else { - open(true); - } - } - break; - - case DRAG_EDGE_TOP: - if (velUpExceeded) { - close(true); - } else if (velDownExceeded) { - open(true); - } else { - if (mMainView.getTop() < pivotVertical) { - close(true); - } else { - open(true); - } - } - break; - - case DRAG_EDGE_BOTTOM: - if (velUpExceeded) { - open(true); - } else if (velDownExceeded) { - close(true); - } else { - if (mMainView.getBottom() < pivotVertical) { - open(true); - } else { - close(true); - } - } - break; - } - } - - @Override - public void onEdgeDragStarted(int edgeFlags, int pointerId) { - super.onEdgeDragStarted(edgeFlags, pointerId); - - if (mLockDrag) { - return; - } - - boolean edgeStartLeft = (mDragEdge == DRAG_EDGE_RIGHT) - && edgeFlags == ViewDragHelper.EDGE_LEFT; - - boolean edgeStartRight = (mDragEdge == DRAG_EDGE_LEFT) - && edgeFlags == ViewDragHelper.EDGE_RIGHT; - - boolean edgeStartTop = (mDragEdge == DRAG_EDGE_BOTTOM) - && edgeFlags == ViewDragHelper.EDGE_TOP; - - boolean edgeStartBottom = (mDragEdge == DRAG_EDGE_TOP) - && edgeFlags == ViewDragHelper.EDGE_BOTTOM; - - if (edgeStartLeft || edgeStartRight || edgeStartTop || edgeStartBottom) { - mDragHelper.captureChildView(mMainView, pointerId); - } - } - - @Override - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { - super.onViewPositionChanged(changedView, left, top, dx, dy); - if (mMode == MODE_SAME_LEVEL) { - if (mDragEdge == DRAG_EDGE_LEFT || mDragEdge == DRAG_EDGE_RIGHT) { - mSecondaryView.offsetLeftAndRight(dx); - } else { - mSecondaryView.offsetTopAndBottom(dy); - } - } - - boolean isMoved = (mMainView.getLeft() != mLastMainLeft) || (mMainView.getTop() != mLastMainTop); - if (mSwipeListener != null && isMoved) { - if (mMainView.getLeft() == mRectMainClose.left && mMainView.getTop() == mRectMainClose.top) { - mSwipeListener.onClosed(SwipeRevealLayout.this); - } - else if (mMainView.getLeft() == mRectMainOpen.left && mMainView.getTop() == mRectMainOpen.top) { - mSwipeListener.onOpened(SwipeRevealLayout.this); - } - else { - mSwipeListener.onSlide(SwipeRevealLayout.this, getSlideOffset()); - } - } - - mLastMainLeft = mMainView.getLeft(); - mLastMainTop = mMainView.getTop(); - ViewCompat.postInvalidateOnAnimation(SwipeRevealLayout.this); - } - - private float getSlideOffset() { - switch (mDragEdge) { - case DRAG_EDGE_LEFT: - return (float) (mMainView.getLeft() - mRectMainClose.left) / mSecondaryView.getWidth(); - - case DRAG_EDGE_RIGHT: - return (float) (mRectMainClose.left - mMainView.getLeft()) / mSecondaryView.getWidth(); - - case DRAG_EDGE_TOP: - return (float) (mMainView.getTop() - mRectMainClose.top) / mSecondaryView.getHeight(); - - case DRAG_EDGE_BOTTOM: - return (float) (mRectMainClose.top - mMainView.getTop()) / mSecondaryView.getHeight(); - - default: - return 0; - } - } - - @Override - public void onViewDragStateChanged(int state) { - super.onViewDragStateChanged(state); - final int prevState = mState; - - switch (state) { - case ViewDragHelper.STATE_DRAGGING: - mState = STATE_DRAGGING; - break; - - case ViewDragHelper.STATE_IDLE: - - // drag edge is left or right - if (mDragEdge == DRAG_EDGE_LEFT || mDragEdge == DRAG_EDGE_RIGHT) { - if (mMainView.getLeft() == mRectMainClose.left) { - mState = STATE_CLOSE; - } else { - mState = STATE_OPEN; - } - } - - // drag edge is top or bottom - else { - if (mMainView.getTop() == mRectMainClose.top) { - mState = STATE_CLOSE; - } else { - mState = STATE_OPEN; - } - } - break; - } - - if (mDragStateChangeListener != null && !mAborted && prevState != mState) { - mDragStateChangeListener.onDragStateChanged(mState); - } - } - }; - - public static String getStateString(int state) { - switch (state) { - case STATE_CLOSE: - return "state_close"; - - case STATE_CLOSING: - return "state_closing"; - - case STATE_OPEN: - return "state_open"; - - case STATE_OPENING: - return "state_opening"; - - case STATE_DRAGGING: - return "state_dragging"; - - default: - return "undefined"; - } - } - - private int pxToDp(int px) { - Resources resources = getContext().getResources(); - DisplayMetrics metrics = resources.getDisplayMetrics(); - return (int) (px / ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)); - } - - private int dpToPx(int dp) { - Resources resources = getContext().getResources(); - DisplayMetrics metrics = resources.getDisplayMetrics(); - return (int) (dp * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT)); - } -} diff --git a/app/src/main/java/com/shuwei/dish/match/view/swipereveallayout/ViewBinderHelper.java b/app/src/main/java/com/shuwei/dish/match/view/swipereveallayout/ViewBinderHelper.java deleted file mode 100644 index 9ed5f5e..0000000 --- a/app/src/main/java/com/shuwei/dish/match/view/swipereveallayout/ViewBinderHelper.java +++ /dev/null @@ -1,271 +0,0 @@ -/** - The MIT License (MIT) - - Copyright (c) 2016 Chau Thai - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ - -package com.shuwei.dish.match.view.swipereveallayout; - -import android.os.Bundle; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * ViewBinderHelper provides a quick and easy solution to restore the open/close state - * of the items in RecyclerView, ListView, GridView or any view that requires its child view - * to bind the view to a data object. - * - *

When you bind you data object to a view, use {@link #bind(SwipeRevealLayout, String)} to - * save and restore the open/close state of the view.

- * - *

Optionally, if you also want to save and restore the open/close state when the device's - * orientation is changed, call {@link #saveStates(Bundle)} in {@link android.app.Activity#onSaveInstanceState(Bundle)} - * and {@link #restoreStates(Bundle)} in {@link android.app.Activity#onRestoreInstanceState(Bundle)}

- */ -public class ViewBinderHelper { - private static final String BUNDLE_MAP_KEY = "ViewBinderHelper_Bundle_Map_Key"; - - private Map mapStates = Collections.synchronizedMap(new HashMap()); - private Map mapLayouts = Collections.synchronizedMap(new HashMap()); - private Set lockedSwipeSet = Collections.synchronizedSet(new HashSet()); - - private volatile boolean openOnlyOne = false; - private final Object stateChangeLock = new Object(); - - /** - * Help to save and restore open/close state of the swipeLayout. Call this method - * when you bind your view holder with the data object. - * - * @param swipeLayout swipeLayout of the current view. - * @param id a string that uniquely defines the data object of the current view. - */ - public void bind(final SwipeRevealLayout swipeLayout, final String id) { - if (swipeLayout.shouldRequestLayout()) { - swipeLayout.requestLayout(); - } - - mapLayouts.values().remove(swipeLayout); - mapLayouts.put(id, swipeLayout); - - swipeLayout.abort(); - swipeLayout.setDragStateChangeListener(new SwipeRevealLayout.DragStateChangeListener() { - @Override - public void onDragStateChanged(int state) { - mapStates.put(id, state); - - if (openOnlyOne) { - closeOthers(id, swipeLayout); - } - } - }); - - // first time binding. - if (!mapStates.containsKey(id)) { - mapStates.put(id, SwipeRevealLayout.STATE_CLOSE); - swipeLayout.close(false); - } - - // not the first time, then close or open depends on the current state. - else { - int state = mapStates.get(id); - - if (state == SwipeRevealLayout.STATE_CLOSE || state == SwipeRevealLayout.STATE_CLOSING || - state == SwipeRevealLayout.STATE_DRAGGING) { - swipeLayout.close(false); - } else { - swipeLayout.open(false); - } - } - - // set lock swipe - swipeLayout.setLockDrag(lockedSwipeSet.contains(id)); - } - - /** - * Only if you need to restore open/close state when the orientation is changed. - * Call this method in {@link android.app.Activity#onSaveInstanceState(Bundle)} - */ - public void saveStates(Bundle outState) { - if (outState == null) - return; - - Bundle statesBundle = new Bundle(); - for (Map.Entry entry : mapStates.entrySet()) { - statesBundle.putInt(entry.getKey(), entry.getValue()); - } - - outState.putBundle(BUNDLE_MAP_KEY, statesBundle); - } - - - /** - * Only if you need to restore open/close state when the orientation is changed. - * Call this method in {@link android.app.Activity#onRestoreInstanceState(Bundle)} - */ - @SuppressWarnings({"unchecked", "ConstantConditions"}) - public void restoreStates(Bundle inState) { - if (inState == null) - return; - - if (inState.containsKey(BUNDLE_MAP_KEY)) { - HashMap restoredMap = new HashMap<>(); - - Bundle statesBundle = inState.getBundle(BUNDLE_MAP_KEY); - Set keySet = statesBundle.keySet(); - - if (keySet != null) { - for (String key : keySet) { - restoredMap.put(key, statesBundle.getInt(key)); - } - } - - mapStates = restoredMap; - } - } - - /** - * Lock swipe for some layouts. - * @param id a string that uniquely defines the data object. - */ - public void lockSwipe(String... id) { - setLockSwipe(true, id); - } - - /** - * Unlock swipe for some layouts. - * @param id a string that uniquely defines the data object. - */ - public void unlockSwipe(String... id) { - setLockSwipe(false, id); - } - - /** - * @param openOnlyOne If set to true, then only one row can be opened at a time. - */ - public void setOpenOnlyOne(boolean openOnlyOne) { - this.openOnlyOne = openOnlyOne; - } - - /** - * Open a specific layout. - * @param id unique id which identifies the data object which is bind to the layout. - */ - public void openLayout(final String id) { - synchronized (stateChangeLock) { - mapStates.put(id, SwipeRevealLayout.STATE_OPEN); - - if (mapLayouts.containsKey(id)) { - final SwipeRevealLayout layout = mapLayouts.get(id); - layout.open(true); - } - else if (openOnlyOne) { - closeOthers(id, mapLayouts.get(id)); - } - } - } - - /** - * Close a specific layout. - * @param id unique id which identifies the data object which is bind to the layout. - */ - public void closeLayout(final String id) { - synchronized (stateChangeLock) { - mapStates.put(id, SwipeRevealLayout.STATE_CLOSE); - - if (mapLayouts.containsKey(id)) { - final SwipeRevealLayout layout = mapLayouts.get(id); - layout.close(true); - } - } - } - - /** - * 关闭所有已展开的侧滑布局,用于点击空白区域时收起侧滑。 - */ - public void closeAll() { - synchronized (stateChangeLock) { - for (Map.Entry entry : mapStates.entrySet()) { - entry.setValue(SwipeRevealLayout.STATE_CLOSE); - } - for (SwipeRevealLayout layout : mapLayouts.values()) { - layout.close(true); - } - } - } - - /** - * Close others swipe layout. - * @param id layout which bind with this data object id will be excluded. - * @param swipeLayout will be excluded. - */ - private void closeOthers(String id, SwipeRevealLayout swipeLayout) { - synchronized (stateChangeLock) { - // close other rows if openOnlyOne is true. - if (getOpenCount() > 1) { - for (Map.Entry entry : mapStates.entrySet()) { - if (!entry.getKey().equals(id)) { - entry.setValue(SwipeRevealLayout.STATE_CLOSE); - } - } - - for (SwipeRevealLayout layout : mapLayouts.values()) { - if (layout != swipeLayout) { - layout.close(true); - } - } - } - } - } - - private void setLockSwipe(boolean lock, String... id) { - if (id == null || id.length == 0) - return; - - if (lock) - lockedSwipeSet.addAll(Arrays.asList(id)); - else - lockedSwipeSet.removeAll(Arrays.asList(id)); - - for (String s : id) { - SwipeRevealLayout layout = mapLayouts.get(s); - if (layout != null) { - layout.setLockDrag(lock); - } - } - } - - private int getOpenCount() { - int total = 0; - - for (int state : mapStates.values()) { - if (state == SwipeRevealLayout.STATE_OPEN || state == SwipeRevealLayout.STATE_OPENING) { - total++; - } - } - - return total; - } -} diff --git a/app/src/main/res/layout/activity_prepare_food.xml b/app/src/main/res/layout/activity_prepare_food.xml index b3b19fc..17191fd 100644 --- a/app/src/main/res/layout/activity_prepare_food.xml +++ b/app/src/main/res/layout/activity_prepare_food.xml @@ -92,15 +92,15 @@ - + tools:listitem="@layout/list_item_dish_cook" /> diff --git a/app/src/main/res/layout/activity_sampling_mode.xml b/app/src/main/res/layout/activity_sampling_mode.xml index 644e003..465b267 100644 --- a/app/src/main/res/layout/activity_sampling_mode.xml +++ b/app/src/main/res/layout/activity_sampling_mode.xml @@ -76,7 +76,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> - - - + android:background="@drawable/shape_white_fb_15_corners"> - + - + - + - + - + - - - - - - - - - - - + diff --git a/app/src/main/res/layout/list_item_food_list.xml b/app/src/main/res/layout/list_item_food_list.xml index 2b89b7a..0865e58 100644 --- a/app/src/main/res/layout/list_item_food_list.xml +++ b/app/src/main/res/layout/list_item_food_list.xml @@ -1,87 +1,61 @@ - + android:background="@drawable/shape_white_fb_15_corners" + android:foreground="?android:attr/selectableItemBackground" + android:paddingStart="30dp" + android:paddingEnd="30dp"> - - + - + - + - - - - - - - - - - - - +