Android自定義View(四)側滑佈局

weixin_34138377發表於2018-08-09

利用Scroller來寫一個側滑佈局效果如下:

5872156-0e37c2a8252102b4.gif
ezgif-5-2511a3329a.gif

1.自定義一個Viewgroup在構造方法初始化引數

        mScroller = new Scroller(context);
        //滑動臨界值
        ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(viewConfiguration);

2.在onMeasure方法中測量子孩子大小

        if (!mIsMeasure) {
            for (int i = 0; i < getChildCount(); i++) {
                View view = getChildAt(i);
                measureChild(view, widthMeasureSpec, heightMeasureSpec);
            }
            mIsMeasure = true;
        }

3.在onLayout佈局中初始化子孩子的位置裡面有三個view分別是文字內容,置頂以及刪除

  @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            if (i == 0) {
                mTextWidth = view.getMeasuredWidth();
                view.layout(0, 0, mTextWidth, view.getMeasuredHeight());
            } else if (i == 1) {
              //置頂文字的寬度
                mStickWidth = view.getMeasuredWidth();
                view.layout(mTextWidth, 0, mTextWidth + mStickWidth, view.getMeasuredHeight());

            } else if (i == 2) {
          //刪除文字的寬度
                mDeleteWidth = view.getMeasuredWidth();
                view.layout(mTextWidth + mStickWidth, 0, mTextWidth + mStickWidth + mDeleteWidth, view.getMeasuredHeight());
            }
        }
    }

4.在onInterceptTouchEvent方法中處理滑動事件,如果是橫向滑動我們就攔截事件自己去處理事件

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownX = (int) ev.getRawX();
                break;
            case MotionEvent.ACTION_MOVE:
                mMovex = (int) ev.getRawX();
                float diff = Math.abs(mMovex - mDownX);
                // 當手指拖動值大於TouchSlop值時,認為應該進行滾動,攔截子控制元件的事件
                mLastMove = mMovex;
                if (diff > mTouchSlop) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

5.在onTouchEvent方法中處理滑動事件,用ScrollBy方法來移動控制元件,移動過程中對移動範圍進行限制
move事件中:

               mMovex = (int) event.getX();
                scrollBy(mLastMove - mMovex, 0);
                if (getScrollX() <= 0) {
                    scrollTo(0, 0);
                    return true;
                }
                if (getScrollX() >= mDeleteWidth + mStickWidth) {
                    scrollTo(mDeleteWidth + mStickWidth, 0);
                    return true;
                }
                mLastMove = mMovex;

當手指抬起的時候根據移動的位置來判斷是否是開啟還是關閉getScrollX()方法可以得到移動的x值,大於置頂和刪除文字寬度一半時就是開啟狀態否則就是關閉

 if (getScrollX() >= mDeleteWidth) {//open
      dx = mDeleteWidth + mStickWidth - getScrollX();
     if (mOnStateChangeListener != null) {
        mOnStateChangeListener.onOpen(this);
      }
   } else {//close
      dx = 0 - getScrollX();
       mOnStateChangeListener.onClose(this);
    }
 mScroller.startScroll(getScrollX(), 0, dx, 0);
 invalidate();

6.在配合Recycleview使用的時候要注意的地方:

在onTouchEvent方法中move事件要請求父控制元件不要攔截否則收不到up事件

 //請求父容器不要攔截事件
getParent().requestDisallowInterceptTouchEvent(true);

在側滑時一個條目時其他條目都要關閉這裡利用adapter進行對當前滑動條目的記錄和已經開啟的條目記錄,然後在move的時候進行判斷

 case MotionEvent.ACTION_MOVE:
    if (getAdapter().getOpenView() != null && getAdapter().getOpenView() != this) {
       getAdapter().getOpenView().close();
       getAdapter().setOpenView(null);
       return true;
       }
     if (getAdapter().getCurrentDeleteView() !=null && getAdapter().getCurrentDeleteView() != this) {
        return true;
     }

完整程式碼:

public class DeleteView extends ViewGroup {

    private Scroller mScroller;
    private int mDownX;
    private int mMovex;
    private int mTouchSlop;
    private boolean mIsMeasure = false;
    private int mTextWidth;
    private int mStickWidth;
    private int mDeleteWidth;
    private int mLastMove;

    public DeleteView(Context context) {
        this(context, null);
    }

