我们需要做的是一个侧滑菜单,在侧滑菜单的主页面中放置一个RecycleView,其中的item可以左右滑动删除,把两个功能都做了结果发现在一起时候会出现冲突,子布局不能使用,有布局同向滑动的冲突。
这里的RecycleView的滑动删除参考这位大佬的博客 https://blog.csdn.net/xiexuan2007/article/details/53156916
侧滑菜单参考的那位大佬的博客找不到了~~~~~~
我们先贴一下我们的布局代码,看一下自定义View 的嵌套:
<com.example.archermind.testview.util.SlideMean xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SlideMeanActivity">
<LinearLayout
android:layout_width="300dp"
android:layout_height="match_parent"
android:background="@color/wheat"
android:orientation="vertical">
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/rosybrown"
android:orientation="vertical">
<include layout="@layout/toolbar" />
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recy_slide">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
</com.example.archermind.testview.util.SlideMean>
<com.example.archermind.testview.util.SlidingButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_delete"
android:layout_width="80dp"
android:layout_height="40dp"
android:layout_toRightOf="@+id/layout_content"
android:background="@color/red"
android:gravity="center"
android:text="删 除"
android:textColor="#DDFFFFFF"
android:textSize="20dp" />
<RelativeLayout
android:id="@+id/layout_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_name"
android:layout_width="100dp"
android:layout_height="40dp"
android:gravity="center"
android:text="@string/app_name" />
<TextView
android:id="@+id/tv_show"
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/tv_name"
android:gravity="center"
android:maxLines="1"
android:text="@string/app_name" />
</RelativeLayout>
</RelativeLayout>
</com.example.archermind.testview.util.SlidingButtonView>
我们自定义View来处理事件,自定义View SlideMean这个View是一个父布局,自定义View SlidingButtonView是一个子布局,我们的SlidingButtonView需要左右滑动来调出来删除按钮,SlideMean view需要左右滑动出现菜单,在滑动的时候系统不知道你这时候点击的事件是应该触发那一个view去执行。
我们需要在父布局中重写onInterceptTouchEvent的这个方法,这个方法会在OnTouchEvent事件执行之前回调,然后在这里做事件的拦截,onInterceptTouchEvent返回值为true这表示拦截,父布局的OnTouchEvent事件触发执行,如果返回值是false,则表示不拦截,OnTouchEvent不会触发,由子布局去执行触摸事件。
这里大家可以参考一下这位大佬的博客 https://blog.csdn.net/Z_L_P/article/details/53488085,他讲解的两种的处理方法,内部拦截和外部拦截,我做的是外部拦截做分发机制
下面是拦截事件的处理代码,我们在删除按钮菜单有和没有,向左右滑动,有四种情况。无菜单左滑,子布局触发;无菜单右滑,父布局触发;有菜单右滑,子布局触发。
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onInterceptTouchEvent.........ACTION_DOWN");
mFirstX = mLastX = ev.getX();
mFirstY = mLastY = ev.getY();
isIntercept =false;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onInterceptTouchEvent.........ACTION_MOVE"+ev.getX()+"-->"+mFirstX+SlidingButtonView.isOpen);
float deltaX = ev.getX() - mLastX;
float deltaY = ev.getY() - mLastY;
mMove = ev.getX() - mFirstX;
if(mMove > 0 && SlidingButtonView.isOpen && Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY) ){
isIntercept=false;
Log.e("--------","右滑动---菜单");
// return false; //不拦截
}else if(mMove > 0 && !SlidingButtonView.isOpen && Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)){
Log.e("--------","右滑动---无菜单");
isIntercept = true; //拦截父布局执行OnTouchEvent
}else if(mMove < 0 && !SlidingButtonView.isOpen && Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)){
Log.e("--------","左滑动---无菜单");
isIntercept = false;
}else if(Math.abs(deltaX) < Math.abs(deltaY)){
isIntercept = false;
}
// if(Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)){
// Log.e("----------","执行了此事件");
// isIntercept=true;
// }
}
return isIntercept;
}
下面放入两个自定义View的代码:
SlideMean的代码,侧滑菜单
package com.example.archermind.testview.util;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;
import android.widget.TextView;
public class SlideMean extends ViewGroup {
private int mTouchSlop;//被判定为滑动的最小距离
private static final String TAG = "SlidingMenu";
private Scroller mScroller;//弹性滑动
public static boolean isIntercept=false;;
private VelocityTracker mVelocityTracker;//速度跟踪器
private int mScrollWidth;
private float mFirstX;
private float mFirstY;
private float mLastX;
private float mLastY;
private int menuWidth;//菜单的宽度
private int contentWidth;//内容的宽度
private View menu;//菜单
private View content;//内容
private float mMove;
public SlideMean(Context context) {
this(context, null);
}
public SlideMean(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlideMean(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mTouchSlop = ViewConfiguration.getTouchSlop();
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//测量所有的子view
int childCount = getChildCount();
for(int i = 0; i < childCount; i++){
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.d(TAG, "l = " + l + "....." + "t = " + t + "....." + "r = " + r + "....." + "b = " + b);
menu = getChildAt(0);
content = getChildAt(1);
menuWidth = menu.getMeasuredWidth();
contentWidth = content.getMeasuredWidth();
menu.layout(-1 * menuWidth, 0, 0, menu.getMeasuredHeight());
content.layout(0, 0, contentWidth, content.getMeasuredHeight());
Log.d(TAG, "content.getMeasuredWidth() = " + content.getMeasuredWidth()+"-----"+menuWidth);
Log.d(TAG, "content.getMeasuredHeight() = " + content.getMeasuredHeight()+"----"+menu.getMeasuredHeight());
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onInterceptTouchEvent.........ACTION_DOWN");
mFirstX = mLastX = ev.getX();
mFirstY = mLastY = ev.getY();
isIntercept =false;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onInterceptTouchEvent.........ACTION_MOVE"+ev.getX()+"-->"+mFirstX+SlidingButtonView.isOpen);
float deltaX = ev.getX() - mLastX;
float deltaY = ev.getY() - mLastY;
mMove = ev.getX() - mFirstX;
if(mMove > 0 && SlidingButtonView.isOpen && Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY) ){
isIntercept=false;
Log.e("--------","右滑动---菜单");
// return false;
}else if(mMove > 0 && !SlidingButtonView.isOpen && Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)){
Log.e("--------","右滑动---无菜单");
isIntercept = true;
}else if(mMove < 0 && !SlidingButtonView.isOpen && Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)){
Log.e("--------","左滑动---无菜单");
isIntercept = false;
}else if(Math.abs(deltaX) < Math.abs(deltaY)){
isIntercept = false;
}
// if(Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)){
// Log.e("----------","执行了此事件");
// isIntercept=true;
// }
}
return isIntercept;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent.........ACTION_DOWN");
return true;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent.........ACTION_MOVE");
Log.d(TAG, "getScrollX() = " + getScrollX());
//边界判断和处理
if (checkBound(event)) return true;
//手指按在屏幕上滑动的处理
float deltaX = event.getX() - mLastX;
scrollBy((int) (-1 * deltaX), 0);
mLastX = event.getX();
mLastY = event.getY();
break;
// }
case MotionEvent.ACTION_UP: //根据滚动的距离判断怎么还原
Log.d(TAG, "onTouchEvent.........ACTION_UP");
int vx = (int) Math.abs(mVelocityTracker.getXVelocity());
Log.d(TAG, "vx = " + vx);
//快速滑动的处理,未必一定是200
if(vx > 200){
if(event.getX() - mFirstX > 0){ //向右快速滑动
showMenuComplete();
return true;
}else{ //向左快速滑动
hideMenuComplete();
return true;
}
}
//正常滑动的处理
if(getScrollX() > -1 * menuWidth && getScrollX() <= -0.5f * menuWidth){ //拉出过半了
showMenuComplete();
return true;
}
if(getScrollX() > -0.5f * menuWidth && getScrollX() <= 0){ //拉出未过半
hideMenuComplete();
return true;
}
break;
}
return super.onTouchEvent(event);
}
/**
* 完全隐藏menu
*/
private void hideMenuComplete() {
mScroller.startScroll(getScrollX(), 0, 0 - getScrollX(), 0);
invalidate();
}
/**
* 完全展示menu
*/
private void showMenuComplete() {
mScroller.startScroll(getScrollX(), 0, -1 * menuWidth - getScrollX(), 0);
invalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
/**
* 边界的判断和处理
*/
private boolean checkBound(MotionEvent event) {
float tempX = event.getX() - mFirstX;
if(tempX > 0){ //说明手指在向右滑动
if(getScrollX() <= -1 * menuWidth){
scrollTo(-1 * menuWidth, 0);
Log.d(TAG, "已经碰到边界了...不能再往右滑动");
return true;
}
}else{ //说明手指在向左滑动
if(getScrollX() >= 0){
scrollTo(0, 0);
Log.d(TAG, "已经碰到边界了...不能再往左滑动");
return true;
}
}
return false;
}
}
SlidingButtonView的代码
package com.example.archermind.testview.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.TextView;
import com.example.archermind.testview.R;
public class SlidingButtonView extends HorizontalScrollView {
private TextView mTextView_Delete, textView_update;
//右侧的删除按钮的宽度
private int mScrollWidth;
private IonSlidingButtonListener mIonSlidingButtonListener;
public static Boolean isOpen = false;
private Boolean once = false;
float x1 = 0;
float x2 = 0;
float y1 = 0;
float y2 = 0;
float Movex,Movey,Downx,Downy;
public SlidingButtonView(Context context) {
this(context, null);
}
public SlidingButtonView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.setOverScrollMode(OVER_SCROLL_NEVER);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!once) {
mTextView_Delete = (TextView) findViewById(R.id.tv_delete);
//textView_update = findViewById(R.id.tv_update);
once = true;
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
this.scrollTo(0, 0);
//获取水平滚动条可以滑动的范围,即右侧按钮的宽度
mScrollWidth = mTextView_Delete.getWidth();
Log.i("asd", "mScrollWidth:" + mScrollWidth);
}
}
@SuppressLint("LongLogTag")
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.e("ButtonView->onTouchEvent","Down事件");
case MotionEvent.ACTION_MOVE:
Log.e("ButtonView->onTouchEvent","Move事件");
if (mIonSlidingButtonListener != null) {
mIonSlidingButtonListener.onDownOrMove(this);
}
float deltaX = ev.getX() - x1;
scrollBy((int) (-1 * deltaX), 0);
x1 = ev.getX();
y1 = ev.getY();
break;
case MotionEvent.ACTION_UP:
changeScrollx();
case MotionEvent.ACTION_CANCEL:
return true;
default:
break;
}
return super.onTouchEvent(ev);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
//这里设置动画使删除按钮貌似后面冒出来的
// Log.e("显示记录", l + "=====" + mScrollWidth);
// mTextView_Delete.setTranslationX(l - mScrollWidth);
}
/**
* 按滚动条被拖动距离判断关闭或打开菜单
*/
public void changeScrollx() {
if (getScrollX() >= (mScrollWidth / 2)) {
this.smoothScrollTo(mScrollWidth, 0);
isOpen = true;
mIonSlidingButtonListener.onMenuIsOpen(this);
openMenu();
} else {
this.smoothScrollTo(0, 0);
isOpen = false;
}
}
/**
* 打开菜单
*/
public void openMenu() {
if (isOpen) {
return;
}
this.smoothScrollTo(mScrollWidth, 0);
isOpen = true;
mIonSlidingButtonListener.onMenuIsOpen(this);
}
/**
* 关闭菜单
*/
public void closeMenu() {
if (!isOpen) {
return;
}
this.smoothScrollTo(0, 0);
isOpen = false;
}
public void setSlidingButtonListener(IonSlidingButtonListener listener) {
mIonSlidingButtonListener = listener;
}
public interface IonSlidingButtonListener {
//删除菜单打开
void onMenuIsOpen(View view);
void onDownOrMove(SlidingButtonView slidingButtonView);
//删除菜单关闭或者该处被移除
}
}
recycleview还使用了Adapteri将删除菜单的判断写到了这里,大家也可以直接在自定义View中进行处理,这样可以减少一定的耦合度。附上Adapter代码:
package com.example.archermind.testview.util;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.example.archermind.testview.R;
import java.util.ArrayList;
import java.util.List;
public class SlideAdapter extends RecyclerView.Adapter<SlideAdapter.MyViewHolder> implements SlidingButtonView.IonSlidingButtonListener {
private List<String> list_data = new ArrayList<>();
private SlidingButtonView mMenu = null;
SlidingButtonView.IonSlidingButtonListener mIonSlidingButtonListener;
private IonSlidingViewClickListener mIDeleteBtnClickListener;
private Context mContext;
public SlideAdapter(List<String> list, Context context){
list_data = list;
mContext = context;
mIDeleteBtnClickListener = (IonSlidingViewClickListener) context;
}
@NonNull
@Override
public SlideAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
final View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.spsn_item, viewGroup, false);
SlideAdapter.MyViewHolder holder = new SlideAdapter.MyViewHolder(v);
return holder;
}
@Override
public void onBindViewHolder(@NonNull final SlideAdapter.MyViewHolder myViewHolder, int i) {
myViewHolder.tv_name.setText(list_data.get(i));
myViewHolder.tv_show.setText("zwn:"+list_data.get(i));
myViewHolder.layout_content.getLayoutParams().width = Utils.getScreenWidth(mContext);
myViewHolder.tv_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int n = myViewHolder.getLayoutPosition();
mIDeleteBtnClickListener.onDeleteBtnCilck(v, n);
Log.e("---=","delete");
}
});
}
@Override
public int getItemCount() {
return list_data.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder{
private TextView tv_name,tv_show,tv_delete;
private ViewGroup layout_content;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
tv_name = itemView.findViewById(R.id.tv_name);
tv_show = itemView.findViewById(R.id.tv_show);
tv_delete = itemView.findViewById(R.id.tv_delete);
layout_content = (ViewGroup) itemView.findViewById(R.id.layout_content);
((SlidingButtonView) itemView).setSlidingButtonListener(SlideAdapter.this);
}
}
/**
* 删除菜单打开信息接收
*/
@Override
public void onMenuIsOpen(View view) {
mMenu = (SlidingButtonView) view;
}
/**
* 滑动或者点击了Item监听
* @param slidingButtonView
*/
@Override
public void onDownOrMove(SlidingButtonView slidingButtonView) {
if(menuIsOpen()){
if(mMenu != slidingButtonView){
closeMenu();
}
}
}
/**
* 关闭菜单
*/
public void closeMenu() {
mMenu.closeMenu();
mMenu = null;
}
/**
* 判断是否有菜单打开
*/
public Boolean menuIsOpen() {
if(mMenu != null){
Log.i("asd","mMenu不为null");
return true;
}
Log.i("asd","mMenu为null");
return false;
}
public interface IonSlidingViewClickListener {
void onDeleteBtnCilck(View view,int position);
}
public void removeData(int position){
list_data.remove(position);
notifyItemRemoved(position);
}
}
工具类中的获取屏幕宽度的方法 public static int getScreenWidth(Context context) { WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE ); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics( outMetrics); return outMetrics .widthPixels ; }