[Android] ScrollMenuLayout, a low-intrusion RecyclerView sliding menu control

Posted by kof20012 on Wed, 08 May 2019 21:42:03 +0200

Suddenly, I want to realize the sliding menu control in RecyclerView by myself. I've read several articles about the gods, including those from ViewGroup, RecyclerView, etc....................... But they don't quite meet my expectations. I hope it's a simple old project that can be used quickly. So after reading a few articles, I decided to try to write a control that could quickly add sliding menus to old projects. The old rule, first on the effect map.

Design sketch:

The effect is very common, there is no cool animation, because we need to consider the reasons for fast compatibility with old projects. It's just the conventional expansion and retraction. It can automatically judge whether to retract or to retract according to the distance of the drag.

Idea analysis:

In fact, at first I intercepted the touch events of RecyclerView, and then I wrote my own logic to achieve the sliding effect. But the technology is not good, the effect of implementation is too bad, and always do not know how to add the menu layout into the original item layout is not destroyed. Later I read an article using Horizontal ScrollView and his ideas, and found it really convenient. Links: RecyclerView Side-Slip Delete Menu One of the Minimalist Editions.

Therefore, my control is also implemented by inheriting Horizontal ScrollView. It consists of a FrameLayout for wrapping Item layout and a FrameLayout for wrapping sideslip menu layout. They are all wrapped in a horizontal Linear Layout. The Linear Layout was eventually wrapped in Horizontal ScrollView.

Code implementation:

Create a new attr.xml file under / res/values to customize the attributes:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ScrollMenuLayout">
        <attr name="itemLayout" format="reference"/>
        <attr name="rightMenuLayout" format="reference"/>
    </declare-styleable>
</resources>

Then create a new Java class named ScrollMenuLayout and inherit Horizontal ScrollView. I'll explain the implementation logic in the form of annotations in the code snippet so that it's easier to understand:

public class ScrollMenuLayout extends HorizontalScrollView {
    private static final String TAG = "MenuItem";
    private FrameLayout container_item;//Containers for wrapping Item layouts
    private FrameLayout container_menu_right;//Containers for wrapping sideslip menu layout

    private float lastX;//Record the value of the x-axis of the last touch, that is, the horizontal position
    private float moveX;//Cumulative distance from finger pressing moving lifting

    public ScrollMenuLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setHorizontalScrollBarEnabled(false);//Hidden scrollbar

        //Getting attributes in xml files
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ScrollMenuLayout);

        //new A horizontal linear layout container for placing two FrameLayout s horizontally
        LinearLayout linearLayout = new LinearLayout(context);
        linearLayout.setOrientation(LinearLayout.HORIZONTAL);

        //Instantiate two containers
        container_item = new FrameLayout(context);
        container_menu_right = new FrameLayout(context);

        //If the layout id is set in the xml file, load it directly
        int item_layout_id = array.getResourceId(R.styleable.ScrollMenuLayout_itemLayout, -1);
        if (item_layout_id != -1) {
            //Load the Item layout file into FrameLayout. Note that when using LayoutInflate, the first parameter is the layout file id and the second parameter is the layout container.
            //Because I use addView, the second parameter fills in Null, otherwise there will be an error that Item already has a parent container.
            container_item.addView(LayoutInflater.from(context).inflate(item_layout_id, null));
        }
        int menu_layout_right_id = array.getResourceId(R.styleable.ScrollMenuLayout_rightMenuLayout, -1);
        if (menu_layout_right_id != -1) {
            //Ibid logic
            container_menu_right.addView(LayoutInflater.from(context).inflate(menu_layout_right_id, null));
        }

        //Assemble
        linearLayout.addView(container_item);
        linearLayout.addView(container_menu_right);
        addView(linearLayout);

        array.recycle();//Recycling Attribute Array
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //Set the width of the Item to the width of the parent container for pushing the sideslip menu out of view
        //The onDraw execution is to ensure that the width of the parent container is obtained, where the parent container refers to the Adapter.
        //ViewGroup, the second parameter of the onCreateViewHolder method
        ViewGroup.LayoutParams layoutParams = container_item.getLayoutParams();
        layoutParams.width = ((ViewGroup) getParent()).getWidth();
        container_item.setLayoutParams(layoutParams);
        super.onDraw(canvas);
    }

    /**
     * Setting up item layout
     *
     * @param v item Layout view
     */
    public void setItemView(View v) {
        container_item.removeAllViews();
        container_item.addView(v);
    }

    /**
     * Get item layout view
     * Easy to do all kinds of monitoring and so on
     *
     * @return Null or View
     */
    public View getItemView() {
        if (container_item.getChildCount() > 0) {
            return container_item.getChildAt(0);
        }
        return null;
    }

    /**
     * Set the menu on the right
     *
     * @param v Right menu layout View
     */
    public void setRightMenuView(View v) {
        container_menu_right.removeAllViews();
        container_menu_right.addView(v);
    }

    /**
     * Get the menu layout View on the right
     *
     * @return Null or View
     */
    public View getRightMenuView() {
        if (container_menu_right.getChildCount() > 0) {
            return container_menu_right.getChildAt(0);
        }
        return null;
    }

    /**
     * Expand the menu on the right
     */
    public void expandRightMenu() {
        arrowScroll(FOCUS_RIGHT);
    }

    /**
     * Put away the menu on the right
     */
    public void closeRightMenu() {
        arrowScroll(FOCUS_LEFT);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = ev.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                //Record moving distance
                moveX += ev.getX() - lastX;
                lastX = ev.getX();
                Log.d(TAG, "menu width = " + container_menu_right.getWidth() + " moveX = " + moveX);
                break;
            case MotionEvent.ACTION_UP:
                if (moveX > 0) {
                    //Intention: Put it away and slide your finger from left to right.
                    if (Math.abs(moveX) >= container_menu_right.getWidth() / 2) {
                        //Sliding distance greater than half, retract
                        closeRightMenu();
                    } else {
                        //Open
                        expandRightMenu();
                    }
                } else if (moveX < 0) {
                    //Intention unfolds, finger slides from right to left
                    if (Math.abs(moveX) >= container_menu_right.getWidth() / 2) {
                        //Open
                        expandRightMenu();
                    } else {
                        //Retract
                        closeRightMenu();
                    }
                }
                moveX = 0;//Reset
                return true;//Consumption of the event, no longer transfer, to resolve sliding conflicts
        }
        return super.onTouchEvent(ev);
    }
}

As you can see, the main implementation is to set the width of the Item layout to the width of the entire Horizontal ScrollView parent container, and then just give the layout of the sideslip menu out of the field of view. We rewrite the sliding logic of the Horizontal ScrollView. When the sliding distance is more than half the width of the sideslip menu layout, the sideslip menu pops up automatically. Or take it away.

In fact, the logic of the whole control is simple. Just notice that in onTouch, when event.getX() is negative, it means that the finger is sliding from right to left, and vice versa.

Concluding remarks:

So far, the implementation of the whole control is completed. Because the layout of Item and the layout of the side sliding menu are separated, the whole control can quickly replace the layout of the old project, and it can complete the function of side sliding without modifying too many things. Of course, if you need some cool animation, you need to achieve this by yourself. I will also consider adding new animation functions later.

The project has been open source to GitHub, and with demo, you are welcome to consult. GitHub links

 

Topics: xml github encoding Java