Analysis on the implementation of sideslip menu in Android's ten SlidingMenu

SlidingMenu sideslip menu is a relatively new setting interface or configuration interface effect. The setting interface effect appears in the left or right sliding of the main interface, which is convenient for various operations. Many excellent applications adopt this interface scheme, such as facebook, Renren, everynote, Google +, Netease News, Zhihu daily, Youdao cloud notes, etc

 

Realization principle of sideslip menu:
There are two parts in an Activity layout, one is the layout of menu, the other is the layout of content. The two layouts are arranged horizontally. The menu layout is on the left and the content layout is on the right. During initialization, shift the menu layout to the left so that it can be completely hidden, so that the content layout will be fully displayed in the Activity. Then the left offset distance of the menu layout is changed by monitoring the finger slide event to control the display and hiding of the menu layout.

 

 

2: Implementation process: click the open button, expand the sideslip menu, and click again to close the sideslip menu.

1. Introduction of custom SlideMenu component

SlideMenu.java

package   com.example.walkerlogin1.view;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;

public class SlideMenu extends ViewGroup {
	public static final int SCREEN_MENU = 0;
	public static final int SCREEN_MAIN = 1;
	private static final int SCREEN_INVALID = -1;
	
	private int mCurrentScreen;
	private int mNextScreen = SCREEN_INVALID;

	private Scroller mScroller;
	private VelocityTracker mVelocityTracker;
	private int mTouchSlop;
	
	private float mLastMotionX;
	private float mLastMotionY;

	private final static int TOUCH_STATE_REST = 0;
	private final static int TOUCH_STATE_SCROLLING = 1;
	private static final int SNAP_VELOCITY = 1000;

	public int mTouchState = TOUCH_STATE_REST;
	private boolean mLocked;
	private boolean mAllowLongPress;

	public SlideMenu(Context context) {
		this(context, null, 0);
	}

	public SlideMenu(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public SlideMenu(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		mScroller = new Scroller(getContext());
		mCurrentScreen = SCREEN_MAIN;
		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		measureViews(widthMeasureSpec, heightMeasureSpec);
	}

	public void measureViews(int widthMeasureSpec, int heightMeasureSpec) {
		View menuView = getChildAt(0);
		menuView.measure(menuView.getLayoutParams().width + menuView.getLeft()
				+ menuView.getRight(), heightMeasureSpec);

		View contentView = getChildAt(1);
		contentView.measure(widthMeasureSpec, heightMeasureSpec);
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int childCount = getChildCount();
		if (childCount != 2) {
			throw new IllegalStateException(
					"The childCount of SlidingMenu must be 2");
		}

		View menuView = getChildAt(0);
		final int width = menuView.getMeasuredWidth();
		menuView.layout(-width, 0, 0, menuView.getMeasuredHeight());

		View contentView = getChildAt(1);
		contentView.layout(0, 0, contentView.getMeasuredWidth(),
				contentView.getMeasuredHeight());
	}
	
	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
		View child;
		for (int i = 0; i < getChildCount(); i++) {
			child = getChildAt(i);
			child.setFocusable(true);
			child.setClickable(true);
		}
	}
	
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		if (mLocked) {
			return true;
		}

		final int action = ev.getAction();
		if ((action == MotionEvent.ACTION_MOVE)
				&& (mTouchState != TOUCH_STATE_REST)) {
			return true;
		}

		final float x = ev.getX();
		final float y = ev.getY();

		switch (action) {
		case MotionEvent.ACTION_MOVE:

			final int xDiff = (int) Math.abs(x - mLastMotionX);
			final int yDiff = (int) Math.abs(y - mLastMotionY);

			final int touchSlop = mTouchSlop;
			boolean xMoved = xDiff > touchSlop;
			boolean yMoved = yDiff > touchSlop;

			if (xMoved || yMoved) {

				if (xMoved) {
					// Scroll if the user moved far enough along the X axis
					mTouchState = TOUCH_STATE_SCROLLING;
					enableChildrenCache();
				}
				// Either way, cancel any pending longpress
				if (mAllowLongPress) {
					mAllowLongPress = false;
					// Try canceling the long press. It could also have been
					// scheduled
					// by a distant descendant, so use the mAllowLongPress flag
					// to block
					// everything
					final View currentScreen = getChildAt(mCurrentScreen);
					currentScreen.cancelLongPress();
				}
			}
			break;

		case MotionEvent.ACTION_DOWN:
			// Remember location of down touch
			mLastMotionX = x;
			mLastMotionY = y;
			mAllowLongPress = true;

			mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
					: TOUCH_STATE_SCROLLING;

			break;

		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			// Release the drag
			clearChildrenCache();
			mTouchState = TOUCH_STATE_REST;
			mAllowLongPress = false;
			break;
		}

