先看一下效果图
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;
/**
* @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)) {
// 请求父类对后续移动事件不进行拦截,这个 flag 会在每一次 down 时被父类自动重置
requestDisallowInterceptTouchEvent(true);
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;
}
}