Android RecycleView 实现滑动删除按钮


转载请标明出处:
http://blog.csdn.net/tyzlmjj/article/details/47060817
本文出自:【M家杰的博客】

概述
最近刚开始做安卓项目,制作给了一份演示稿要求做一个列表滑动删除按钮的效果,网上很多类似的效果,但多是用ListView,而且一些细节上无法完全符合制作的要求!无奈之下只能自己静下心来写一个。使用了RecyclerView完成。


前奏

实现的功能细节:

  • 滑动时删除按钮显示在底下,非后方平移出现(这有什么区别吗!真想把制作打一顿)
  • 内容项和删除按钮都可以点击
  • 当有滑动菜单显示时,点击任意项关闭滑动菜单
  • 当有滑动菜单显示时,滑动别的项关闭之前的滑动菜单
  • 删除的视觉动画效果(偷懒直接用了默认的)

Demo展示:

Android RecycleView 实现滑动删除按钮_第1张图片

敲代码前的思考

自定义实现这个功能必定要自定义View,那么监听滑动事件好像变成必然,但是对于像我这样的菜鸟对滑动事件的重写感觉亚历山大,于是决定自定义View继承水平滚动条,这样就简单多了!

需要的引用

compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:recyclerview-v7:22.2.1'

开始敲代码

首先搭建布局

  • 主布局就放了一个RecyclerView
"http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    .support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:background="#EEEEEE"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:overScrollMode="never"
        />
  • Item布局
    com.mjj.slidingbutton.SlidingButtonView 就是自定义的View,继承水平滚动条
<com.mjj.slidingbutton.SlidingButtonView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:background="@android:color/white"
    android:layout_marginBottom="1dp"
    >
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        <TextView
            android:id="@+id/tv_delete"
            android:layout_height="match_parent"
            android:layout_width="80dp"
            android:gravity="center"
            android:layout_toRightOf="@+id/layout_content"
            android:text="删 除"
            android:background="@drawable/btn_click_red_havebackground"
            android:textColor="#DDFFFFFF"
            />
        <RelativeLayout
            android:id="@+id/layout_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:id="@+id/text"
                android:layout_height="match_parent"
                android:layout_width="match_parent"
                android:textSize="50dp"
                android:textColor="#DD000000"
                android:background="@drawable/btn_click_black_havebackground"
                />
        RelativeLayout>

    RelativeLayout>
com.mjj.slidingbutton.SlidingButtonView>

自定义View(Item)

  • 用到的变量

删除按钮

private TextView mTextView_Delete;

用于记录滚动条可以滚动的距离

private int mScrollWidth;

自定义的接口,用于传达滑动事件等

private IonSlidingButtonListener mIonSlidingButtonListener;

记录按钮菜单是否打开,默认关闭false

private Boolean isOpen = false;

在onMeasure中只执行一次的判断

private Boolean once = false;
  • 在onMeasure中先取得作为删除按钮的TextView
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       super.onMeasure(widthMeasureSpec, heightMeasureSpec);

       if(!once){
           mTextView_Delete = (TextView) findViewById(com.mjj.slidingbutton.R.id.tv_delete);
           once = true;
       }

}
  • 在onLayout中使Item在每次变更布局大小时回到初始位置,并且获取滚动条的可移动距离
@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();
    }
}
  • 接口定义及注册方法
public void setSlidingButtonListener(IonSlidingButtonListener listener){
        mIonSlidingButtonListener = listener;
}

public interface IonSlidingButtonListener{
    void onMenuIsOpen(View view);
    void onDownOrMove(SlidingButtonView slidingButtonView);
}
  • 滑动监听,传递按下、移动这些事件,并按滑动的距离大小控制菜单开关
@Override
public boolean onTouchEvent(MotionEvent ev) {
    int action = ev.getAction();
    switch (action) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            mIonSlidingButtonListener.onDownOrMove(this);
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            changeScrollx();
            return true;
        default:
            break;
    }
    return super.onTouchEvent(ev);
}

/**
* 按滚动条被拖动距离判断关闭或打开菜单
*/
public void changeScrollx(){
    if(getScrollX() >= (mScrollWidth/2)){
        this.smoothScrollTo(mScrollWidth, 0);
        isOpen = true;
        mIonSlidingButtonListener.onMenuIsOpen(this);
    }else{
        this.smoothScrollTo(0, 0);
        isOpen = false;
    }
}
  • 滚动监听,为了让删除按钮显示在项的背后的效果
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    super.onScrollChanged(l, t, oldl, oldt);
    mTextView_Delete.setTranslationX(l - mScrollWidth);
}

