Add button to a SlidingDrawer handle?
Asked Answered
M

4

9

I've been working on this for a while. The Idea started simple, I wanted a button on a SlidingDrawer handle to allow the user to view settings specific to the content of the drawer. So I made a layout with a button off to the side and set it as the handle. The drawer drew fine, but would not allow the button (on the handle) to be pressed. When ever I try to click the thing, the click is interpreted as a handle click, and toggle the state of the drawer.

Does anyone know whats going on?

Thanks ~Aedon

Mennonite answered 14/4, 2011 at 16:50 Comment(0)
S
5

You can suppress the action that interprets a click on the handle button as an "open" with an attribute in the SlidingDrawer element in the layout XML. Like this:

<SlidingDrawer android:layout_width="fill_parent"android:id="@+id/SlidingDrawer" android:handle="@+id/slideHandleButton"
                    android:content="@+id/txtHolder" android:layout_height="fill_parent"
                    android:orientation="horizontal" android:allowSingleTap="false">

Just make the android:allowSingleTap="false" Then just implement a click handler for the button like you normally would. This will stop it from opening/closing the drawer, but you might need to intercept the events for the button to get it to do what YOU want it to do.

Servomechanism answered 15/4, 2011 at 16:13 Comment(5)
Thank you sir, after a little tinkering, it works flawlessly. I think you just taught me a lesson that I really should have learned earlier. : )Mennonite
@AedonEtLIRA can you give me a sample code. I'm trying the same thing but it wouldn't work. I am intercepting the onClickEvent by implementing an onClickListener of a button which is inside my handle. But the event is not fired. Can you tell me why?Usance
@Aki - Did you check to make sure that the click was in the bounds of the button? You have to get the hit rect of the button and then check if the intercepted touch was within the bounds. If so: manually fire off the buttons onClick(View) method. Hope that helps, if not I'm still here :)Mennonite
@AedonEtLIRA I implemented an onClickListener on the Button itself. Will that not work? And, why do I need to make sure that the click was in the bounds of the Button, its the Button's onClickListener which is being fired, doesn't that mean that the click was within the bounds of the Button?Usance
@Aki - Yes it does, but the sliding drawer is a greedy listener. This mean that the button will never receive the click unless you redirect the click to the button, hence checking if the click was within the bounds and forwarding the event.Mennonite
H
13

I'll post my implementation, to save others the trouble.

You basically have to extend the SlidingDrawer class and handle the onInterceptTouch events to pass through when they're on top of items inside the handle layout.

This assumes you are using a ViewGroup (e.g. any layout) for the handle and all the views inside it are clickable.

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SlidingDrawer;

public class ClickableSlidingDrawer extends SlidingDrawer {

    private ViewGroup mHandleLayout;
    private final Rect mHitRect = new Rect();

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

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



    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        View handle = getHandle();

        if (handle instanceof ViewGroup) {
            mHandleLayout = (ViewGroup) handle;
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (mHandleLayout != null) {
            int childCount = mHandleLayout.getChildCount();
            int handleClickX = (int)(event.getX() - mHandleLayout.getX());
            int handleClickY = (int)(event.getY() - mHandleLayout.getY());

            Rect hitRect = mHitRect;

            for (int i=0;i<childCount;i++) {
                View childView = mHandleLayout.getChildAt(i);
                childView.getHitRect(hitRect);

                if (hitRect.contains(handleClickX, handleClickY)) {
                    return false;
                }
            }
        }

        return super.onInterceptTouchEvent(event);
    }
}

Then, in your layout .xml just use <my.package.name.ClickableSlidingDrawer> instead of <SlidingDrawer>

Hetaerism answered 8/12, 2011 at 17:27 Comment(1)
Also, if you always call super.onInterceptTouchEvent, it won't block the drag event over the button.Disseise
H
13

I tried out d4n3's implementation, but since my handle contains a button that is nested within multiple ViewGroups, I had to modify it to make it work.

My implementations also assumes that you are using a ViewGroup for the handle, but the child views don't have to be clickable. Also, you have to set the tag to "click_intercepted" of the View(s) that you want to be clickable in the handle. Only child views with this specific tag set will be considered for clicks within the handle. This way, you can layout your handle anyway you want, and still act appropriately on clicks on specific Views (e.g. a Button) in the handle. Also, with this implementation, you can still both drag and click the handle to toggle its state.

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SlidingDrawer;

public class ClickableSlidingDrawer extends SlidingDrawer
{
    private static final String TAG_CLICK_INTERCEPTED = "click_intercepted";

    private ViewGroup mHandleLayout;
    private final Rect mHitRect = new Rect();

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

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

