Total Pageviews

Wednesday 16 July 2014

Android - Sliding Menu With Expandable List

Hi, I will share my experience while working with sliding menu without using Navigation Drawer and Action Bar.
Objective : Develop Android project with sliding menu with motion events and menu with expandable listview.
Expandable ListView : I created 5 parent groups and 3 children's for every even parent group. Instead of normal Strings, I tried to use Data Transfer Objects.
When parent group clicked, then child groups need to open if present.
When child group clicked , then parent group selection color needs to change.
Above are the requirements I have taken while developing this project.

Thanks to Paul Grime, for giving this Scrolling Menu.

Project Structure : 

Step 1: After creating android project, create new android xml file and named it as activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<com.slidingview.MyHorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_margin="0px"
    android:background="#00ffffff"
    android:fadingEdge="none"
    android:fadingEdgeLength="0px"
    android:padding="0px"
    android:scrollbars="none" >

    <LinearLayout
        android:id="@+id/top"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_margin="0px"
        android:orientation="horizontal"
        android:padding="0px" >
    </LinearLayout>

</com.slidingview.MyHorizontalScrollView>

Step 2 : we need to create two more layout files, one for menu drawer and another for main application. Then create new android xml file and named it as main_application.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/app"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#222222" >

    <RelativeLayout
        android:id="@+id/listImages_content"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:background="@color/black" >

        <RelativeLayout
            android:id="@+id/home_tabBar"
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:layout_marginBottom="1dp"
            android:background="@color/thickgray"
            android:orientation="horizontal" >

            <ImageView
                android:id="@+id/slideBtn"
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:layout_marginLeft="10dp"
                android:padding="3dp"
                android:src="@drawable/slide" />
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/total_frame_content"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_below="@id/home_tabBar" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:layout_centerVertical="true"
                android:text="Sliding Menu Demo"
                android:textColor="@android:color/white" />
        </RelativeLayout>
    </RelativeLayout>


</RelativeLayout>

and it will look like below :

I have kept a sliding button on top left of the layout to toggle the menu and create new android layout file and named it as menu_drawer.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/menu1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/thickgray"
    android:orientation="horizontal" >

    <LinearLayout
        android:id="@+id/menu"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="@color/thickgray"
        android:orientation="vertical" >

        <RelativeLayout
            android:id="@+id/category_relView"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="@color/thickgray"
            android:visibility="visible" >

            <ExpandableListView
                android:id="@+id/categorylist"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_alignParentTop="true"
                android:background="@color/thickgray"
                android:dividerHeight="0dp"
                android:groupIndicator="@null"
                android:listSelector="@android:color/transparent" />
        </RelativeLayout>
    </LinearLayout>

    <RelativeLayout
        android:id="@+id/drawerCloseLayout"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_alignRight="@+id/menu"
        android:background="@drawable/shadow" >

        <ImageButton
            android:id="@+id/left_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:background="@android:color/transparent"
            android:onClick="closeDrawerFunction"
            android:src="@drawable/closedrawer"
            android:visibility="visible" />
    </RelativeLayout>

</RelativeLayout>

and it will look like below :
 I have kept item selected with sky blue color.

Step 3 : Create java class and named it as MyHorizontalScrollView.java, this class is responsible for sliding menu.

/*
 * #%L
 * SlidingMenuDemo
 * $Id:$
 * $HeadURL:$
 * %%
 * Copyright (C) 2012 Paul Grime
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package com.slidingview;

import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.HorizontalScrollView;

/**
 * A HorizontalScrollView (HSV) implementation that disallows touch events (so no scrolling can be done by the user).
 *
 * This HSV MUST contain a single ViewGroup as its only child, and this ViewGroup will be used to display the children Views
 * passed in to the initViews() method.
 */
public class MyHorizontalScrollView extends HorizontalScrollView {
public MyHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}

public MyHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public MyHorizontalScrollView(Context context) {
super(context);
init(context);
}

void init(Context context) {
// remove the fading as the HSV looks better without it
setHorizontalFadingEdgeEnabled(false);
setVerticalFadingEdgeEnabled(false);
}

