动手打造史上最简单的 Recycleview 侧滑菜单

本文介绍了一种简单易懂的方法,实现在RecyclerView中的自定义侧滑菜单。通过在item布局中直接使用HorizontalScrollView,解决了第三方库存在的局限性,如菜单文字不可修改等问题。

在实现 Recycleview 侧滑菜单时起初使用了开源库 SwipeRecyclerView ,此库功能广泛,但无法满足个人需求,这是因为此库中存在以下局限性:

  1. 菜单文字一旦确定将无法修改
  2. 侧滑时整个 item 都会滑动
  3. 无法自定义菜单样式

只能自己实现了,查阅资料后发现,较多通过 DragHelper 实现的,它是一个手势滑动辅助工具,使 item 可以滑动,然后…… , 等等!既然目的是让 item 可以滑动,那为什么不直接在 item 布局中使用 HorizontalScrollView 呢?参考鸿神的Android 自定义控件打造史上最简单的侧滑菜单,标题致敬一波~

首先是自定义 SlidingMenu :

public class SlidingMenu extends HorizontalScrollView {

    private static final float radio = 0.3f;//菜单占屏幕宽度比
    private final int mScreenWidth;
    private final int mMenuWidth;
    private boolean once = true;

    public SlidingMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScreenWidth = ScreenUtil.getScreenWidth(context);
        mMenuWidth = (int) (mScreenWidth * radio);
        setOverScrollMode(View.OVER_SCROLL_NEVER);
        setHorizontalScrollBarEnabled(false);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (once) {
            LinearLayout wrapper = (LinearLayout) getChildAt(0);
            wrapper.getChildAt(0).getLayoutParams().width = mScreenWidth;
            wrapper.getChildAt(1).getLayoutParams().width = mMenuWidth;
            once = false;
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
            case MotionEvent.ACTION_UP:
                int scrollX = getScrollX();
                if (Math.abs(scrollX) > mMenuWidth / 2) {
                    this.smoothScrollTo(mMenuWidth, 0);
                } else {
                    this.smoothScrollTo(0, 0);
                }
                return true;
        }
        return super.onTouchEvent(ev);
    }
}

 

在 item 布局中应用 SlidingMenu,注意 SlidingMenu 中是按照顺序区分 content 和 menu 的,所以布局文件中顺序要对应一致 :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginTop="2dp"
              android:orientation="vertical"
    >
    <com.xmwj.slidingmenu.SlidingMenu
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            >
            <LinearLayout
                android:id="@+id/content"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                >
                <ImageView
                    android:id="@+id/imageView"
                    android:layout_width="match_parent"
                    android:layout_height="70dp"
                    android:background="#af6fe1"
                    />
            </LinearLayout>
            <LinearLayout
                android:id="@+id/menu"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="horizontal"
                >
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:text="置顶"
                    android:textColor="#fff"
                    android:gravity="center"
                    android:background="@color/colorAccent"
                    />
            </LinearLayout>
        </LinearLayout>
    </com.xmwj.slidingmenu.SlidingMenu>
</LinearLayout>

 

OK~ ,这就实现了具有侧滑菜单的 Recycleview 了!不需要 DragHelper !不需要自定义 Recycleview !不需要处理事件分发!是不是超级简单?与其自定义 Recycleview 然后关联 item 实现,为什么不直接改变 item 布局实现!

简单使用一下这个侧滑菜单,首先是适配器代码:

class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<String> mData;
    private Context mContext;

    MyAdapter(List<String> data, Context context) {
        mData = data;
        mContext = context;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new MyViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, parent, false));
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {  }

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

    private class MyViewHolder extends RecyclerView.ViewHolder {
        MyViewHolder(View itemView) {
            super(itemView);
        }
    }

}

 

MainActivity 如下:

public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        init();
    }

    private void init() {
        List<String> data = new ArrayList<>();
        for(int i=0;i<20;i++) {
            data.add(null);
        }
        mRecyclerView.setAdapter(new MyAdapter(data, this));
    }
}

 