		/*
		 * The only time we want to intercept motion events is if we are in the
		 * drag mode.
		 */
		return mTouchState != TOUCH_STATE_REST;
	}
	
	void enableChildrenCache() {
		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			final View layout = (View) getChildAt(i);
			layout.setDrawingCacheEnabled(true);
		}
	}

	void clearChildrenCache() {
		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			final View layout = (View) getChildAt(i);
			layout.setDrawingCacheEnabled(false);
		}
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (mLocked) {
			return true;
		}

		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(ev);

		final int action = ev.getAction();
		final float x = ev.getX();

		switch (action) {
		case MotionEvent.ACTION_DOWN:
			/*
			 * If being flinged and user touches, stop the fling. isFinished
			 * will be false if being flinged.
			 */
			if (!mScroller.isFinished()) {
				mScroller.abortAnimation();
			}

			// Remember where the motion event started
			mLastMotionX = x;
			break;
		case MotionEvent.ACTION_MOVE:
			if (mTouchState == TOUCH_STATE_SCROLLING) {
				// Scroll to follow the motion event
				final int deltaX = (int) (mLastMotionX - x);
				mLastMotionX = x;

				if (deltaX < 0) {
					if (deltaX + getScrollX() >= -getChildAt(0).getWidth()) {
						scrollBy(deltaX, 0);
					}

				} else if (deltaX > 0) {
					final int availableToScroll = getChildAt(
							getChildCount() - 1).getRight()
							- getScrollX() - getWidth();

					if (availableToScroll > 0) {
						scrollBy(Math.min(availableToScroll, deltaX), 0);
					}
				}
			}
			break;
		case MotionEvent.ACTION_UP:
			if (mTouchState == TOUCH_STATE_SCROLLING) {
				final VelocityTracker velocityTracker = mVelocityTracker;
				velocityTracker.computeCurrentVelocity(1000);
				int velocityX = (int) velocityTracker.getXVelocity();

				if (velocityX > SNAP_VELOCITY && mCurrentScreen == SCREEN_MAIN) {
					// Fling hard enough to move left
					snapToScreen(SCREEN_MENU);
				} else if (velocityX < -SNAP_VELOCITY
						&& mCurrentScreen == SCREEN_MENU) {
					// Fling hard enough to move right
					snapToScreen(SCREEN_MAIN);
				} else {
					snapToDestination();
				}

				if (mVelocityTracker != null) {
					mVelocityTracker.recycle();
					mVelocityTracker = null;
				}
			}
			mTouchState = TOUCH_STATE_REST;
			break;
		case MotionEvent.ACTION_CANCEL:
			mTouchState = TOUCH_STATE_REST;
		}

		return true;
	}
	
	@Override
	public void computeScroll() {
		if (mScroller.computeScrollOffset()) {
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
		} else if (mNextScreen != SCREEN_INVALID) {
			mCurrentScreen = Math.max(0,
					Math.min(mNextScreen, getChildCount() - 1));
			mNextScreen = SCREEN_INVALID;
			clearChildrenCache();
		}
	}

	@Override
	public void scrollTo(int x, int y) {
		super.scrollTo(x, y);
		postInvalidate();
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {
		final int scrollX = getScrollX();
		super.dispatchDraw(canvas);
		canvas.translate(scrollX, 0);
	}

	@Override
	public boolean dispatchUnhandledMove(View focused, int direction) {
		if (direction == View.FOCUS_LEFT) {
			if (getCurrentScreen() > 0) {
				snapToScreen(getCurrentScreen() - 1);
				return true;
			}
		} else if (direction == View.FOCUS_RIGHT) {
			if (getCurrentScreen() < getChildCount() - 1) {
				snapToScreen(getCurrentScreen() + 1);
				return true;
			}
		}
		return super.dispatchUnhandledMove(focused, direction);
	}
	
	protected void snapToScreen(int whichScreen) {

		enableChildrenCache();

		whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
		boolean changingScreens = whichScreen != mCurrentScreen;

		mNextScreen = whichScreen;

		View focusedChild = getFocusedChild();
		if (focusedChild != null && changingScreens
				&& focusedChild == getChildAt(mCurrentScreen)) {
			focusedChild.clearFocus();
		}

		final int newX = (whichScreen - 1) * getChildAt(0).getWidth();
		final int delta = newX - getScrollX();
		mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
		invalidate();
	}

	protected void snapToDestination() {
		if (getScrollX() == 0) {
			return;
		}
		final int screenWidth = getChildAt(0).getWidth();
		final int whichScreen = (screenWidth + getScrollX() + (screenWidth / 2))
				/ screenWidth;
		snapToScreen(whichScreen);
	}
	
	public int getCurrentScreen() {
		return mCurrentScreen;
	}
	
	public boolean isMainScreenShowing() {
		return mCurrentScreen == SCREEN_MAIN;
	}
	
	public void openMenu() {
		mCurrentScreen = SCREEN_MENU;
		snapToScreen(mCurrentScreen);
	}
	
	public void closeMenu() {
		mCurrentScreen = SCREEN_MAIN;
		snapToScreen(mCurrentScreen);
	}
	
	public void unlock() {
		mLocked = false;
	}

	public void lock() {
		mLocked = true;
	}
	
}