/**
* @param children
*            The child Views to add to parent.
* @param scrollToViewIdx
*            The index of the View to scroll to after initialisation.
* @param sizeCallback
*            A SizeCallback to interact with the HSV.
*/
public void initViews(View[] children, int scrollToViewIdx, SizeCallback sizeCallback) {
// A ViewGroup MUST be the only child of the HSV
ViewGroup parent = (ViewGroup) getChildAt(0);

// Add all the children, but add them invisible so that the layouts are calculated, but you can't see the Views
for (int i = 0; i < children.length; i++) {
children[i].setVisibility(View.INVISIBLE);
parent.addView(children[i]);
}

// Add a layout listener to this HSV
// This listener is responsible for arranging the child views.
OnGlobalLayoutListener listener = new MyOnGlobalLayoutListener(parent, children, scrollToViewIdx, sizeCallback);
getViewTreeObserver().addOnGlobalLayoutListener(listener);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
// Do not allow touch events.
return false;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// Do not allow touch events.
return false;
}

/**
* An OnGlobalLayoutListener impl that passes on the call to onGlobalLayout to a SizeCallback, before removing all the Views
* in the HSV and adding them again with calculated widths and heights.
*/
class MyOnGlobalLayoutListener implements OnGlobalLayoutListener {
ViewGroup parent;
View[] children;
int scrollToViewIdx;
int scrollToViewPos = 0;
SizeCallback sizeCallback;

/**
* @param parent
*            The parent to which the child Views should be added.
* @param children
*            The child Views to add to parent.
* @param scrollToViewIdx
*            The index of the View to scroll to after initialisation.
* @param sizeCallback
*            A SizeCallback to interact with the HSV.
*/
public MyOnGlobalLayoutListener(ViewGroup parent, View[] children, int scrollToViewIdx, SizeCallback sizeCallback) {
this.parent = parent;
this.children = children;
this.scrollToViewIdx = scrollToViewIdx;
this.sizeCallback = sizeCallback;
}

@Override
public void onGlobalLayout() {
// System.out.println("onGlobalLayout");

final HorizontalScrollView me = MyHorizontalScrollView.this;

// The listener will remove itself as a layout listener to the HSV
me.getViewTreeObserver().removeGlobalOnLayoutListener(this);

// Allow the SizeCallback to 'see' the Views before we remove them and re-add them.
// This lets the SizeCallback prepare View sizes, ahead of calls to SizeCallback.getViewSize().
sizeCallback.onGlobalLayout();

parent.removeViewsInLayout(0, children.length);

final int w = me.getMeasuredWidth();
final int h = me.getMeasuredHeight();

// System.out.println("w=" + w + ", h=" + h);

// Add each view in turn, and apply the width and height returned by the SizeCallback.
int[] dims = new int[2];
scrollToViewPos = 0;
for (int i = 0; i < children.length; i++) {
sizeCallback.getViewSize(i, w, h, dims);
// System.out.println("addView w=" + dims[0] + ", h=" + dims[1]);
children[i].setVisibility(View.VISIBLE);
parent.addView(children[i], dims[0], dims[1]);
if (i < scrollToViewIdx) {
scrollToViewPos += dims[0];
}
}

// For some reason we need to post this action, rather than call immediately.
// If we try immediately, it will not scroll.
new Handler().post(new Runnable() {
@Override
public void run() {
me.scrollBy(scrollToViewPos, 0);
}
});
}
}

/**
* Callback interface to interact with the HSV.
*/
public interface SizeCallback {
/**
* Used to allow clients to measure Views before re-adding them.
*/
public void onGlobalLayout();

/**
* Used by clients to specify the View dimensions.
*
* @param idx
*            Index of the View.
* @param w
*            Width of the parent View.
* @param h
*            Height of the parent View.
* @param dims
*            dims[0] should be set to View width. dims[1] should be set to View height.
*/
public void getViewSize(int idx, int w, int h, int[] dims);
}
}

Step 4 : create java class and named it as SimpleGestureFilter.java , and this class is responsible for detecting swipe motion events.

package com.shyam.smelsample.common;

import android.app.Activity;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;

