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 這就完了 其中的自定義事件回調 一級事件攔截

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