android自定义View之从入门到放弃(五)仿QQ侧滑详解 记录学习

因公司开发需要,以前在实现列表展示后的修改删除等操作都是在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 这就完了 其中的自定义事件回调 一级事件拦截

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章