public class SimpleGestureFilter extends SimpleOnGestureListener{

public final static int SWIPE_UP    = 1;
public final static int SWIPE_DOWN  = 2;
public final static int SWIPE_LEFT  = 3;
public final static int SWIPE_RIGHT = 4;

public final static int MODE_TRANSPARENT = 0;
public final static int MODE_SOLID       = 1;
public final static int MODE_DYNAMIC     = 2;

private final static int ACTION_FAKE = -13; //just an unlikely number
private int swipe_Min_Distance = 180;
private int swipe_Max_Distance = 300;
private int swipe_Min_Velocity = 100;

private int mode             = MODE_DYNAMIC;
private boolean running      = true;
private boolean tapIndicator = false;

private Activity context;
private GestureDetector detector;
private SimpleGestureListener listener;

public SimpleGestureFilter(Activity context,SimpleGestureListener sgl) {

this.context = context;
this.detector = new GestureDetector(context, this);
this.listener = sgl;
}

public void onTouchEvent(MotionEvent event){

if(!this.running)
return; 

boolean result = this.detector.onTouchEvent(event);

if(this.mode == MODE_SOLID)
event.setAction(MotionEvent.ACTION_CANCEL);
else if (this.mode == MODE_DYNAMIC) {

if(event.getAction() == ACTION_FAKE)
event.setAction(MotionEvent.ACTION_UP);
else if (result)
event.setAction(MotionEvent.ACTION_CANCEL);
else if(this.tapIndicator){
event.setAction(MotionEvent.ACTION_DOWN);
this.tapIndicator = false;
}

}
//else just do nothing, it's Transparent
}

public void setMode(int m){
this.mode = m;
}

public int getMode(){
return this.mode;
}

public void setEnabled(boolean status){
this.running = status;
}

public void setSwipeMaxDistance(int distance){
this.swipe_Max_Distance = distance;
}

public void setSwipeMinDistance(int distance){
this.swipe_Min_Distance = distance;
}

public void setSwipeMinVelocity(int distance){
this.swipe_Min_Velocity = distance;
}

public int getSwipeMaxDistance(){
return this.swipe_Max_Distance;
}

public int getSwipeMinDistance(){
return this.swipe_Min_Distance;
}

public int getSwipeMinVelocity(){
return this.swipe_Min_Velocity;
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {

final float xDistance = Math.abs(e1.getX() - e2.getX());
final float yDistance = Math.abs(e1.getY() - e2.getY());

if(xDistance > this.swipe_Max_Distance || yDistance > this.swipe_Max_Distance)
return false;

velocityX = Math.abs(velocityX);
velocityY = Math.abs(velocityY);
boolean result = false;

if(velocityX > this.swipe_Min_Velocity && xDistance > this.swipe_Min_Distance){
if(e1.getX() > e2.getX()) // right to left
this.listener.onSwipe(SWIPE_LEFT);
else
this.listener.onSwipe(SWIPE_RIGHT);

result = true;
}
else if(velocityY > this.swipe_Min_Velocity && yDistance > this.swipe_Min_Distance){
if(e1.getY() > e2.getY()) // bottom to up
this.listener.onSwipe(SWIPE_UP);
else
this.listener.onSwipe(SWIPE_DOWN);

result = true;
}

return result;
}

@Override
public boolean onSingleTapUp(MotionEvent e) {
this.tapIndicator = true;
return false;
}

@Override
public boolean onDoubleTap(MotionEvent arg) {
this.listener.onDoubleTap();;
return true;
}

@Override
public boolean onDoubleTapEvent(MotionEvent arg) {
return true;
}

@Override
public boolean onSingleTapConfirmed(MotionEvent arg) {

if(this.mode == MODE_DYNAMIC){        // we owe an ACTION_UP, so we fake an
arg.setAction(ACTION_FAKE);      //action which will be converted to an ACTION_UP later.
this.context.dispatchTouchEvent(arg);
}  

return false;
}

public static interface SimpleGestureListener{
void onSwipe(int direction);
void onDoubleTap();
}

}

Step 5 : create a java class and named it as CategoriesDTO.java

package com.shyam.smelsample.dto;

import java.util.ArrayList;

public class CategoriesDTO {
private String title;
private int id;
private int parentId;
private ArrayList<Integer> childrenIds;

public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}

public int getParentId() {
return parentId;
}
public void setParentId(int parentId) {
this.parentId = parentId;
}

public ArrayList<Integer> getChildrenIds() {
return childrenIds;
}
public void setChildrenIds(ArrayList<Integer> childrenIds) {
this.childrenIds = childrenIds;
}
}