效果如下:

这里写图片描述

至此已经实现了最简单的 Recycleview 侧滑菜单,但 item 的菜单可以同时打开,一般情况下,当触摸其他 item 时应该关闭已打开的菜单,我们可以将打开的 item 引用记录下来,方便及时关闭。改造 SlidingMenu,添加以下方法 :

    /**
     * 关闭菜单
     */
    public void closeMenu() {
        this.smoothScrollTo(0, 0);
        isOpen = false;
    }

    /**
     * 菜单是否打开
     */
    public boolean isOpen() {
        return isOpen;
    }

    /**
     * 当打开菜单时记录此 view ,方便下次关闭
     */
    private void onOpenMenu() {
        View view = this;
        while (true) {
            view = (View) view.getParent();
            if (view instanceof RecyclerView) {
                break;
            }
        }
        ((MyAdapter) ((RecyclerView) view).getAdapter()).holdOpenMenu(this);
        isOpen = true;
    }

    /**
     * 当触摸此 item 时,关闭上一次打开的 item
     */
    private void closeOpenMenu() {
        if (!isOpen) {
            View view = this;
            while (true) {
                view = (View) view.getParent();
                if (view instanceof RecyclerView) {
                    break;
                }
            }
            ((MyAdapter) ((RecyclerView) view).getAdapter()).closeOpenMenu();
        }
    }

 

修改 onTouchEvent 方法:

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                closeOpenMenu();
                break;
            case MotionEvent.ACTION_UP:
                int scrollX = getScrollX();
                if (Math.abs(scrollX) > mMenuWidth / 2) {
                    this.smoothScrollTo(mMenuWidth, 0);
                    onOpenMenu();
                } else {
                    this.smoothScrollTo(0, 0);
                }
                return true;
        }
        return super.onTouchEvent(ev);
    }

 

显然我们将已打开的 item 记录在适配器中,在 Adapter 中添加记录与关闭方法:

    private SlidingMenu mOpenMenu;

    public void holdOpenMenu(SlidingMenu slidingMenu) {
        mOpenMenu= slidingMenu;
    }

    public void closeOpenMenu() {
        if (mOpenMenu!= null && mOpenMenu.isOpen()) {
            mOpenMenu.closeMenu();
        }
    }

 

OK~ 非常简单,看下效果,当触摸其他 item 时已打开的 item 就会自动关闭啦:

这里写图片描述

在实际使用时,一般需要监听侧滑菜单的点击事件,此方法中的侧滑菜单是 item 的一部分,对其监听也就非常简单,同样,修改菜单文字也非常简单。下面通过改变菜单文字实现置顶/取消置顶功能。

改造适配器,直接附上 MyAdapter 全部代码:

class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {


    private List<String> mData;
    private Context mContext;

    private SlidingMenu mOpenMenu;

    public void holdOpenMenu(SlidingMenu slidingMenu) {
        mOpenMenu = slidingMenu;
    }

    public void closeOpenMenu() {
        if (mOpenMenu != null && mOpenMenu.isOpen()) {
            mOpenMenu.closeMenu();
        }
    }