In strings.xml under vlues, add the following code

<?xml version="1.0" encoding="utf-8"?>
<resources>

    
    <item>User login</item>
    <item>Exercise test</item>
    <item>Personal information</item>
    <item>Itinerary record</item>
    <item>Weather query</item>
    <item>Health column</item>
    <item>Software setup</item>
   
</string-array>
</resources>

Item.java

package com.example.walkerlogin1.view;

public class Item {
	private String name;
	private int imageId;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getImageId() {
		return imageId;
	}
	public void setImageId(int imageId) {
		this.imageId = imageId;
	}
	public Item(String name, int imageId) {
		super();
		this.name = name;
		this.imageId = imageId;
	}
	
}

Establish data source

ItemAdapter.java

package com.example.walkerlogin1.view;

import java.util.List;

import com.example.walkerlogin1.R;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class ItemAdapter extends ArrayAdapter<Item> {

public ItemAdapter(Context context, int textViewResourceId,
			List<Item> objects) {
		super(context,  textViewResourceId, objects);
		// TODO Auto-generated constructor stub
		resourceId=textViewResourceId;
	}


	private int resourceId;

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		Item item = getItem(position);
		View view;
		ViewHolder viewHolder;
		if (convertView == null) {
			view = LayoutInflater.from(getContext()).inflate(resourceId, null);

			viewHolder = new ViewHolder();
			viewHolder.ivIcon = (ImageView) view.findViewById(R.id.ivIcon);
			viewHolder.tvIntroduction = (TextView) view.findViewById(R.id.tvIntroduction);
			view.setTag(viewHolder);
		} else {
			view = convertView;
			viewHolder = (ViewHolder) view.getTag();
		}
		viewHolder.ivIcon.setImageResource(item.getImageId());
		viewHolder.tvIntroduction.setText(item.getName());
		return view;
	}
	
	class ViewHolder {
		
		ImageView ivIcon;
		
		TextView tvIntroduction;
		
	}

}

Main interface MainActivity2.java
 

package com.example.walkerlogin1;

import java.util.ArrayList;
import java.util.List;

import com.example.walkerlogin1.view.Item;
import com.example.walkerlogin1.view.ItemAdapter;
import com.example.walkerlogin1.view.SlideMenu;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Toast;