Step 6 : Now it's turn to create Activity class, so create a java class and named it as MainActivity.java

package com.shyam.smelsample.gui;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.shyam.slidingmenuwithexpandablelistexample.R;
import com.shyam.smelsample.common.SimpleGestureFilter;
import com.shyam.smelsample.common.SimpleGestureFilter.SimpleGestureListener;
import com.shyam.smelsample.dto.CategoriesDTO;
import com.slidingview.MyHorizontalScrollView;
import com.slidingview.MyHorizontalScrollView.SizeCallback;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupClickListener;
import android.widget.ExpandableListView.OnGroupCollapseListener;
import android.widget.ExpandableListView.OnGroupExpandListener;
import android.widget.RelativeLayout.LayoutParams;

public class MainActivity extends Activity implements SimpleGestureListener
{
MyHorizontalScrollView scrollView;
View menu,app,btnSlide;
ExpandableListView categoryMenulistView;
private boolean menuOut = false;
HashMap<String, List<CategoriesDTO>> expListDataChild;
ArrayList<Integer> selectedData;
ArrayList<CategoriesDTO> categoryObjectsParentList;
ExpandableListAdapter listAdapter;
private SimpleGestureFilter detector; 
int idCounter = 10;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater inflater = LayoutInflater.from(this);
scrollView = (MyHorizontalScrollView) inflater.inflate(R.layout.activity_main, null);
setContentView(scrollView);

// Detect touched area 
detector = new SimpleGestureFilter(this,this);

menu = inflater.inflate(R.layout.menu_drawer, null);
app = inflater.inflate(R.layout.main_application, null);

btnSlide = app.findViewById(R.id.slideBtn);

btnSlide.setOnClickListener(new ClickListenerForScrolling(scrollView, menu));

categoryMenulistView = (ExpandableListView) menu.findViewById(R.id.categorylist);

selectedData = new ArrayList<Integer>();
prepareExpandableListData();
listAdapter = new ExpandableListAdapter(this, categoryObjectsParentList, expListDataChild);
categoryMenulistView.setAdapter(listAdapter);
// Listview Group click listener
categoryMenulistView.setOnGroupClickListener(new OnGroupClickListener() {

@Override
public boolean onGroupClick(ExpandableListView parent, View v,
int groupPosition, long id) {
ImageView groupImageView = (ImageView)v.findViewById(R.id.groupImageView);
int clickedId = categoryObjectsParentList.get(groupPosition).getId();
//check here is any of this childs are in selected data, if selected remove them and its parent
//if not selected, add this id to selected data.
ArrayList<Integer> childIds =categoryObjectsParentList.get(groupPosition).getChildrenIds();
boolean childrenSelected = checkChildrenSelected(childIds);
boolean flagClicked = iterateSelectedData(clickedId); // if already selected, ID will be removed from list.
if(flagClicked)
{
groupImageView.setBackgroundResource(R.color.thickgray);
}
else
{
if(childrenSelected)
{
groupImageView.setBackgroundResource(R.color.thickgray);
}
else
{
groupImageView.setBackgroundResource(R.color.sky_blue);
selectedData.add(clickedId);
}

}

return false;
}
});

// Listview Group expanded listener
categoryMenulistView.setOnGroupExpandListener(new OnGroupExpandListener() {

@Override
public void onGroupExpand(int groupPosition) {

}
});

// Listview Group collasped listener
categoryMenulistView.setOnGroupCollapseListener(new OnGroupCollapseListener() {
@Override
public void onGroupCollapse(int groupPosition) {

}
});

// Listview on child click listener
categoryMenulistView.setOnChildClickListener(new OnChildClickListener() {

@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
View groupView = parent.getExpandableListAdapter().getGroupView(groupPosition, true, null, (ViewGroup) v.getParent());
ImageView groupCatIcon = (ImageView) groupView.findViewById(R.id.groupImageView);

ImageView childImageView = (ImageView)v.findViewById(R.id.childImageView);
int childClickId = expListDataChild.get(categoryObjectsParentList.get(groupPosition).getTitle()).get(childPosition).getId();
int parentId =  expListDataChild.get(categoryObjectsParentList.get(groupPosition).getTitle()).get(childPosition).getParentId();
ArrayList<Integer> childIds =categoryObjectsParentList.get(groupPosition).getChildrenIds();
boolean flagClicked = iterateSelectedData(childClickId);
if(flagClicked)
{

childImageView.setBackgroundResource(R.color.lightgray);
boolean counterFlag = checkOtherSiblingsClicked(childIds);
if(counterFlag == true)//remove parent id from selected data
{
removeParentId(parentId);
groupCatIcon.setBackgroundResource(R.color.thickgray);

}
else // add parent id in selected data
{
selectedData.add(parentId);
groupCatIcon.setBackgroundResource(R.color.sky_blue);

}

}
else
{
childImageView.setBackgroundResource(R.color.sky_blue);
selectedData.add(childClickId);

removeParentId(parentId);
groupCatIcon.setBackgroundResource(R.color.thickgray);
}
listAdapter.notifyDataSetChanged();
return false;
}

});