RecyclerView适配器

用惯RecyclerView的这个代码写的肯定不止1次,就不拆分了说明了

public class Adapter extends RecyclerView.Adapter implements SlidingButtonView.IonSlidingButtonListener {

    private Context mContext;

    private IonSlidingViewClickListener mIDeleteBtnClickListener;

    private List mDatas = new ArrayList();

    private SlidingButtonView mMenu = null;

    public Adapter(Context context) {

        mContext = context;
        mIDeleteBtnClickListener = (IonSlidingViewClickListener) context;

        for (int i = 0; i < 10; i++) {
            mDatas.add(i+"");
        }
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    @Override
    public void onBindViewHolder(final MyViewHolder holder, int position) {

        holder.textView.setText(mDatas.get(position));
        //设置内容布局的宽为屏幕宽度
        holder.layout_content.getLayoutParams().width = Utils.getScreenWidth(mContext);

        holder.textView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                //判断是否有删除菜单打开
                if (menuIsOpen()) {
                    closeMenu();//关闭菜单
                } else {
                    int n = holder.getLayoutPosition();
                    mIDeleteBtnClickListener.onItemClick(v, n);
                }

            }
        });
        holder.btn_Delete.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                int n = holder.getLayoutPosition();
                mIDeleteBtnClickListener.onDeleteBtnCilck(v, n);
            }
        });
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup arg0, int arg1) {

        View view = LayoutInflater.from(mContext).inflate(com.mjj.slidingbutton.R.layout.layout_item, arg0,false);
        MyViewHolder holder = new MyViewHolder(view);

        return holder;
    }



    class MyViewHolder extends RecyclerView.ViewHolder {
        public TextView btn_Delete;
        public TextView textView;
        public ViewGroup layout_content;
        public MyViewHolder(View itemView) {
            super(itemView);
            btn_Delete = (TextView) itemView.findViewById(com.mjj.slidingbutton.R.id.tv_delete);
            textView = (TextView) itemView.findViewById(com.mjj.slidingbutton.R.id.text);
            layout_content = (ViewGroup) itemView.findViewById(com.mjj.slidingbutton.R.id.layout_content);

            ((SlidingButtonView) itemView).setSlidingButtonListener(Adapter.this);
        }
    }

    public void addData(int position) {
        mDatas.add(position, "添加项");
        notifyItemInserted(position);
    }

    public void removeData(int position){
        mDatas.remove(position);
        notifyItemRemoved(position);

    }

    /**
     * 删除菜单打开信息接收
     */
    @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){
            return true;
        }
        return false;
    }

    public interface IonSlidingViewClickListener {
        void onItemClick(View view,int position);
        void onDeleteBtnCilck(View view,int position);
    }
}

最后Activity

前面都写完之后Activity就没什么了

public class MainActivity extends AppCompatActivity implements Adapter.IonSlidingViewClickListener {

    private RecyclerView mRecyclerView;

    private Adapter mAdapter;

    private final String TAG = "test";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(com.mjj.slidingbutton.R.layout.activity_main);

        initView();
        setAdapter();

    }

    private void initView(){
        mRecyclerView = (RecyclerView) findViewById(com.mjj.slidingbutton.R.id.recyclerview);
    }

    private void setAdapter(){

        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(mAdapter = new Adapter(this));
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());

    }

    @Override
    public void onItemClick(View view, int position) {
        Log.i(TAG,"点击项:"+position);
    }

    @Override
    public void onDeleteBtnCilck(View view, int position) {
        Log.i(TAG,"删除项:"+position);
        mAdapter.removeData(position);
    }
}

总结

  • 删除按钮使用了TextView而不是Button,是因为5.0以上版本使用Button会显示在上层产出错误显示

  • 使用水平滚动条并不是最好的选择,但是实现起来比较简单,适合刚开始做安卓的

  • 当多点触控时会产生一些问题,我的做法是直接屏蔽多点触控,在style中添加下列代码

<item name="android:windowEnableSplitTouch" >false item>
<item name="android:splitMotionEvents" >false item>

参考资料

Android RecyclerView 使用完全解析 体验艺术般的控件

源码

源码下载

你可能感兴趣的:(android自定义控件)