public class MainActivity2 extends Activity {
	private SlideMenu slideMenu;
	private ImageView ivSwitchSlideMenu;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_main2);
		initMenuList();
		slideMenu=(SlideMenu) findViewById(R.id.slideMenu);
		ivSwitchSlideMenu=(ImageView) findViewById(R.id.switch_slidemenu);
		ivSwitchSlideMenu.setOnClickListener(new OnClickListener() {
		public void onClick(View view) {
		if(slideMenu.isMainScreenShowing()){
		slideMenu.openMenu();
		}else{
		slideMenu.closeMenu();
		}
		}
		});
	}
	private void initMenuList() {
		int[] icons = { R.drawable.icons_menu_login,
		R.drawable.icons_menu_sport, R.drawable.icons_menu_inform,
		R.drawable.icons_menu_history, R.drawable.icons_menu_weather,
		R.drawable.icons_menu_health, R.drawable.icons_menu_setting };
		final String[] introductons = getResources().getStringArray(R.array.menulist);
		List<Item> items=new ArrayList<Item>();
		for(int i=0;i<icons.length;i++){
		   items.add(new Item(introductons[i],icons[i]));
		}
		ListView lvMenuList=(ListView) findViewById(R.id.lvMenuList);
		ItemAdapter itemAdapter=new ItemAdapter(this, R.layout.menulist_item, items);
		lvMenuList.setAdapter(itemAdapter);
		lvMenuList.setOnItemClickListener(new OnItemClickListener() {
		public void onItemClick(AdapterView<?> adapterView, View view, int position,
		long id) {
		//Toast.makeText(MainActivity2.this, "you clicked" + introductions [position],
		//Toast.LENGTH_LONG).show();
			Intent  intent=new Intent();
		switch (position) {
		  case 0:  //Click to jump to listview
			Toast.makeText(MainActivity2.this," You click."+introductons[position],
					Toast.LENGTH_LONG).show();
			intent.setClass(getApplicationContext(), MainActivity.class);
			startActivity(intent);
			break;

		    default:
			break;
		}
		}
		});
}
}

UI layout activity main.xml
 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <com.example.walkerlogin1.view.SlideMenu
        android:id="@+id/slideMenu"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <!-- Sideslip menu -->

        <include layout="@layout/leftmenu" />
        <!-- Main interface -->

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >

            <include layout="@layout/main_layout_titlebar" />

            <include layout="@layout/main_layout_content" />
        </LinearLayout>
    </com.example.walkerlogin1.view.SlideMenu>

</RelativeLayout>

leftmenu.xml
 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.example.walkerlogin1"
    android:layout_width="200dp"
    android:layout_height="match_parent"
    android:background="@drawable/leftmenu_bg" >

    <com.makeramen.roundedimageview.RoundedImageView
        android:id="@+id/rivUserPhoto"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:src="@drawable/test_photo"
        app:riv_oval="true" />

    <TextView
        android:id="@+id/tvMotto"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/rivUserPhoto"
        android:layout_marginBottom="24dp"
        android:layout_marginLeft="5dp"
        android:layout_toRightOf="@+id/rivUserPhoto"
        android:text="Running endlessly"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:textColor="@color/white" />

    <ListView
        android:id="@+id/lvMenuList"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_below="@id/tvMotto"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:divider="@color/whitesmoke"
        android:dividerHeight="1dp"
        android:listSelector="#00000000" >
    </ListView>

</RelativeLayout>

main_layout_titlebar.xml
 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="45.0dip"
    android:background="@drawable/titlebar_bg"
    android:gravity="center_vertical" >

    <ImageView
        android:id="@+id/switch_slidemenu"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="3.0dip"
        android:gravity="center"       
        android:src="@drawable/switch_silidemenu" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:text="Walker"
        android:textColor="@color/white"
        android:textSize="22sp" />

    <ImageView
        android:id="@+id/switch_map"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginRight="15.0dip"
        android:src="@drawable/switch_map" />

</RelativeLayout>


main_layout_content.xml
 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/welcome_bg" >

    <TextView
        android:id="@+id/tvCity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:text="Binzhou City"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/tvTemperature"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignRight="@+id/tvCity"
        android:layout_below="@+id/tvCity"
        android:layout_marginRight="26dp"
        android:layout_marginTop="40dp"
        android:text="temperature"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/tvDay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/tvTemperature"
        android:layout_below="@+id/tvTemperature"
        android:layout_marginTop="20dp"
        android:text="Sunday"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/tvWeather"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/tvDay"
        android:layout_below="@+id/tvDay"
        android:layout_marginTop="24dp"
        android:text="Fine"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/tvTemperature"
        android:layout_marginLeft="24dp"
        android:layout_toRightOf="@+id/tvTemperature"
        android:background="#00000000"
        android:src="@drawable/monkey" />

</RelativeLayout>

Final layout

Click the button on the left or slide it to the right;

269 original articles published, praised 2, 6893 visitors
Private letter follow

Tags: Android xml Java encoding

Posted on Tue, 11 Feb 2020 09:53:30 -0500 by pjsteinfort