因公司开发需要,以前在实现列表展示后的修改删除等操作都是在recyclerview的长按事件中进行实现的,慢慢的接触到了自定义View 就想着自定义一个仿QQ的实现侧滑的效果,期间也查看了很多大神的文档,毕竟刚开始学习自定义view,所以还是有些生疏。。。。废话不多哔哔,开始开始
效果图:
首先要实现列表 我们需要适配器,子布局还有数据 我们就来一点一点实现
item布局:
<?xml version="1.0" encoding="utf-8"?>
<com.example.zidingyidemo.Second.SlideLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RelativeLayout
android:id="@+id/ll_content_view"
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:visibility="visible">
<ImageView
android:id="@+id/iv_avatar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/tv_test2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_toEndOf="@id/iv_avatar"
android:text="好友名称"
android:textColor="#000000"
android:textSize="18sp"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/iv_avatar" />
<TextView
android:id="@+id/tv_test3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_test2"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_toEndOf="@id/iv_avatar"
android:maxLines="1"
android:text="内容展示,随便写一些东西测试一下就好"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/iv_avatar" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginStart="10dp"
android:layout_marginTop="15dp"
android:text="昨天"
android:layout_alignParentRight="true"
android:layout_marginLeft="10dp" />
</RelativeLayout>
<LinearLayout
android:layout_width="200dp"
android:layout_height="70dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_toFirst"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/darker_gray"
android:gravity="center"
android:text="置顶"
android:textColor="@android:color/white"
android:textSize="22sp" />
<TextView
android:id="@+id/tv_delete"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/holo_red_light"
android:gravity="center"
android:text="删除"
android:textColor="@android:color/white"
android:textSize="22sp" />
</LinearLayout>
</com.example.zidingyidemo.Second.SlideLayout>
首先我们定义了一个自定义的组件,然后将我们要显示的内容进行填充,其中还包括我们需要的置顶,删除的功能 其中用的textview进行显示
然后来看一下我们的自定义View:
public class SlideLayout extends RelativeLayout {
public SlideLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
}
好了 这样我们的项目就简单的搭建好了 然后我们来一点点的实现效果
private Scroller mScroller;
private View mContentView;
private View mMenuView;
public SlideLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContentView = getChildAt(0);//自定义组件的一级子view->这里为RelativeLayout
mMenuView = getChildAt(1);//自定义组件的二级子view->这里为LinearLayout
}
首先在我们的自定义view初始化的时候 我们将scroller先定义出来 ,然后我们可以重写onFinishInflate()这个方法,这个方法的意思是在自定义View初始化或者xml初始化结束之后调用 然后我们可以在里面拿到一级二级子view
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//将menu布局到右侧不可见(屏幕外)
//720,0,720+400,140
mMenuView.layout(mContentWidth, 0, mContentWidth + mMenuWidth, mMenuHeight);
Log.i("mMenuWidth","@"+mMenuWidth);
Log.i("mContentWidth","@"+mContentWidth);
Log.i("mMenuHeight","@"+mMenuHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
onLayout()是确定View和孩子的位置 然后我们可以在这里指定我们的置顶删除的位置 详细的参数意思我就不说了
private float startX;
private float downX;
private float downY;
@Override
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = x; //获取x的起始坐标
break;
case MotionEvent.ACTION_MOVE:
final float dx = (int) (x - startX);//拿到偏移量
Log.i("ssinstance","@"+dx);
Log.i("ssgetScrollX","@"+getScrollX());
int disX = (int) (getScrollX() - dx); //其中的getScrollx()坐标的获取是根据起始坐标减去移动后View试图左上角的值
Log.i("movemove","@"+disX);
if (disX <= 0) {
disX = 0;
}
scrollTo(Math.min(disX, mMenuWidth), getScrollY());
final float moveX = Math.abs(x - downX);
final float moveY = Math.abs(y - downY);
if (moveX > moveY && moveX > 10f) {
//父布局不要拦截子view的touch事件
getParent().requestDisallowInterceptTouchEvent(true);
}
startX = x;
break;
return true;
}
然后我们重写onTouchEvent()方法 监听当前的x,y坐标的改变
根据上图 我们设置屏幕的宽度为720 高度为140 置顶删除的宽度为400 高度为140
思路:
假设我们当前的按下的x为700 向左进行移动 move的最终值为500 这样他的偏移量就为500-700 = -200
我们的getScrollX()值就为置顶删除的宽度 就为400 然后用400–200 = 600
然后我们进行判断 最终移动的位置就为-400,0
final float moveX = Math.abs(x - downX);
final float moveY = Math.abs(y - downY);
if (moveX > moveY && moveX > 10f) {
//父布局不要拦截子view的touch事件
getParent().requestDisallowInterceptTouchEvent(true);
}
startX = x;
break;
这里的主要意思是先进行判断移动的距离 太小的话监听没必要
case MotionEvent.ACTION_UP:
Log.i("upup","@"+getScrollX());
if (getScrollX() < mMenuWidth / 2) {//判断向左移动的距离
closeMenu();//不显示置顶删除
} else {
openMenu();//显示置顶删除
}
break;
//如果当前方法返回true,拦截事件 并会触发当前控件的onTouchEvent方法 else 继续往下传递
//拦截事件不传递给子view
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercept = false;
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = x;
downY = y;
if (mOnSlideChangeListener != null) {
mOnSlideChangeListener.onClick(this);
}
break;
case MotionEvent.ACTION_MOVE:
final float moveX = Math.abs(x - downX);
if (moveX > 10f) { //对touch事件进行拦截
intercept = true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return intercept;
}
@Override
public void computeScroll() { //当我们调用invalidate(); 时会执行
super.computeScroll();
//当动画执行完成以后,执行新的动画
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
public final void openMenu() {
mScroller.startScroll(getScrollX(), getScrollY(), mMenuWidth - getScrollX(), 0);
invalidate();
if (mOnSlideChangeListener != null) {
mOnSlideChangeListener.onMenuOpen(this);
}
}
public final void closeMenu() {
mScroller.startScroll(getScrollX(), getScrollY(), 0 - getScrollX(), 0);
invalidate();
if (mOnSlideChangeListener != null) {
mOnSlideChangeListener.onMenuClose(this);
}
}
//自定义事件回调
public interface onSlideChangeListener {
void onMenuOpen(SlideLayout slideLayout);
void onMenuClose(SlideLayout slideLayout);
void onClick(SlideLayout slideLayout);
}
public void setOnSlideChangeListener(onSlideChangeListener onSlideChangeListener1) {
this.mOnSlideChangeListener = onSlideChangeListener1;
}
这些很抽象 我也不知道怎么详细介绍 您就自己看看吧
.
完整的自定义View的代码:
public class SlideLayout extends RelativeLayout {
private View mContentView;
private View mMenuView;
private int mMenuWidth;
private int mMenuHeight;
private int mContentWidth;
private Scroller mScroller;
private float startX;
private float downX;
private float downY;
private onSlideChangeListener mOnSlideChangeListener;
public SlideLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContentView = getChildAt(0);//内容的父容器
mMenuView = getChildAt(1);//置顶删除的父容器
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mContentWidth = getMeasuredWidth();//屏幕宽度
// mContentHeight = getMeasuredHeight();
mMenuWidth = mMenuView.getMeasuredWidth();//置顶删除父容器的宽
mMenuHeight = mMenuView.getMeasuredHeight();//置顶删除父容器的高
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//将menu布局到右侧不可见(屏幕外)
//720,0,720+400,140
mMenuView.layout(mContentWidth, 0, mContentWidth + mMenuWidth, mMenuHeight);
Log.i("mMenuWidth","@"+mMenuWidth);
Log.i("mContentWidth","@"+mContentWidth);
Log.i("mMenuHeight","@"+mMenuHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = x;
break;
case MotionEvent.ACTION_MOVE:
final float dx = (int) (x - startX);
Log.i("ssinstance","@"+dx);
Log.i("ssgetScrollX","@"+getScrollX());
int disX = (int) (getScrollX() - dx);
Log.i("movemove","@"+disX);
if (disX <= 0) {
disX = 0;
}
scrollTo(Math.min(disX, mMenuWidth), getScrollY());
final float moveX = Math.abs(x - downX);
final float moveY = Math.abs(y - downY);
if (moveX > moveY && moveX > 10f) {
//父布局不要拦截子view的touch事件
getParent().requestDisallowInterceptTouchEvent(true);
}
startX = x;
// startY = y;
break;
case MotionEvent.ACTION_UP:
Log.i("upup","@"+getScrollX());
if (getScrollX() < mMenuWidth / 2) {
closeMenu();
} else {
openMenu();
}
break;
}
return true;
}
//如果当前方法返回true,拦截事件 并会触发当前控件的onTouchEvent方法 else 继续往下传递
//拦截事件不传递给子view
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercept = false;
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = x;
downY = y;
if (mOnSlideChangeListener != null) {
mOnSlideChangeListener.onClick(this);
}
break;
case MotionEvent.ACTION_MOVE:
final float moveX = Math.abs(x - downX);
if (moveX > 10f) { //对touch事件进行拦截
intercept = true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return intercept;
}
@Override
public void computeScroll() {
super.computeScroll();
//当动画执行完成以后,执行新的动画
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
public final void openMenu() {
mScroller.startScroll(getScrollX(), getScrollY(), mMenuWidth - getScrollX(), 0);
invalidate();
if (mOnSlideChangeListener != null) {
mOnSlideChangeListener.onMenuOpen(this);
}
}
public final void closeMenu() {
mScroller.startScroll(getScrollX(), getScrollY(), 0 - getScrollX(), 0);
invalidate();
if (mOnSlideChangeListener != null) {
mOnSlideChangeListener.onMenuClose(this);
}
}
public interface onSlideChangeListener {
void onMenuOpen(SlideLayout slideLayout);
void onMenuClose(SlideLayout slideLayout);
void onClick(SlideLayout slideLayout);
}
public void setOnSlideChangeListener(onSlideChangeListener onSlideChangeListener1) {
this.mOnSlideChangeListener = onSlideChangeListener1;
}
}
然后来看适配器
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private ArrayList<String> arrayList;
private Context mContext;
private SlideLayout mSlideLayout;
public MyAdapter(Context context, ArrayList<String> dataList) {
this.arrayList = dataList;
this.mContext = context;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MyViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, null));
}
@Override
public void onBindViewHolder(final MyViewHolder myViewHolder, int position) {
myViewHolder.textView.setText(arrayList.get(position));
myViewHolder.contentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(mContext, "item被点击", Toast.LENGTH_SHORT).show();
}
});
// myViewHolder.contentView.setOnLongClickListener(new View.OnLongClickListener() {
// @Override
// public boolean onLongClick(View view) {
// int[] location = new int[2];
// view.getLocationOnScreen(location);
// View view1 = LayoutInflater.from(mContext).inflate(R.layout.item_background_popwindow, null);
// PopupWindow popupWindow = new PopupWindow(view1, 300, 150);
// popupWindow.setContentView(view1);
// popupWindow.setOutsideTouchable(false);
// popupWindow.setFocusable(true);
// popupWindow.showAtLocation(view, Gravity.NO_GRAVITY, (view.getWidth() - popupWindow.getWidth()) / 2, location[1] - popupWindow.getHeight() - 5);
// return false;
// }
// });
myViewHolder.to_first.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//position在remove后会变,所以先把内容取出来
String content = arrayList.get(myViewHolder.getAdapterPosition());
arrayList.remove(myViewHolder.getAdapterPosition());
arrayList.add(0, content);
notifyDataSetChanged();
}
});
myViewHolder.delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
arrayList.remove(myViewHolder.getAdapterPosition());
notifyDataSetChanged();
}
});
mSlideLayout = (SlideLayout) myViewHolder.itemView;
mSlideLayout.setOnSlideChangeListener(new SlideLayout.onSlideChangeListener() {
@Override
public void onMenuOpen(SlideLayout slideLayout) {
mSlideLayout = slideLayout;
}
@Override
public void onMenuClose(SlideLayout slideLayout) {
if (mSlideLayout != null) {
mSlideLayout = null;
}
}
@Override
public void onClick(SlideLayout slideLayout) {
if (mSlideLayout != null) {
mSlideLayout.closeMenu();
}
}
});
}
@Override
public int getItemCount() {
return arrayList.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
private TextView to_first;
private TextView delete;
private RelativeLayout contentView;
public MyViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.tv_test2);
to_first = itemView.findViewById(R.id.tv_toFirst);
delete = itemView.findViewById(R.id.tv_delete);
contentView = itemView.findViewById(R.id.ll_content_view);
}
}
}
这个就不做过多的解释了
actiity:
private void initView() {
recyclerView = findViewById(R.id.recycler);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
for(int i=0;i<20;i++){
data.add("我是第"+i+"个内容");
}
MyAdapter adapter = new MyAdapter(this,data);
recyclerView.setAdapter(adapter);
}
ok 这就完了 其中的自定义事件回调 一级事件拦截