final View[] children = new View[] { menu, app };
// Scroll to app (view[1]) when layout finished.
int scrollToViewIdx = 1;
scrollView.initViews(children, scrollToViewIdx, new SizeCallbackForMenu(btnSlide));
}
public void removeParentId(int parentId)
{
for(int i=0;i<selectedData.size();i++)
{

if (selectedData.get(i) == parentId)
{
selectedData.remove(i);

break;
}
else
{

}
}
}
private boolean iterateSelectedData(int clickedId)
{
Boolean flagClicked = false;
for(int i=0;i<selectedData.size();i++)
{

if (selectedData.get(i) == clickedId)
{
flagClicked = true;
selectedData.remove(i);
break;
}
else
{
flagClicked = false;

}
}
return flagClicked;

}
protected Boolean checkChildrenSelected(ArrayList<Integer> childIds) {
Boolean flagClicked = false;
int selectedDataSize = selectedData.size();

for(int i=0;i<selectedDataSize;i++)
{
int SelectedId = 0;
if(selectedData.size()>0)
{
SelectedId = selectedData.get(0);
}

int childsDataSize = childIds.size();
for(int j=0;j<childsDataSize;j++)
{
if (childIds.get(j) == SelectedId)
{
flagClicked = true;
selectedData.remove(0);
break;
}

}
}
return flagClicked;
}
private boolean checkOtherSiblingsClicked(ArrayList<Integer> childIds) {
boolean flag = false;
for(int i=0;(i<childIds.size() && !flag);i++)
{
int siblingId = childIds.get(i);

for(int j=0;j<selectedData.size();j++)
{
if(siblingId == selectedData.get(j))//remove parent id from selected data
{
flag = true;
break;
}
else
{
flag = false;
}
}
}

return flag;
}
private void prepareExpandableListData() {
expListDataChild = new HashMap<String,List<CategoriesDTO>>();
categoryObjectsParentList = new ArrayList<CategoriesDTO>();

//creating 5 parents
for(int i=1;i<=5;i++)
{
CategoriesDTO dto = new CategoriesDTO();
dto.setId(i);
dto.setTitle("Parent No-"+i);
dto.setParentId(-1);


if(i % 2 == 0)
{
//creating 3 children to each even number parent
ArrayList<CategoriesDTO> childrenList = new ArrayList<CategoriesDTO>();
ArrayList<Integer> childIds = new ArrayList<Integer>();

for(int j=1;j<=3;j++)
{
CategoriesDTO childdto = new CategoriesDTO();


childdto.setId(idCounter);
childdto.setTitle("children No-"+j);
childdto.setParentId(i);
childrenList.add(childdto);
childIds.add(idCounter);
idCounter++;
}
dto.setChildrenIds(childIds);
expListDataChild.put(dto.getTitle(), childrenList);
}
else
{
dto.setChildrenIds(new ArrayList<Integer>());
expListDataChild.put(dto.getTitle(),new ArrayList<CategoriesDTO>());

}
categoryObjectsParentList.add(dto); //adding parent to ArrayList
}

}
private void openDrawerMenu()
{
// Scroll to 0 to reveal menu
int left = 0;
scrollView.smoothScrollTo(left, 0);
menuOut = true;
}
public void closeDrawerFunction(View v)
{

int menuWidth = menu.getMeasuredWidth();
// Scroll to menuWidth so menu isn't on screen.
menuOut = false;
int left = menuWidth;

scrollView.smoothScrollTo(left, 0);

}

