先看一下效果图
1、左右两个控件都可以自己设置点击事件
2、右侧控件有个动画,300ms
3、右侧的临界点是 右侧控件宽度/2
4、最大的亮点是没有任何侵入,大家可以直接使用源码。看布局代码就能了解
注意点:只能用两个直接子View,并且第一个子View 是具体内容,第二个子View是隐藏控件
reset ()函数 用于复用时候的重置状态
import android.content.Context; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; import android.widget.Scroller; import com.chinabim.smartconstructionsite.utils.Logger; /** * @author 高延荣 * @date 2018/4/24 12:14 * 描述: 项目列表Item,附带有左划功能,显示 不再提醒 按钮 */ public class SwipeLayout extends LinearLayout { private Context mContext; /** * 内容部分 */ private View viewTop; /** * 删除按钮 */ private View viewBottom; /** * top 宽度 */ private int viewTopWidth; /** * bottom 宽度 */ private int viewBottomWidth; /** * 滑动计算器 */ private Scroller scroller; public SwipeLayout(Context context) { super(context); initView(context); } public SwipeLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(context); } private void initView(Context context) { mContext = context; scroller = new Scroller(context); } @Override protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() != 2) { throw new IllegalStateException(SwipeLayout.class.getSimpleName() + "必须有且只有两个子控件"); } viewTop = getChildAt(0); viewBottom = getChildAt(1); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); viewTop.measure(widthMeasureSpec, heightMeasureSpec); } private boolean loadOne; @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (!loadOne) { loadOne = true; viewTopWidth = viewTop.getWidth(); viewBottomWidth = viewBottom.getWidth(); // @see reset() if (viewBottomVisible) { scrollBy(viewBottomWidth, 0); } } } private float xDown; private float xOldDown; private float yDown; private float xMove; private float yMove; private float xDistance; private float xAbsDistance; private float yDistance; private boolean viewBottomVisible; /** * 临界值设为 50px , 保证不会稍微动一点点就会拦截事件 */ private static final float criticalValue = 50; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: if (!scroller.isFinished()) { scroller.forceFinished(true); } curState = TOUCH_DOWN; xOldDown = xDown = ev.getX(); yDown = ev.getY(); break; case MotionEvent.ACTION_MOVE: xMove = ev.getX(); yMove = ev.getY(); xDistance = xMove - xDown; yDistance = yMove - yDown; // 先判断用户是左右滑动还是上下滑动,因为用户可能是先右滑,再左滑,所以使用绝对值,但是右滑不需要进行滚动,所以在 onTouch方法中进行判断 if (Math.abs(xDistance) > criticalValue && Math.abs(xDistance) > Math.abs(yDistance)) { return true; } break; default: } return super.onInterceptTouchEvent(ev); } private static final byte TOUCH_DOWN = 0; private static final byte TOUCH_MOVE = 1; private static final byte TOUCH_UP = 2; private byte curState = TOUCH_DOWN; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_MOVE: xMove = event.getX(); yMove = event.getY(); xDistance = xMove - xDown; yDistance = yMove - yDown; xDown = xMove; xAbsDistance = xMove - xOldDown; if (curState == TOUCH_DOWN) { if (Math.abs(xDistance) > Math.abs(yDistance)) { curState = TOUCH_MOVE; } } // 当移动时,判断viewBottom状态,如果显示,则只能右拉,如果不显示则只能左拉 if (curState == TOUCH_MOVE) { if (viewBottomVisible) { if (xAbsDistance >= 0 && Math.abs(xAbsDistance) <= viewBottomWidth) { scrollBy(-(int) xDistance, 0); } } else { if (xAbsDistance <= 0 && Math.abs(xAbsDistance) <= viewBottomWidth) { scrollBy(-(int) xDistance, 0); } } } return true; // 抬起时,直接scrollBy 很生硬,使用 scroller case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (curState == TOUCH_MOVE) { curState = TOUCH_UP; int moveWidth; if (getScrollX() >= viewBottomWidth / 2) { viewBottomVisible = true; moveWidth = viewBottomWidth - getScrollX(); } else { viewBottomVisible = false; moveWidth = -getScrollX(); } if (onBottomViewChangedListener != null) { onBottomViewChangedListener.onBottomViewChanged(viewBottomVisible); } if (moveWidth != 0) { scroller.startScroll(getScrollX(), 0, moveWidth, 0, 300); postInvalidate(); } return true; } break; default: } return super.onTouchEvent(event); } @Override public void computeScroll() { super.computeScroll(); if (scroller.computeScrollOffset() && curState == TOUCH_UP) { int v = scroller.getCurrX(); //执行 scrollTo if (v < 0) { v = 0; } scrollTo(v, 0); postInvalidate(); } } ////////////////////////////////////////////////////////////////////////////// /** * 在RecyclerView中,使用SparseArrayCompat<Boolean> 记录当前 position 对应的 显示状态 */ public interface OnBottomViewChangedListener { void onBottomViewChanged(boolean viewBottomVisible); } public OnBottomViewChangedListener getOnBottomViewChangedListener() { return onBottomViewChangedListener; } private OnBottomViewChangedListener onBottomViewChangedListener; public void setOnBottomViewChangedListener(OnBottomViewChangedListener onBottomViewChangedListener) { this.onBottomViewChangedListener = onBottomViewChangedListener; } /** * 重置考虑两种情况 * 1、控件已存在,那这时候 viewBottomWidth != 0,直接 reset就可以 * 2、控件不存在,那这时候的控件只是在View树存在对象,即只是完成了 onFinishInflate() 函数,但是还未走测量等方法,这时候就需要在 * onLayout内对控件进行布局设置。 */ public void reset(boolean viewBottomVisible) { this.viewBottomVisible = viewBottomVisible; if (viewBottomWidth == 0) { return; } if (viewBottomVisible) { scrollBy(getScrollX() == 0 ? viewBottomWidth : 0, 0); } else { scrollBy(getScrollX() != 0 ? -getScrollX() : 0, 0); } } }
Demo
<?xml version="1.0" encoding="utf-8"?><!-- 项目列表中,group --> <你的路径.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp" android:layout_marginTop="@dimen/dp_4" android:orientation="horizontal"> <RelativeLayout android:clickable="true" android:id="@+id/topView" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1"> <TextView android:id="@+id/itemTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="@dimen/dp_20" android:maxLines="1" android:text="项目名称" android:textColor="@color/_000" /> <TextView android:id="@+id/itemNumber" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="@dimen/dp_20" android:src="@drawable/arrows_bottom" android:text="编号:123456" android:textColor="@color/_000" /> </RelativeLayout> <TextView android:clickable="true" android:id="@+id/bottomView" android:layout_width="100dp" android:layout_height="match_parent" android:background="@color/_DE1E25" android:gravity="center" android:text="不再显示" android:textColor="@color/_FFF" /> </com.chinabim.smartconstructionsite.ui.extend.SwipeLayout>这样就可以了,大家在 getView里面找到控件,然后设置监听。
//////////////////////////////////////////////////////////////////
附带 AC 和 Adapter代码
AC
import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.widget.FrameLayout; import android.widget.TextView; import com.chad.library.adapter.base.entity.MultiItemEntity; import com.chinabim.smartconstructionsite.R; import com.chinabim.smartconstructionsite.adapter.ProjectListAdapter; import com.chinabim.smartconstructionsite.entity.ProjectListGroup; import com.chinabim.smartconstructionsite.entity.ProjectListItem; import com.chinabim.smartconstructionsite.feature.BaseActivity; import java.util.ArrayList; import java.util.Random; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import static com.chad.library.adapter.base.BaseQuickAdapter.SLIDEIN_RIGHT; /** * @author 高延荣 * @date 2018/4/23 16:42 * 描述: 项目列表 */ public class ProjectListActivity extends BaseActivity { @BindView(R.id.back) FrameLayout back; @BindView(R.id.title) TextView title; @BindView(R.id.recyclerView) RecyclerView recyclerView; /** * 数据集合 */ private ArrayList<MultiItemEntity> list; /** * 适配器 */ private ProjectListAdapter adapter; @Override protected int initLayoutID() { return R.layout.activity_project_list; } @Override protected void initContentView() { title.setText("项目列表"); list = generateData(); adapter = new ProjectListAdapter(list); LinearLayoutManager manager = new LinearLayoutManager(this); recyclerView.setLayoutManager(manager); recyclerView.setAdapter(adapter); adapter.openLoadAnimation(SLIDEIN_RIGHT); // adapter.expandAll(); } private ArrayList<MultiItemEntity> generateData() { Random random = new Random(); ArrayList<MultiItemEntity> res = new ArrayList<>(); String[] groups = {"我创建的项目", "我加入的项目"}; String[] items = {"长寿村旧村改造二期工程", "二通厂场地建设", "马家堡东路金胜嘉谊家园", "中国电信大厦项目工程组", "中白产业园项目", "长寿村旧村改造二期工程", "二通厂场地建设", "马家堡东路金胜嘉谊家园", "中国电信大厦项目工程组", "中白产业园项目", "长寿村旧村改造二期工程", "二通厂场地建设", "马家堡东路金胜嘉谊家园", "中国电信大厦项目工程组", "中白产业园项目"}; String[] nums = {"25003", "30147", "56244", "95270", "46380"}; int groupCount = groups.length; int itemCount = items.length; for (int i = 0; i < groupCount; i++) { ProjectListGroup group = new ProjectListGroup(groups[i]); for (int k = 0; k < itemCount; k++) { ProjectListItem item = new ProjectListItem(items[random.nextInt(5)], nums[random.nextInt(5)],i * itemCount + k); group.addSubItem(item); } res.add(group); } return res; } @OnClick(R.id.back) public void onViewClicked() { finish(); } }
Adapter
import android.support.v4.util.SparseArrayCompat; import android.view.View; import android.widget.TextView; import com.chad.library.adapter.base.BaseMultiItemQuickAdapter; import com.chad.library.adapter.base.BaseViewHolder; import com.chad.library.adapter.base.entity.MultiItemEntity; import com.chinabim.smartconstructionsite.R; import com.chinabim.smartconstructionsite.entity.ProjectListGroup; import com.chinabim.smartconstructionsite.entity.ProjectListItem; import com.chinabim.smartconstructionsite.ui.extend.SwipeLayout; import com.chinabim.smartconstructionsite.utils.Tool; import java.util.List; /** * @author 高延荣 * @date 2018/4/23 18:32 * 描述: */ public class ProjectListAdapter extends BaseMultiItemQuickAdapter<MultiItemEntity, BaseViewHolder> { /** * 当前条目类型,这里0表示group */ public static final int TYPE_LEVEL_0 = 0; /** * 当前条目类型,这里1表示group下的Item */ public static final int TYPE_LEVEL_1 = 1; /** * Same as QuickAdapter#QuickAdapter(Context,int) but with * some initialization data. * * @param data A new list is created out of this one to avoid mutable list */ public ProjectListAdapter(List<MultiItemEntity> data) { super(data); // 设置条目类型 及 对应的布局文件样式 addItemType(TYPE_LEVEL_0, R.layout.project_list_group); addItemType(TYPE_LEVEL_1, R.layout.project_list_item); } @Override protected void convert(final BaseViewHolder helper, final MultiItemEntity item) { switch (helper.getItemViewType()) { case TYPE_LEVEL_0: final ProjectListGroup group = (ProjectListGroup) item; helper.setText(R.id.groupTitle, group.title) .setImageResource(R.id.groupArrows, group.isExpanded() ? R.drawable.arrows_bottom : R.drawable.arrows_right); final int pos = helper.getAdapterPosition(); View viewLine = helper.getView(R.id.viewLine); viewLine.setVisibility(pos != 0 ? View.VISIBLE : View.GONE); helper.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (group.isExpanded()) { collapse(pos); } else { expand(pos); } } }); break; case TYPE_LEVEL_1: final ProjectListItem item1 = (ProjectListItem) item; helper.setText(R.id.itemTitle, item1.name) .setText(R.id.itemNumber, item1.number); final int position = item1.position; SwipeLayout swipeLayout = (SwipeLayout) helper.itemView; swipeLayout.reset(sparseArrayCompat.get(position, false)); swipeLayout.setOnBottomViewChangedListener(new SwipeLayout.OnBottomViewChangedListener() { @Override public void onBottomViewChanged(boolean viewBottomVisible) { sparseArrayCompat.append(position, viewBottomVisible); } }); helper.getView(R.id.topView).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Tool.showToastCenter(item1.name); } }); helper.getView(R.id.bottomView).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Tool.showToastCenter("不再提示"); } }); break; default: } } private SparseArrayCompat<Boolean> sparseArrayCompat = new SparseArrayCompat<>(); }
ProjectListGroup
import com.chad.library.adapter.base.entity.AbstractExpandableItem; import com.chad.library.adapter.base.entity.MultiItemEntity; import com.chinabim.smartconstructionsite.adapter.ProjectListAdapter; /** * @author 高延荣 * @date 2018/4/24 10:35 * 描述: ProjectListActivity 项目列表中,项目组,既 自由项目,加入项目 */ public class ProjectListGroup extends AbstractExpandableItem<ProjectListItem> implements MultiItemEntity { public ProjectListGroup(String title) { this.title = title; } public String title; @Override public int getLevel() { return 0; } @Override public int getItemType() { return ProjectListAdapter.TYPE_LEVEL_0; } }
ProjectListItem
import com.chad.library.adapter.base.entity.MultiItemEntity; import com.chinabim.smartconstructionsite.adapter.ProjectListAdapter; /** * @author 高延荣 * @date 2018/4/24 10:36 * 描述: ProjectListActivity 项目列表中,项目item */ public class ProjectListItem implements MultiItemEntity { public String name; public String number; public int position; public ProjectListItem(String name, String number, int position) { this.name = name; this.number = number; this.position = position; } @Override public int getItemType() { return ProjectListAdapter.TYPE_LEVEL_1; } }