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 0000000..4f8f0a7 Binary files /dev/null and b/app/libs/SwipeRecyclerView.aar differ diff --git a/app/src/main/java/com/shuwei/dish/match/adapter/DishPartAdapter.kt b/app/src/main/java/com/shuwei/dish/match/adapter/DishPartAdapter.kt index ac95dd9..e15131c 100644 --- a/app/src/main/java/com/shuwei/dish/match/adapter/DishPartAdapter.kt +++ b/app/src/main/java/com/shuwei/dish/match/adapter/DishPartAdapter.kt @@ -12,22 +12,13 @@ import com.shuwei.dish.match.entity.CookFoodGoodsEntity import com.shuwei.dish.match.utils.ext.gone import com.shuwei.dish.match.utils.ext.roundedOneDecimalPlace import com.shuwei.dish.match.utils.ext.visible -import com.shuwei.dish.match.view.swipereveallayout.ViewBinderHelper class DishPartAdapter(list: MutableList) : 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"> - - + - + - + - - - - - - - - - - - - +