/**
* Helper for examples with a HSV that should be scrolled by a menu View's width.
*/
class ClickListenerForScrolling implements OnClickListener {
HorizontalScrollView scrollView;
View menu;

RelativeLayout categoryRelView;
/**
* Menu must NOT be out/shown to start with.
*/


public ClickListenerForScrolling(HorizontalScrollView scrollView, View menu) {
super();
this.scrollView = scrollView;
this.menu = menu;

categoryRelView = (RelativeLayout)menu.findViewById(R.id.category_relView);

}

@Override
public void onClick(View v) {
int menuWidth = menu.getMeasuredWidth();

// Ensure menu is visible
menu.setVisibility(View.VISIBLE);

if (!menuOut) {
// Scroll to 0 to reveal menu
int left = 0;
scrollView.smoothScrollTo(left, 0);


} else {
// Scroll to menuWidth so menu isn't on screen.
int left = menuWidth;

scrollView.smoothScrollTo(left, 0);

}
menuOut = !menuOut;
}
}

/**
* Helper that remembers the width of the 'slide' button, so that the 'slide' button remains in view, even when the menu is
* showing.
*/
class SizeCallbackForMenu implements SizeCallback {
int btn1width;
View btn1;
LayoutParams lp ;
public SizeCallbackForMenu(View btnSlide) {
super();
this.btn1 = btnSlide;

}

@Override
public void onGlobalLayout() {
lp = (LayoutParams) btn1.getLayoutParams();
btn1width = btn1.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;
}

@Override
public void getViewSize(int idx, int w, int h, int[] dims) {
dims[0] = w;
dims[1] = h;
final int menuIdx = 0;
if (idx == menuIdx) {
dims[0] = w - btn1width;
}
}
}

public class ExpandableListAdapter extends BaseExpandableListAdapter {

private Context _context;
private ArrayList<CategoriesDTO> catObjectsList;
private HashMap<String,List<CategoriesDTO>> catChildObjectsData;
public ExpandableListAdapter(Context context, List<String> listDataHeader,
HashMap<String, List<String>> listChildData) {
this._context = context;
}

public ExpandableListAdapter(Context context,ArrayList<CategoriesDTO> categoryObjectsList,
HashMap<String, List<CategoriesDTO>> expListDataChild2) {
this._context = context;
this.catObjectsList = categoryObjectsList;
this.catChildObjectsData = expListDataChild2;
}

@Override
public Object getChild(int groupPosition, int childPosititon) {
return this.catChildObjectsData.get(this.catObjectsList.get(groupPosition).getTitle()).get(childPosititon).getTitle();
}
public Object getChildObject(int groupPosition, int childPosititon) {
return this.catChildObjectsData.get(this.catObjectsList.get(groupPosition).getTitle()).get(childPosititon);
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}

@Override
public View getChildView(int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
CategoriesDTO dto = (CategoriesDTO) getChildObject(groupPosition, childPosition);
if (convertView == null) {
LayoutInflater infalInflater = (LayoutInflater) this._context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = infalInflater.inflate(R.layout.custom_expandable_menu_child, null);

}
ImageView catChildIcon = (ImageView)convertView.findViewById(R.id.childImageView);
boolean flagClicked = iterateSelectedData(dto.getId());
if(flagClicked)
{
catChildIcon.setBackgroundResource(R.color.sky_blue);
}
else
{
catChildIcon.setBackgroundResource(R.color.mediumgray);  
}
TextView txtListChild = (TextView) convertView.findViewById(R.id.lblListItem);
String childTitle = Html.fromHtml(dto.getTitle()).toString();


txtListChild.setText(childTitle);

return convertView;
}
private boolean iterateSelectedData(int clickedId)
{
Boolean flagClicked = false;
for(int i=0;i<selectedData.size();i++)
{

if (selectedData.get(i) == clickedId)
{
flagClicked = true;

break;
}
else
{
flagClicked = false;

}
}
return flagClicked;

}
@Override
public int getChildrenCount(int groupPosition) {
return this.catChildObjectsData.get(this.catObjectsList.get(groupPosition).getTitle()).size();
}

@Override
public Object getGroup(int groupPosition) {
return this.catObjectsList.get(groupPosition).getTitle();
}

@Override
public int getGroupCount() {
int count = 0;
for(int i=0;i<catObjectsList.size();i++)
{
CategoriesDTO dto = catObjectsList.get(i);
if(dto.getParentId()==-1)
{
count++;
}
}
return count;
}

@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}

