OnKeyListener or OnKeydown not work if children views take the focus
Asked Answered
C

2

7

I want to listen to a ScrollView to see if it is scrolling. I used OnTouchListener, it works well. But when I want to add compatibility to trackball using OnKeyListener or overriding the OnKeydown method, it can't work. Seems like the child buttons taking the focus causes the problem.

Any solution or workaround to resolve this problem? Any help is appreciated.

Here are some demo codes to reproduce my problem:

public class TestActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    ScrollView scrollView = (ScrollView) findViewById(R.id.scroll_view);
    LinearLayout mainLayout = (LinearLayout) findViewById(R.id.main_layout);

    LinearLayout.LayoutParams wrapParams = new LinearLayout.LayoutParams(
            ViewGroup.LayoutParams.FILL_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT);
    for (int i = 0; i < 100; i++) {
        MyItem item = (MyItem) LayoutInflater.from(this).inflate(R.layout.item, null);
        item.setParent(scrollView);
        item.setBackgroundColor(Color.WHITE);
        item.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
                LayoutParams.WRAP_CONTENT));
        mainLayout.addView(item, wrapParams);
    }

    scrollView.setOnTouchListener(new OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // can go into here
        }

    });

    scrollView.setOnKeyListener(new OnKeyListener() {

        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                // never go in, unless no child button get focus
            }
            return false;
        }
    });
}

}

main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
    <com.fannyxie.MyScroller 
        android:id="@+id/scroll_view"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:descendantFocusability="beforeDescendants"
        android:clickable="true"
        android:focusable="true">
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:orientation="vertical">
            <LinearLayout android:id="@+id/main_layout"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:orientation="vertical">
            </LinearLayout>
        </LinearLayout>
    </com.fannyxie.MyScroller>
</LinearLayout>

item.xml:

<?xml version="1.0" encoding="utf-8"?>
<com.fannyxie.MyItem xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
    <TextView 
       android:layout_width="fill_parent"
       android:layout_height="wrap_content"
       android:text="TITLE"/>
    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:id="@+id/myButton" android:text="Button"></Button>
</com.fannyxie.MyItem>

MyScroller.java

public class MyScroller extends ScrollView {

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

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        //not go into here...
        Log.i("MyScroller", "onKeyDown");
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onTrackballEvent(MotionEvent event) {
        //not go into here...
        Log.i("MyScroller", "onTrackballEvent");
        return super.onTrackballEvent(event);
    }

    @Override
    protected boolean onRequestFocusInDescendants(int direction,
            Rect previouslyFocusedRect) {
        //some times go into here, when no button get the focus when entering first time
        Log.i("MyScroller", "request focus in descendants");
        return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
    }



}

MyItem.java

public class MyItem extends LinearLayout {

    private Button myButton;
    public MyItem(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected boolean onRequestFocusInDescendants(int direction,
            Rect previouslyFocusedRect) {
        // never go into here
        Log.i("MyItem", "request focus in descendants");
        return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
    }

}
Carolynecarolynn answered 11/7, 2012 at 16:59 Comment(0)
C
8

Thanks @Jeffrey. But I found a better way to do it. Just override the dispatchkeyevent method in ScrollView and handle trackball/keyboard events there. It works well.

public class MyScroller extends ScrollView {

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

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if ((KeyEvent.KEYCODE_DPAD_UP == event.getKeyCode() || KeyEvent.KEYCODE_DPAD_DOWN == event.getKeyCode())) {
            //handle key events here
    }
    return super.dispatchKeyEvent(event);
}

}
Carolynecarolynn answered 12/7, 2012 at 9:8 Comment(0)
R
0

touch events are passed from child to parent. if any child consumes the even (returns true), then it stops; it is not passed to the parent. so, 2 solutions,

first is to extend the view container class and override onInterceptTouchEvent(). here's an example where i did it for TabHost, but for you it might be LinearLayout, or whatever,

public class FlingableTabHost extends TabHost {
    private GestureDetector gestureDetector;

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

    public FlingableTabHost(Context context) {
        super(context);
        initGestureListener();
    }

    public boolean onInterceptTouchEvent(MotionEvent event){
        super.onInterceptTouchEvent(event);
        // handle touch event. only return true if you handle it yourself here.
    }
}

second way is to set the recursively set the touch listen for all child view to your custom touch listener,

ViewGroup myLayout = ...;
registerTouchListener(myLayout, myTouchListener);

private void registerTouchListener(View view, View.OnTouchListener listener) {
  view.setOnTouchListener(listener);
  if (view instanceof ViewGroup) {
    ViewGroup vg = (ViewGroup)view;
    for (int i = 0, n = vg.getChildCount(); i < n; i++) {
      View v = vg.getChildAt(i);
      registerTouchListener(v, listener);
    }
  }
}
Robichaud answered 11/7, 2012 at 17:14 Comment(2)
But my problem just occurs with trackball/keyboard. Touching works fine.Carolynecarolynn
I know there is a similar solution like your #2. I can set focus changed listener to all child views. But that looks a little ugly and not good at scalability(I should add the listener to every new child view in the future)Carolynecarolynn

© 2022 - 2024 — McMap. All rights reserved.