    @Override
    protected void onFinishInflate()
    {
        super.onFinishInflate();

        View handle = getHandle();

        if (handle instanceof ViewGroup)
        {
            mHandleLayout = (ViewGroup) handle;
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event)
    {
        if (mHandleLayout != null)
        {
            int clickX = (int) (event.getX() - mHandleLayout.getLeft());
            int clickY = (int) (event.getY() - mHandleLayout.getTop());

            if (isAnyClickableChildHit(mHandleLayout, clickX, clickY))
            {
                return false;
            }
        }
        return super.onInterceptTouchEvent(event);
    }

    private boolean isAnyClickableChildHit(ViewGroup viewGroup, int clickX, int clickY)
    {
        for (int i = 0; i < viewGroup.getChildCount(); i++)
        {
            View childView = viewGroup.getChildAt(i);

            if (TAG_CLICK_INTERCEPTED.equals(childView.getTag()))
            {
                childView.getHitRect(mHitRect);

                if (mHitRect.contains(clickX, clickY))
                {
                    return true;
                }
            }

            if (childView instanceof ViewGroup && isAnyClickableChildHit((ViewGroup) childView, clickX, clickY))
            {
                return true;
            }
        }
        return false;
    }
}
Holleyholli answered 22/3, 2012 at 13:59 Comment(3)
with a little changes this fit exatlly what i was looking for... just a problem... when i hit the "handle" space to drag the slider all the children get the "pressed" state. Do you know a work around this issue? My only guess was to use the dupilicataParentState="false" on the children but that seems to not be the solution. Any ides?Thereupon
I have given you both upvotes, but yours was the best implementation.Collective
for me, int clickX = (int) (mHandleLayout.getRight() - event.getX()); seemed to work instead of the one from above.Jimmiejimmy
S
5

You can suppress the action that interprets a click on the handle button as an "open" with an attribute in the SlidingDrawer element in the layout XML. Like this:

<SlidingDrawer android:layout_width="fill_parent"android:id="@+id/SlidingDrawer" android:handle="@+id/slideHandleButton"
                    android:content="@+id/txtHolder" android:layout_height="fill_parent"
                    android:orientation="horizontal" android:allowSingleTap="false">

Just make the android:allowSingleTap="false" Then just implement a click handler for the button like you normally would. This will stop it from opening/closing the drawer, but you might need to intercept the events for the button to get it to do what YOU want it to do.

Servomechanism answered 15/4, 2011 at 16:13 Comment(5)
Thank you sir, after a little tinkering, it works flawlessly. I think you just taught me a lesson that I really should have learned earlier. : )Mennonite
@AedonEtLIRA can you give me a sample code. I'm trying the same thing but it wouldn't work. I am intercepting the onClickEvent by implementing an onClickListener of a button which is inside my handle. But the event is not fired. Can you tell me why?Usance
@Aki - Did you check to make sure that the click was in the bounds of the button? You have to get the hit rect of the button and then check if the intercepted touch was within the bounds. If so: manually fire off the buttons onClick(View) method. Hope that helps, if not I'm still here :)Mennonite
@AedonEtLIRA I implemented an onClickListener on the Button itself. Will that not work? And, why do I need to make sure that the click was in the bounds of the Button, its the Button's onClickListener which is being fired, doesn't that mean that the click was within the bounds of the Button?Usance
@Aki - Yes it does, but the sliding drawer is a greedy listener. This mean that the button will never receive the click unless you redirect the click to the button, hence checking if the click was within the bounds and forwarding the event.Mennonite
D
0

First make a layout and put your Handle content in it (say you put in handle_content.xml).

Second replace your current handle handle with this:

<include android:id="@id/handle" 
android:layout="@layout/handle_content.xml"/>

Now do as below (I say this because below work correctly if u do as above)

This is my implementation:

package com.examples.my.views;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.SlidingDrawer;

import com.examples.my.MainFragmentActivity;

public class MYSlidingDrawer extends SlidingDrawer {

    private View button;
    private int height;
    private MainFragmentActivity activity;

    public MYSlidingDrawer (Context context, AttributeSet attrs) {
        super(context, attrs);
        DisplayMetrics metrics = this.getResources().getDisplayMetrics();
        height = metrics.heightPixels;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        int left = button.getLeft();
        int top = button.getTop();
        int right = button.getRight();
        int bottom = button.getBottom();
        Rect rect = new Rect(left, top, right, bottom);
        int x = (int) event.getX();
        int y = (int) event.getY();
        if (isOpened()) {
            if (rect.contains(x, y)) {
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    if (activity != null)
                        {
                         //HERE DO YOUR WORK
                         // Like activity.tooglePlay();
                        }

                }
                return true;
            }
        } else {
            y -= height;
            if (rect.contains(x, Math.abs(y))) {
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    if (activity != null)
                         {
                          //HERE DO YOUR WORK
                          // Like activity.tooglePlay();
                          }
                }
                return true;
            }
        }
        return super.onTouchEvent(event);
    }

    public void setButton(View button) {
        this.button = button;
    }

    public void setActivity(MainFragmentActivity activity) {
        this.activity = activity;
    }

}

And now define this in which you include MYSlidingDrawer:

        MYSlidingDrawer drawer = (MYSlidingDrawer) findViewById(R.id.drawer);
        drawer.setActivity(this);
        Button btn = (Button) findViewById(R.id.play_btn);//button inside your handle
        drawer.setButton(btn);

Hope this help you.

Diastrophism answered 20/7, 2013 at 16:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.