@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
String headerTitle = (String) getGroup(groupPosition);
if (convertView == null) {
LayoutInflater infalInflater = (LayoutInflater) this._context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = infalInflater.inflate(R.layout.custom_expandable_menu_group, null);
}

TextView lblListHeader = (TextView) convertView.findViewById(R.id.lblListHeader);
ImageView catIcon = (ImageView)convertView.findViewById(R.id.groupImageView);

boolean flagClicked = iterateSelectedData(catObjectsList.get(groupPosition).getId());
if(flagClicked)
{
catIcon.setBackgroundResource(R.color.sky_blue);
}
else
{
catIcon.setBackgroundResource(R.color.thickgray);  
}
String parentTitle = Html.fromHtml(headerTitle).toString();
lblListHeader.setText(parentTitle);

return convertView;
}

@Override
public boolean hasStableIds() {
return false;
}

@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}

}
@Override
public boolean dispatchTouchEvent(MotionEvent me){
// Call onTouchEvent of SimpleGestureFilter class
this.detector.onTouchEvent(me);
return super.dispatchTouchEvent(me);
}
@Override
public void onSwipe(int direction) {
switch (direction) {

case SimpleGestureFilter.SWIPE_RIGHT : 
if(!menuOut)//if menu drawer is not opened
{
openDrawerMenu();
}
break;
case SimpleGestureFilter.SWIPE_LEFT : 
if(menuOut)//if menu drawer is opened
{
closeDrawerFunction(null);
}

break;
case SimpleGestureFilter.SWIPE_DOWN : 

break;
case SimpleGestureFilter.SWIPE_UP :  

break;

}
}
@Override
public void onDoubleTap() {

}
}

Step 7 : modify the android manifest file with below : 

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.shyam.slidingmenuwithexpandablelistexample"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="21" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.shyam.smelsample.gui.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="portrait"
            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Thats it! Now run the program and see the application.
Cheers....

Click Here For Complete Code

8 comments :

  1. You have not upload full code. What is the use of that.

    ReplyDelete
    Replies
    1. Send me your mail id, I will send the code.
      Thanks from next time, I will keep the total code to download.

      Delete
  2. Hi Shyam, nice tutorial. How can we add images in parent, pls help me.

    ReplyDelete
    Replies
    1. Hi Harshada,
      You can download the code and see the custom_expandable_menu_group.xml & custom_expandable_menu_child.xml in layout folder.
      In above said files I already used Imageviews to display images.

      Delete
  3. please send me the full code.......

    rahul0505@live.com

    ReplyDelete
    Replies
    1. https://drive.google.com/file/d/0B2mREa5sC69eSUt5eGJ4OVVFMDg/view

      Delete
  4. hi

    Can you send me full code. My mail id is pg2025@gmail.com

    ReplyDelete
    Replies
    1. https://drive.google.com/file/d/0B2mREa5sC69eSUt5eGJ4OVVFMDg/view

      Delete