3

This question seems trivial. I want to create a button like keyboard keys for my app. When I click on it, a popup window appears above that button showing the letter pressed. Everything works great till now except one thing. When I add onFocusChangedListener to the button, nothing happens. I need to let my button act as a keyboard key, but I don't know how.

Keyboard Button Example

As you can see here, when a button is focused, a popup window appears. I want to do that, but onFocusChangeListener doesn't work. I know I can use a KeyboardView to achieve that, but I don't want to use that due to some other issues like centering buttons and setting keys' height with layout_weight. So I need to make it with normal buttons.

What I tried:

My First Try:

button.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (hasFocus) {
            popupWindow.showAtLocation(keyboardPopup, Gravity.NO_GRAVITY, location.left - 10, location.top - button.getHeight());
        } else {
            popupWindow.dismiss();
        }
    }
});

Result: Nothing happened. The popup window didn't appear at all.

Edit: After I have added button.setFocusableInTouchMode(true); as Ashley suggested, onFocusChanged is now getting called, but it acts so weird. The popup is sometimes shown, but at the same time when it is shown, it never disappears...

My Second Try:

button.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                popupWindow.showAtLocation(keyboardPopup, Gravity.NO_GRAVITY, location.left - 10, location.top - button.getHeight());
                break;
            case MotionEvent.ACTION_UP:
                popupWindow.dismiss();
                break;
        }
        return true;
    }
});

Result: This one acted so weird. Sometimes the popup shows and sometimes not, but when it is shown, the button didn't also change its state. It should have been focused, but nothing happened to the button, it acts as if it was in a normal state (Button's background doesn't change with state_focused declared in my drawable xml). It seems that onTouchListener overrides the button's functionality.

Here is a part of my layout:

<LinearLayout
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="0dp"
    android:layout_weight="3">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="Q"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="W"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="E"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="R"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="T"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="Y"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="U"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="I"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="O"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="P"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <View
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="0.5" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="A"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="S"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="D"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="F"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="G"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="H"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="J"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="K"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="L"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <View
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="0.5" />

    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <View
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1.5" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="Z"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="X"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="C"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="V"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="B"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="N"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <Button
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="M"
            android:background="@drawable/keyboard_button"
            android:textColor="#FFFFFF"
            android:onClick="onKeyboardClick" />

        <View
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="1.5" />

    </LinearLayout>

</LinearLayout>

In code:

public void onKeyboardClick(View view) {
    //The view pressed is a button.
    final Button button = (Button) view;

    //Create a PopupWindow.
    LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
    final View keyboardPopup = inflater.inflate(R.layout.keyboard_popup, null);
    final PopupWindow popupWindow = new PopupWindow(keyboardPopup, view.getWidth() + 20, view.getHeight());
    TextView keyboardKey = (TextView) keyboardPopup.findViewById(R.id.keyboard_key);
    keyboardKey.setText(button.getText().toString());

    //Get button location to show the popup above it.
    int[] keyLocation = new int[2];
    button.getLocationOnScreen(keyLocation);
    final Rect location = new Rect();
    location.left = keyLocation[0];
    location.top = keyLocation[1];
    location.right = location.left + button.getWidth();
    location.bottom = location.top + button.getHeight();

    //This is a temporary solution. I don't want to use that.
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //Show popup.
            popupWindow.showAtLocation(keyboardPopup, Gravity.NO_GRAVITY, location.left - 10, location.top - button.getHeight());
            Handler handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    //Dismiss popup.
                    popupWindow.dismiss();
                }
            }, 200);
        }
    });
}

Any help will be greatly appreciated. Thanks.

Community
  • 1
  • 1
Hussein El Feky
  • 6,627
  • 5
  • 44
  • 57
  • Have you tried adding a` Log.i` to see if `onFocusChange` is being called? – Bhargav Aug 21 '15 at 19:20
  • @Bhargav Yes, It is called if I use Ashley Alvarado's answer, but is not called if I didn't write what he wrote. However, it still acts so weird and doesn't show the popup correctly. It **sometimes** shows but never disappears at the same time. – Hussein El Feky Aug 21 '15 at 21:19

3 Answers3

1

I suggest you use the second try onTouchListener! You had 2 issues with that:

1. The button does not change state

Indeed, when you override the onTouchListener you must simulate the state yourself. Please take a look at this SO thread for how it is done: "Press and hold" button on Android needs to change states (custom XML selector) using onTouchListener

2. Sometime shows and sometimes not

This should not happen, especially if you handle all the relevant touch event cases properly. You will want to show the pop up when the user touch down on a button and hide that pop up when a user move out of the button (either by swiping out or taking the finger off the screen).

Please try the following code sample:

button.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                v.setPressed(true);
                showPopupWindow(...);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_OUTSIDE:
            case MotionEvent.ACTION_CANCEL:
                v.setPressed(false);
                hidePopupWindow(...);
                break;
        }
        return true;
    }
});
  • Notice the use of getActionMasked instead of getAction to better handle multi touch.
  • Notice the v.setPressed(...); - this will update the button state.
  • Notice the different cases of hiding the pop up.
Community
  • 1
  • 1
Eyal Biran
  • 5,656
  • 1
  • 28
  • 29
0

I didn't notice it in your code but potentially you left out.

btn.setFocusableInTouchMode(true);
btn.setFocusable(true);

For the on focus change listener.

holder.sAttedenceList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
            {
                if (!parent.hasFocus()) {
                    return;
                }

not the full code but this the general structure that worked for me.

Ashley Alvarado
  • 1,078
  • 10
  • 17
  • I have tried that, now onFocusChanged is called but still acts so weird. It **sometimes** show the popup window, and if it is shown, it never disappears. – Hussein El Feky Aug 21 '15 at 21:22
  • Ya, i get your I had problems with this earlier. I think it was by luck that it worked for me earlier when I randomly tried to get parent. What worked previously for me is the code I just added on top. May or may not work for you. – Ashley Alvarado Aug 21 '15 at 21:33
  • I have added a bounty if you can help me... :) – Hussein El Feky Aug 25 '15 at 00:25
0

Instead of setting an OnTouchListener, try subclassing Button and overriding onTouchEvent itself:

public class KeyboardButton extends Button
{
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                popupWindow.showAtLocation(keyboardPopup, Gravity.NO_GRAVITY, location.left - 10, location.top - button.getHeight());
                break;
            case MotionEvent.ACTION_UP:
                popupWindow.dismiss();
                break;
        }
        return super.onTouchEvent(event);
    }
}