RefreshView左滑效果
最近项目中有需求要做列表item的左滑效果,需要左滑时显示操作按钮。以为很容易,做起来也是磕磕绊绊,特地保存下来,也为了能够得到建议优化方案。
初步思路:
1、利用RefreshView加载数据
2、在item样式xml文件中添加左滑按钮
3、补充item点击事件和左滑事件
查阅了一些相关博主的资料后,东改西凑写了一个RecyclerView自定义view:
public class SwipeRecyclerView extends RecyclerView { private int maxLength, mTouchSlop; private int xDown, yDown, xMove, yMove; /** * 当前选中的item索引(这个很重要) */ private int curSelectPosition; private Scroller mScroller; private LinearLayout mCurItemLayout, mLastItemLayout; private LinearLayout mLlHidden;//隐藏部分 private TextView mItemContent; private TextView mTvDelete, mTvEdit; private LinearLayout mItemDelete; /** * 隐藏部分长度 */ private int mHiddenWidth; /** * 记录连续移动的长度 */ private int mMoveWidth = 0; /** * 是否是第一次touch */ private boolean isFirst = true; private boolean isItemClick = true; //上下滑动标识 private boolean isDragging = false; private Context mContext; /** * 编辑的监听事件 */ private OnItemClickListener mItemListener; public void setItemClickListener(OnItemClickListener listener) { this.mItemListener = listener; } /** * 删除的监听事件 */ private OnRightClickListener mRightListener; public void setRightClickListener(OnRightClickListener listener) { this.mRightListener = listener; } /** * 编辑的监听事件 */ private OnEditClickListener mEditListener; public void setEditClickListener(OnEditClickListener listener) { this.mEditListener = listener; } public SwipeRecyclerView(Context context) { this(context, null); } public SwipeRecyclerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SwipeRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; //滑动到最小距离 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); //滑动的最大距离 maxLength = ((int) (180 * context.getResources().getDisplayMetrics().density + 0.5f)); //初始化Scroller mScroller = new Scroller(context, new LinearInterpolator(context, null)); } @Override public boolean onTouchEvent(MotionEvent e) { int x = (int) e.getX(); int y = (int) e.getY(); switch (e.getAction()) { case MotionEvent.ACTION_DOWN: //记录当前按下的坐标 (onTouchEvent会最先拦截触摸事件,所以这里要获取按下时选中的item) xDown = x; yDown = y; View view = findChildViewUnder(xDown, yDown); if (view == null) { renew(); return false; } //计算选中哪个Item int firstPosition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition(); Rect itemRect = new Rect(); final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() == View.VISIBLE) { child.getHitRect(itemRect); if (itemRect.contains(x, y)) { curSelectPosition = firstPosition + i; break; } } } if (isFirst) {//第一次时,不用重置上一次的Item isFirst = false; } else { //屏幕再次接收到点击时,恢复上一次Item的状态 renew(); } //取到当前选中的Item,赋给mCurItemLayout,以便对其进行左移 int select = curSelectPosition - firstPosition; if (select == 0) { return false; } View item = getChildAt(curSelectPosition - firstPosition); if (item != null) { //获取当前选中的Item GroupAdapter.GroupHolder viewHolder = (GroupAdapter.GroupHolder) getChildViewHolder(item); mCurItemLayout = viewHolder.llayout; //找到具体元素(这与实际业务相关了~~) mLlHidden = (LinearLayout) mCurItemLayout.findViewById(R.id.ll_hidden); mItemDelete = (LinearLayout) mCurItemLayout.findViewById(R.id.ll_hidden); mTvDelete = (TextView) mCurItemLayout.findViewById(R.id.tv_item_delete); mTvEdit = (TextView) mCurItemLayout.findViewById(R.id.tv_item_edit); mTvEdit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mEditListener != null) { //将Item右移,恢复原位 scrollRight(mLastItemLayout, (0 - mMoveWidth)); //清空变量 mHiddenWidth = 0; mMoveWidth = 0; mEditListener.onEditClick(curSelectPosition, ""); } } }); mItemDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mRightListener != null) { //删除 //将Item右移,恢复原位 scrollRight(mLastItemLayout, (0 - mMoveWidth)); //清空变量 mHiddenWidth = 0; mMoveWidth = 0; mRightListener.onRightClick(curSelectPosition, ""); } } }); //这里将删除按钮的宽度设为可以移动的距离 mHiddenWidth = mLlHidden.getWidth(); } break; case MotionEvent.ACTION_MOVE: xMove = x; yMove = y; int dx = xMove - xDown;//为负时:手指向左滑动;为正时:手指向右滑动。这与Android的屏幕坐标定义有关 int dy = yMove - yDown;// //左滑 if (dx < 0 && Math.abs(dx) > mTouchSlop && Math.abs(dy) < mTouchSlop) { int newScrollX = Math.abs(dx); if (mMoveWidth >= mHiddenWidth) {//超过了,不能再移动了 newScrollX = 0; } else if (mMoveWidth + newScrollX > mHiddenWidth) {//这次要超了, newScrollX = mHiddenWidth - mMoveWidth; } //左滑,每次滑动手指移动的距离 scrollLeft(mCurItemLayout, newScrollX); //对移动的距离叠加 mMoveWidth = mMoveWidth + newScrollX; } else if (dx > 0) {//右滑 //执行右滑,这里没有做跟随,瞬间恢复 scrollRight(mCurItemLayout, 0 - mMoveWidth); mMoveWidth = 0; } break; case MotionEvent.ACTION_UP://手抬起时 int scrollX = 0; if (mCurItemLayout != null) { scrollX = mCurItemLayout.getScrollX(); }
//判断时点击事件调用点击事件回调 if (scrollX < 6 && isItemClick && !isDragging) { mItemListener.onItemClick(curSelectPosition, ""); } else { if (mHiddenWidth > mMoveWidth) { int toX = (mHiddenWidth - mMoveWidth); if (scrollX > mHiddenWidth / 2) {//超过一半长度时松开,则自动滑到左侧 scrollLeft(mCurItemLayout, toX); mMoveWidth = mHiddenWidth; } else {//不到一半时松开,则恢复原状 scrollRight(mCurItemLayout, 0 - mMoveWidth); mMoveWidth = 0; } } } mLastItemLayout = mCurItemLayout; break; } return super.onTouchEvent(e); } private void renew(){ if (mLastItemLayout != null && mMoveWidth > 0) { //将Item右移,恢复原位 isItemClick = false; scrollRight(mLastItemLayout, (0 - mMoveWidth)); //清空变量 mHiddenWidth = 0; mMoveWidth = 0; } else { isItemClick = true; } } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { Log.e(TAG, "computeScroll getCurrX ->" + mScroller.getCurrX()); mCurItemLayout.scrollBy(mScroller.getCurrX(), 0); invalidate(); } } @Override public void onScrollStateChanged(int state) { super.onScrollStateChanged(state); isDragging = state == SCROLL_STATE_DRAGGING; } /** * 向左滑动 */ private void scrollLeft(View item, int scorllX) { Log.e(TAG, " scroll left -> " + scorllX); if (item != null) item.scrollBy(scorllX, 0); } /** * 向右滑动 */ private void scrollRight(View item, int scorllX) { Log.e(TAG, " scroll right -> " + scorllX); if (item != null) item.scrollBy(scorllX, 0); } public interface OnRightClickListener { void onRightClick(int position, String id); } public interface OnEditClickListener { void onEditClick(int position, String id); } public interface OnItemClickListener { void onItemClick(int position, String id); } }
在布局文件中引用自定义view:
<com.***.SwipeRecyclerView android:id="@+id/groups" android:focusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:cacheColorHint="#00000000" android:fadingEdge="none" android:listSelector="#00000000" android:scrollbars="none"/>
在对应activity中声明对应view的使用
SwipeRefreshLayout mRefreshView;
*** mGroupsView.setAdapter(groupAdapter); mGroupsView.setLayoutManager(new LinearLayoutManager(this)); // mGroupsView.setItemAnimator(new DefaultItemAnimator());//自适应item添加删除动画 mGroupsView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
在对应的adapter中间中定义自定义的itemView文件:
@Override public GroupHolder onCreateHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_group, parent, false); return new GroupHolder(view); }
对应的itemView文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/ll_item" android:background="@drawable/bg_selector_primary" android:focusable="true" android:orientation="horizontal"> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:padding="8dp"> <ImageView android:id="@+id/head" android:layout_width="40dp" android:layout_height="40dp" android:src="@mipmap/logo" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:text="ZenoZhang" android:textColor="@android:color/white" android:textSize="@dimen/textArticle" app:layout_constraintBottom_toTopOf="@+id/online" app:layout_constraintLeft_toRightOf="@id/head" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/online" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/in_online_member" android:textColor="@android:color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="@id/name" app:layout_constraintTop_toBottomOf="@id/name" /> <TextView android:id="@+id/count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="count" android:textColor="@color/textColorGreen" android:textSize="@dimen/textArticle" app:layout_constraintLeft_toRightOf="@+id/online" app:layout_constraintTop_toTopOf="@+id/online" /> </android.support.constraint.ConstraintLayout> <LinearLayout android:id="@+id/ll_hidden" android:layout_width="160dp" android:layout_height="match_parent"> <TextView android:id="@+id/tv_item_edit" android:layout_width="80dp" android:layout_height="match_parent" android:layout_marginBottom="2dp" android:background="#ffa042" android:gravity="center" android:padding="15dp" android:text="编辑" /> <TextView android:id="@+id/tv_item_delete" android:layout_width="80dp" android:layout_height="match_parent" android:layout_marginBottom="2dp" android:background="#ff0000" android:gravity="center" android:padding="15dp" android:text="删除" /> </LinearLayout> </LinearLayout>
接着在对用的Holder中声明itemView中的按钮
mTvDelete = itemView.findViewById(R.id.tv_item_delete); mTvEdit = itemView.findViewById(R.id.tv_item_edit);
最后在使用的activity中定义好对应的点击事件即可:
mGroupsView.setRightClickListener(new SwipeRecyclerView.OnRightClickListener() { @Override public void onRightClick(int position, String id) { } }); mGroupsView.setEditClickListener(new SwipeRecyclerView.OnEditClickListener() { @Override public void onEditClick(int position, String id) { } }); mGroupsView.setItemClickListener(new SwipeRecyclerView.OnItemClickListener() { @Override public void onItemClick(int position, String id) { } @Override public void onFailure(Group group) { } }); } });
注意点:
SwipeRecyclerView中的onTouchEvent方法会最先获取item的点击事件,此时如果监听了down事件会导致adapter中的itemClick事件无法接受,具体listview事件分发机制感兴趣的可以自行搜索,网上说的详细的文章很多,当up事件触发后需要判断好是否时单个item的点击还是上下滑动从而决定是否出发itemclick事件,还要注意点击滑动其他item时复原最初改变位置的item。
如果有涉及侵占他人权益,请及时沟通。如侵比删。
以上,over。