RecycleView实现QQ侧滑效果
2018-03-31 20:53
357 查看
一、 侧滑效果描述
1、 item向左滑动不是马上删除Item,而是展示删除按钮
2、 这边利用RecycleView提供的ItemTouchHelper可以较轻松实现这个效果
3、 效果图:
二、 代码实现
1、 item向左滑动不是马上删除Item,而是展示删除按钮
2、 这边利用RecycleView提供的ItemTouchHelper可以较轻松实现这个效果
3、 效果图:
二、 代码实现
1、创建含有RecycleView的fragment
package com.example.myapplication; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.ArrayList; import java.util.List; public class FragmentMainPage extends Fragment { private RecyclerView mRecyclerView; private RecyclerView.LayoutManager manager; private MyAdapter adapter; private ItemTouchHelper itemTouchHelper; private List<String> mDatas= new ArrayList<>(); @Override public ViewonCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ View view =inflater.inflate(R.layout.fragment_main_page, container, false); return view; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { mRecyclerView = view.findViewById(R.id.recycler); manager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false); initData(); initRecycler(); } private void initData() { for (int i = 0; i < 30; i++){ mDatas.add("这是第" + i + "item"); } } private void initRecycler() { mRecyclerView.setLayoutManager(manager); mRecyclerView.setHasFixedSize(true); adapter = new MyAdapter(getActivity(), mDatas); mRecyclerView.setAdapter(adapter); //RecyclerView与ItemTouchHelper关联 ItemTouchHelper.Callback callback = new MyItemTouchHelperCallback(adapter); itemTouchHelper = new ItemTouchHelper(callback); itemTouchHelper.attachToRecyclerView(mRecyclerView); } }上面的重点就是itemTouchHelper,可以看到它的构造函数需要一个Callback,这个Callback将adapter包裹起来,然后把这个itemTouchHelper依附在recycleview上面,这样它就能监听到RecycleView的各种事件,从而封装它们,为我们实现侧滑等效果提供方便的接口 2、先来看看adapter的具体内容
package com.example.myapplication; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.content.Intent; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; import android.widget.TextView; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ItemViewHolder> implements MyItemTouchHelperCallback.ItemTouchHelperAdapter { public Context mContext; private List<String> mDatas = new ArrayList<>(); public static final int ITEM_TYPE_SINGLE_MATERIAL = 0; public static final int ITEM_TYPE_ALBUM_TITLE = 1; public static final int ITEM_TYPE_ALBUM_ITEM = 2; private int lastSelectPos = -1; public MyAdapter(Context context, List<String> mdatas) { this.mContext = context; this.mDatas = mdatas; 4000 } @Override public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = null; if (viewType == ITEM_TYPE_SINGLE_MATERIAL) view = LayoutInflater.from(mContext).inflate(R.layout.single_material_item, parent, false); else if (viewType == ITEM_TYPE_ALBUM_TITLE) view = LayoutInflater.from(mContext).inflate(R.layout.album_title_item, parent, false); else view = LayoutInflater.from(mContext).inflate(R.layout.album_item, parent, false); return new ItemViewHolder(view, viewType); } @Override public void onBindViewHolder(ItemViewHolder holder, int position) { int viewType = getItemViewType(position); if (viewType == ITEM_TYPE_ALBUM_ITEM) { if (position == mDatas.size() - 1) holder.mDivider.setVisibility(View.GONE); holder.mAlbumTitle.setText(mDatas.get(position - 2)); if (holder.itemView.getScrollX() != 0) { holder.itemView.scrollTo(0, 0); holder.mAlbumDeleteIv.setVisibility(View.VISIBLE); } } } @Override public int getItemViewType(int position) { if (position == 0) return ITEM_TYPE_SINGLE_MATERIAL; else if (position == 1) return ITEM_TYPE_ALBUM_TITLE; else return ITEM_TYPE_ALBUM_ITEM; } @Override public int getItemCount() { if (null == mDatas) { return 2; } return mDatas.size() + 2; } @Override public void onItemMove(int fromPosition, int toPosition) { Collections.swap(mDatas, fromPosition, toPosition); notifyItemMoved(fromPosition, toPosition); } @Override public void onItemDelete(int position) { mDatas.remove(position); notifyItemRemoved(position); } @Override public void onItemChange(int lastSelectPos) { notifyItemChanged(lastSelectPos); } @Override public int getLastSelectItem() { return lastSelectPos; } class ItemViewHolder extends RecyclerView.ViewHolder implements MyItemTouchHelperCallback.ItemTouchHelperViewHolder { ImageView mAlbumIcon; TextView mAlbumTitle; ImageView mAlbumDeleteIv; TextView mAlbumDeleteTv; TextView mDivider; ImageView mSingleMaterialGoIv; int mAnimDistance; ValueAnimator mAnimator; int mDirection = -1; public ItemViewHolder(View itemView, int type) { super(itemView); initView(type); initListener(type); } private void initView(int type) { if (type == ITEM_TYPE_SINGLE_MATERIAL) { mSingleMaterialGoIv = itemView.findViewById(R.id.go_to_single_material_iv); } else if (type == ITEM_TYPE_ALBUM_ITEM) { mAlbumIcon = itemView.findViewById(R.id.album_icon_iv); mAlbumTitle = itemView.findViewById(R.id.album_title_tv); mAlbumDeleteIv = itemView.findViewById(R.id.album_delete_iv); mAlbumDeleteTv = itemView.findViewById(R.id.album_delete_tv); mDivider = itemView.findViewById(R.id.divider); } } public void startAnimation(int direction) { mDirection = direction; mAnimator.start(); } private void initListener(int type) { if (type == ITEM_TYPE_ALBUM_ITEM) { mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); mAnimator.setInterpolator(new DecelerateInterpolator()); mAnimator.setDuration(200); mAnimator.addUpdateListener(animation -> { float ratio = animation.getAnimatedFraction(); if (mDirection == 1) itemView.scrollTo((int) (ratio * mAnimDistance), 0); else itemView.scrollTo((int) ((1 - ratio) * mAnimDistance), 0); }); mAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mDirection == -1) mAlbumDeleteIv.setVisibility(View.VISIBLE); } @Override public void onAnimationStart(Animator animation) { if (mAnimDistance <= 0) mAnimDistance = mAlbumDeleteTv.getWidth(); if (mDirection == 1) mAlbumDeleteIv.setVisibility(View.GONE); } } ); mAlbumDeleteIv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (lastSelectPos >= 0 && lastSelectPos != getAdapterPosition()) { notifyItemChanged(lastSelectPos); } startAnimation(1); lastSelectPos = getAdapterPosition(); } }); mAlbumDeleteTv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onItemDelete(getAdapterPosition()); } }); } else if (type == ITEM_TYPE_SINGLE_MATERIAL) { mSingleMaterialGoIv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } }); } } @Override public void onItemSelect() { //恢复上一次选中的Item mAlbumDeleteIv.setVisibility(View.GONE); if (lastSelectPos != getAdapterPosition()) { onItemChange(lastSelectPos); } } @Override public void onItemClear() { lastSelectPos = getAdapterPosition(); if (itemView.getScrollX() != 0) mAlbumDeleteIv.setVisibility(View.GONE); else mAlbumDeleteIv.setVisibility(View.VISIBLE); } } }
album_item.xml布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="64dp" android:background="#ffffff" android:descendantFocusability="blocksDescendants" android:orientation="horizontal"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="20dp" android:layout_marginStart="20dp"> <ImageView android:id="@+id/album_icon_iv" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginLeft="16dp" android:layout_marginTop="8dp" /> <TextView android:id="@+id/album_title_tv" android:layout_width="wrap_content" android:layout_height="21dp" android:layout_marginLeft="16dp" android:layout_marginTop="22dp" android:layout_toRightOf="@id/album_icon_iv" android:gravity="left" android:text="ddd" android:textColor="#2c2e30" android:textSize="15sp" /> <ImageView android:id="@+id/album_delete_iv" android:layout_width="24dp" android:layout_height="24dp" android:layout_alignParentRight="true" android:layout_marginEnd="20dp" android:layout_marginRight="20dp" android:layout_marginTop="20dp" android:src="@drawable/ic_launcher_background" /> <TextView android:layout_width="match_parent" android:layout_height="1dp" android:id="@+id/divider" android:layout_alignParentBottom="true" android:background="#f7f7f7"> </TextView> </RelativeLayout> <FrameLayout android:layout_width="80dp" android:layout_height="match_parent" android:background="#fd4965"> <TextView android:id="@+id/album_delete_tv" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="删除" android:textColor="#ffffff" android:textSize="15sp" /> </FrameLayout> </LinearLayout>
可以看到这个Adapter就是我们常见的RecycleView的adapter,不同的是利用它的getViewType,我们可以实现一个RecycleView里面有不同的item布局,比如上面的第一个item布局和第二个和剩余的item布局是不一样的,这也是这个adapter的一个优点
3、现在来看一下最重要的MyItemTouchHelperCallback
package com.example.myapplication; import android.graphics.Canvas; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.util.Log; import android.view.View; import android.view.ViewGroup; import java.nio.file.FileAlreadyExistsException; public class MyItemTouchHelperCallback extends ItemTouchHelper.Callback { private ItemTouchHelperAdapter mAdapter; /** * 当前滑动距离 */ private float scrollDistance = 0; private boolean isNeedRecover = true; private boolean isCanScrollLeft = false; private boolean isCanScrollRight = false; /** * 删除按钮宽度 */ private int deleteBtnWidth = 0; public MyItemTouchHelperCallback(ItemTouchHelperAdapter adapter) { this.mAdapter = adapter; } @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; return makeMovementFlags(dragFlags, swipeFlags); } @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { if (viewHolder.getItemViewType() != target.getItemViewType()) { return false; } mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition()); return true; } @Override public boolean isItemViewSwipeEnabled() { return true; } @Override public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) { //设置滑动删除最大距离,1.5代表是itemview宽度的1.5倍,目的是不让它删除 return 1.5f; } @Override public float getSwipeEscapeVelocity(float defaultValue) { //设置滑动速度,目的是不让它进入onSwiped return defaultValue * 100; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { //mAdapter.onItemDelete(viewHolder.getAdapterPosition()); } @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { if (deleteBtnWidth <= 0) deleteBtnWidth = getSlideLimitation(viewHolder); int currentScroll = viewHolder.itemView.getScrollX(); if (dX < 0 && isCanScrollLeft && currentScroll <= deleteBtnWidth) { dX = Math.abs(dX) <= deleteBtnWidth ? dX : -deleteBtnWidth; if (!isNeedRecover) { int newScroll = deleteBtnWidth + (int) dX; newScroll = newScroll <= currentScroll ? currentScroll : newScroll; viewHolder.itemView.scrollTo(newScroll, 0); } else { viewHolder.itemView.scrollTo(-(int) dX, 0); scrollDistance = dX; } } else if (dX > 0 && isCanScrollLeft) { //可以左滑的情况下往右滑,恢复item位置 viewHolder.itemView.scrollTo(0, 0); scrollDistance = 0; } else if (dX > 0 && isCanScrollRight && currentScroll >= 0) { if (!isNeedRecover) { dX = Math.abs(dX) <= Math.abs(currentScroll) ? dX : currentScroll; viewHolder.itemView.scrollTo((int) dX, 0); } else { dX = Math.abs(dX) <= deleteBtnWidth ? dX : deleteBtnWidth; viewHolder.itemView.scrollTo(deleteBtnWidth - (int) dX, 0); scrollDistance = dX; } } else if (dX < 0 && isCanScrollRight) { //可以右滑的情况下往左滑,恢复item位置 viewHolder.itemView.scrollTo(deleteBtnWidth, 0); scrollDistance = deleteBtnWidth; } } else { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } } /** * 获取删除按钮的宽度 */ public int getSlideLimitation(RecyclerView.ViewHolder viewHolder) { ViewGroup viewGroup = (ViewGroup) viewHolder.itemView; return viewGroup.getChildAt(1).getLayoutParams().width; } @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { //ACTION_DOWN首先会调用这个,然后再调用onChildDraw if (viewHolder instanceof ItemTouchHelperViewHolder) { ItemTouchHelperViewHolder itemTouchHelperViewHolder = (ItemTouchHelperViewHolder) viewHolder; itemTouchHelperViewHolder.onItemSelect(); isNeedRecover = true; scrollDistance = 0; isCanScrollLeft = false; isCanScrollRight = false; if (viewHolder.itemView.getScrollX() > 0) isCanScrollRight = true; else isCanScrollLeft = true; } } else { //ACTION_UP会首先进入这里,然后再执行recover animation if (Math.abs(scrollDistance) >= deleteBtnWidth / 2) { isNeedRecover = false; } } super.onSelectedChanged(viewHolder, actionState); } @Override public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); //滑动结束后触发 if (viewHolder instanceof ItemTouchHelperViewHolder) { ItemTouchHelperViewHolder itemTouchHelperViewHolder = (ItemTouchHelperViewHolder) viewHolder; itemTouchHelperViewHolder.onItemClear(); if (viewHolder.itemView.getScrollX() >= deleteBtnWidth / 2) viewHolder.itemView.scrollTo(deleteBtnWidth, 0); else viewHolder.itemView.scrollTo(0, 0); } } public interface ItemTouchHelperViewHolder { void onItemSelect(); void onItemClear(); } ac12 public interface ItemTouchHelperAdapter { void onItemMove(int fromPosition, int toPosition); void onItemDelete(int position); void onItemChange(int lastSelectPos); int getLastSelectItem(); } }
这个就是我们实现QQ侧滑效果最重要的地方了,我们来具体介绍一下:
1) ItemTouchHelperCallback支持滑动和拖拽,默认这两个都是开启的,如果想关掉,可以覆盖它的方法来禁用
2) 不过ItemTouchHelperCallback支持的水平滑动删除是当你手指滑动距离超过recycleView宽度一半或者你滑动速度超过最大速度都会触发onSwiped,即删除item操作,具体可以看一下ItemTouchHelper这个类的源码:
private int checkHorizontalSwipe(ViewHolder viewHolder, int flags) { if ((flags & (LEFT | RIGHT)) != 0) { final int dirFlag = mDx > 0 ? RIGHT : LEFT; if (mVelocityTracker != null && mActivePointerId > -1) { mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND, mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity)); final float xVelocity = mVelocityTracker.getXVelocity(mActivePointerId); final float yVelocity = mVelocityTracker.getYVelocity(mActivePointerId); final int velDirFlag = xVelocity > 0f ? RIGHT : LEFT; final float absXVelocity = Math.abs(xVelocity); if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag && absXVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) && absXVelocity > Math.abs(yVelocity)) { return velDirFlag; } } final float threshold = mRecyclerView.getWidth() * mCallback .getSwipeThreshold(viewHolder); if ((flags & dirFlag) != 0 && Math.abs(mDx) > threshold) { return dirFlag; } } return 0; }
备注:
1)这个函数就是判断水平滑动是否触发删除操作,可以看到有两个依据,一个是mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity),这个是获取触发删除的最小滑动速度,如果滑动速度大于它会触发删除;另一个是mCallback.getSwipeThreshold(viewHolder),这个是获取触发删除的滑动距离,默认是RecycleView宽度的一半。所以一旦这两个条件的任何一个满足就会触发删除操作
2)那触发删除操作会有什么影响呢?问题就来了,触发后ItemTouchHelper会把当前item的移动距离设为RecycleView的宽度大小,这样你下次滑动的时候就会出问题了,因为你得先再触发一次删除操作才会把item的移动距离恢复,然后你才可以继续左右滑动,不然你会看到它一动不动
3)但是我们若想实现QQ侧滑效果,就不能让它触发删除操作,因为我们只是要展示删除按钮而已,所以解决办法就是覆盖触发删除的两个条件:
@Override public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) { //设置滑动删除最大距离,1.5代表是itemview宽度的1.5倍,目的是不让它删除 return 1.5f; } @Override public float getSwipeEscapeVelocity(float defaultValue) { //设置滑动速度,目的是不让它进入onSwiped return defaultValue * 100; }4)经过上面的操作我们就能在onChildDraw里面实现侧滑展示删除按钮的逻辑了,具体可以看一下上面的代码,因为onChildDraw给我们的距离是绝对距离(当前位置与第一次按下位置的位移向量),所以我们要选择scrollTo来改变位置
相关文章推荐
- 自定义view系列(5)--99.99%实现QQ侧滑删除效果
- [置顶] 利用自定义View结合onTouchListener实现QQ侧滑菜单效果
- ViewDragHelper实现QQ侧滑效果
- Android自定义view系列之99.99%实现QQ侧滑删除效果实例代码详解
- ViewDragHelper实现QQ条目侧滑效果
- Android 自定义View修炼-仿QQ5.0 的侧滑菜单效果的实现
- 再造 “手机QQ” 侧滑菜单(一)——实现侧滑效果
- 自定义view系列(5)--99.99%实现QQ侧滑删除效果
- 安卓端实现拉出式、抽屉式、仿QQ侧滑菜单效果
- android 使用ViewDragHelper轻松实现DrawerLayout和SlidMenu侧滑效果
- SlidingMenu+ViewPager实现侧滑菜单效果
- 简单实现界面的侧滑效果(Swift)仿QQ侧滑效果
- Android ViewDragHelper实现QQ侧滑边栏
- 详解RecyclerView+BGARefreshLayout实现自定义下拉刷新、上拉加载和侧滑删除效果
- ViewDragHelper 自定义ViewGroup实现QQ5.0侧滑效果
- QQ5.0侧滑效果的实现
- RecycleView实现侧滑删除item
- android实现QQ微信侧滑删除效果
- 仿QQ侧滑效果ViewDragHelper
- Android5.x:RecycleView(一):实现ListView + GridView + StaggeredGridLayou效果