    MyAdapter(List<String> data, Context context) {
        mData = data;
        mContext = context;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new MyViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, parent, false));
    }

    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position) {
        holder.imageView.setBackgroundColor(ContextCompat.getColor(mContext, R.color.colorItem));
        holder.menuText.setText(mData.get(position));
        holder.menuText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                closeOpenMenu();
                boolean top;
                if (holder.menuText.getText().toString().equals("置顶")) {
                    holder.menuText.setText("取消置顶");
                    holder.imageView.setBackgroundColor(ContextCompat.getColor(mContext, R.color.colorTopItem));
                    top = true;
                }else{
                    holder.menuText.setText("置顶");
                    holder.imageView.setBackgroundColor(ContextCompat.getColor(mContext, R.color.colorItem));
                    top = false;
                }
                if (mOnMenuClickListener != null) {
                    mOnMenuClickListener.onClick(position, top);
                }
            }
        });
    }

    public interface OnMenuClickListener {
        void onClick(int position,boolean top);
    }

    private OnMenuClickListener mOnMenuClickListener;

    public void setOnMenuClickListener(OnMenuClickListener onMenuClickListener) {
        this.mOnMenuClickListener = onMenuClickListener;
    }


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

    class MyViewHolder extends RecyclerView.ViewHolder {
        TextView menuText;
        ImageView imageView;

        MyViewHolder(View itemView) {
            super(itemView);
            menuText = (TextView) itemView.findViewById(R.id.menuText);
            imageView = (ImageView) itemView.findViewById(R.id.imageView);
        }
    }
}

其中 imageView 为 item 布局中的 item 内容,menuText 为菜单文字控件,当置顶时 item 颜色变为黄色,菜单文字变为“取消置顶”。菜单点击事件以接口形式公开,代码十分简单,下面是 MainActivity :


public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        init();
    }

    private void init() {
        final List<String> data = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            data.add("置顶");
        }
        MyAdapter myAdapter = new MyAdapter(data, this);
        myAdapter.setOnMenuClickListener(new MyAdapter.OnMenuClickListener() {
            @Override
            public void onClick(int position, boolean top) {
                data.set(position, top ? "取消置顶" : "置顶");
            }
        });
        mRecyclerView.setAdapter(myAdapter);
    }
}

 

实现效果:

这里写图片描述

OK~ 成功弥补第三方库“菜单文字一旦确定将无法修改”这个致命缺陷了。有的场景下,我们不希望整个 item 侧滑,比如:

这里写图片描述

使用第三库时很难实现这样的需求,而此方法就非常容易了,只需要把底部控件置于 SlidingMenu 外部就可以了,以下是 item 布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginTop="2dp"
              android:orientation="vertical"
    >

    <com.xmwj.slidingmenu.SlidingMenu
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            >

            <LinearLayout
                android:id="@+id/content"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                >
                <ImageView
                    android:id="@+id/imageView"
                    android:layout_width="match_parent"
                    android:layout_height="70dp"
                    />
            </LinearLayout>

            <LinearLayout
                android:id="@+id/menu"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="horizontal"
                >
                <TextView
                    android:id="@+id/menuText"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:text="置顶"
                    android:textColor="#fff"
                    android:gravity="center"
                    android:background="@color/colorAccent"
                    />
            </LinearLayout>

        </LinearLayout>

    </com.xmwj.slidingmenu.SlidingMenu>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="item 底部备注 ..."
        android:background="@color/colorAccent"
        android:textColor="#fff"
        android:gravity="center"
        />


</LinearLayout>

 

轻松解决了 “菜单文字一旦确定将无法修改“,“侧滑时整个 item 都会滑动”这两个缺陷,最后一个“无法自定义菜单样式”,第三方库中高度集成了菜单的生成方式,只能设置单一的文字和图标,有时无法满足需求,而此方法实现自定义菜单样式十分简单,在 item 中直接编写 menu 布局即可。

总结

Recycleview 侧滑菜单大多的实现思路是:通过自定义 Recycleview 或 Adapter 提供创建菜单方法,然后内部再关联到各个 item 改变其布局,从而使 item 具有侧滑功能,优点是使用简单,但是不够灵活,比如开始提到的三个局限性。本文实现方法直接在 item 布局中进行设置,使 item 具有侧滑功能,实现过程及其简单,易于理解,应该是最简单的 Recycleview 侧滑菜单了,希望能给你带来帮助。

源码 : https://github.com/yhaolpz/SlidingMenu

如果帮助到了你,希望能够 star 鼓励一下,有句话烂大街了但确实是真心话,你的鼓励是我的最大动力。

转载于:https://my.oschina.net/JiangTun/blog/1526663

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值