    public DeleteView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
        //滑動臨界值
        ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(viewConfiguration);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (!mIsMeasure) {
            for (int i = 0; i < getChildCount(); i++) {
                View view = getChildAt(i);
                measureChild(view, widthMeasureSpec, heightMeasureSpec);
            }
            mIsMeasure = true;
        }
    }
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            if (i == 0) {
                mTextWidth = view.getMeasuredWidth();
                view.layout(0, 0, mTextWidth, view.getMeasuredHeight());
            } else if (i == 1) {
                mStickWidth = view.getMeasuredWidth();
                view.layout(mTextWidth, 0, mTextWidth + mStickWidth, view.getMeasuredHeight());

            } else if (i == 2) {
                mDeleteWidth = view.getMeasuredWidth();
                view.layout(mTextWidth + mStickWidth, 0, mTextWidth + mStickWidth + mDeleteWidth, view.getMeasuredHeight());

            }

        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownX = (int) ev.getRawX();
                break;
            case MotionEvent.ACTION_MOVE:
                mMovex = (int) ev.getRawX();
                float diff = Math.abs(mMovex - mDownX);
                // 當手指拖動值大於TouchSlop值時,認為應該進行滾動,攔截子控制元件的事件
                mLastMove = mMovex;
                if (diff > mTouchSlop) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {

            case MotionEvent.ACTION_MOVE:
                if (getAdapter().getOpenView() != null && getAdapter().getOpenView() != this) {
                    getAdapter().getOpenView().close();
                    getAdapter().setOpenView(null);
                    return true;
                }
                if (getAdapter().getCurrentDeleteView() !=null && getAdapter().getCurrentDeleteView() != this) {
                    return true;
                }
                getAdapter().setCurrentDeleteView(this);
                //請求父容器不要攔截事件
                getParent().requestDisallowInterceptTouchEvent(true);
                mMovex = (int) event.getX();
                scrollBy(mLastMove - mMovex, 0);
                if (getScrollX() <= 0) {
                    scrollTo(0, 0);
                    return true;
                }
                if (getScrollX() >= mDeleteWidth + mStickWidth) {
                    scrollTo(mDeleteWidth + mStickWidth, 0);
                    return true;
                }
                mLastMove = mMovex;
                break;
            case MotionEvent.ACTION_UP:
                LogUtils.LogE("up");
                int dx = 0;
                if (getScrollX() >= mDeleteWidth) {//open
                    dx = mDeleteWidth + mStickWidth - getScrollX();
                    if (mOnStateChangeListener != null) {
                        mOnStateChangeListener.onOpen(this);
                    }
                } else {//close
                    dx = 0 - getScrollX();
                    mOnStateChangeListener.onClose(this);
                }

                mScroller.startScroll(getScrollX(), 0, dx, 0);
                invalidate();
                getAdapter().setCurrentDeleteView(null);
                break;
        }
        return true;
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }

    }

    public interface onStateChangeListener {
        void onOpen(DeleteView deleteView);

        void onClose(DeleteView deleteView);
    }

    private onStateChangeListener mOnStateChangeListener;

    public void setOnStateChangeListener(onStateChangeListener onStateChangeListener) {
        mOnStateChangeListener = onStateChangeListener;
    }

    public void open() {
        scrollTo(mStickWidth + mDeleteWidth, 0);
    }

    public void close() {
        scrollTo(0, 0);
    }

    /**
     * 得到介面卡
     *
     * @return
     */
    private DeleteAdapter getAdapter() {
        return (DeleteAdapter) ((RecyclerView) getParent()).getAdapter();
    }

Adapter程式碼:

public class DeleteAdapter extends RecyclerView.Adapter {


    private List<CartModel> mDatas;

    private DeleteView mDeleteView;
    private DeleteView mOpenView;

    public DeleteAdapter(List<CartModel> list) {
        mDatas = list;
    }


    public void setData(List<CartModel> list) {
        mDatas = list;
        notifyDataSetChanged();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_delete, parent, false);
        MyHolder holder = new MyHolder(view);

        return holder;
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
        MyHolder myHolder = (MyHolder) holder;
        myHolder.setItem(position);

        ((DeleteView) holder.itemView).setOnStateChangeListener(new DeleteView.onStateChangeListener() {
            @Override
            public void onOpen(DeleteView deleteView) {
                mOpenView = deleteView;
            }

            @Override
            public void onClose(DeleteView deleteView) {

            }
        });

        myHolder.delete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(v.getContext(),"刪除"+position,Toast.LENGTH_SHORT).show();
                mDatas.remove(position);
                getOpenView().close();
                notifyDataSetChanged();
            }
        });
        myHolder.stick.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                CartModel cartModel = mDatas.get(position);
                Toast.makeText(v.getContext(),"置頂"+position,Toast.LENGTH_SHORT).show();
                mDatas.remove(cartModel);
                mDatas.add(0,cartModel);
                getOpenView().close();
                notifyDataSetChanged();
            }
        });
    }

    /**
     * 得到當前滑動的view
     *
     * @return
     */
    public DeleteView getCurrentDeleteView() {
        return mDeleteView;
    }

    /**
     * 得到當前滑動的view
     *
     * @return
     */
    public void setCurrentDeleteView(DeleteView deleteView) {
        this.mDeleteView = deleteView;
    }

    public DeleteView getOpenView() {
        return mOpenView;
    }

    public void setOpenView(DeleteView openView) {
        mOpenView = openView;
    }

    @Override
    public int getItemCount() {//加的一是腳步局
        return mDatas.size();
    }


    class MyHolder extends RecyclerView.ViewHolder {
        TextView title;
        TextView stick;
        TextView delete;

        public MyHolder(View itemView) {
            super(itemView);
            title = itemView.findViewById(R.id.tv_title);
            stick = itemView.findViewById(R.id.tv_stick);
            delete = itemView.findViewById(R.id.tv_delete);

        }

        public void setItem(int position) {
            CartModel cartModel = mDatas.get(position);
            title.setText(cartModel.getName());

        }
    }
}

相關文章