Обнаружение жестов и проблема с прокруткой

Я пытаюсь создать макет с помощью ViewFlipper, содержащего ScrollViews. Идея состоит в том, чтобы обнаружить горизонтальные прокрутки для перехода к предыдущему/следующему ScrollView. Кроме того, ScrollView содержит еще один ViewFlipper, содержащий ImageView с вертикальным детектором прокрутки, чтобы перейти к предыдущему/следующему ImageView. Когда я заменяю ScrollView на LinearLayout, оба детектора жестов работают нормально, но с ScrollView никто не работает (слушатели жестов даже не триггеры). Почему использование ScrollView отключает мои детекторы жестов? Как я могу заставить его работать?

Деятельность

public class ProduitHome extends Activity{  

    private Resources res;
    float density;

    private int position, parent_id;;
    private int num_products;

    private Produit produit;
    private ImageDownloader mImageLoader;   

    private ViewFlipper product_viewflipper;
    private ScrollView current_product_layout;
    Animation next_product_out, next_product_in, previous_product_in, previous_product_out;

    private GestureDetector galleryGestureDetector;
    private View.OnTouchListener galleryGestureListener;

    private GestureDetector productGestureDetector;
    private View.OnTouchListener productGestureListener;

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.produit_home);

        num_products = GlobalData.map_list_produits.get(parent_id).size();

        product_viewflipper = (ViewFlipper) findViewById(R.id.product_viewflipper);

        LayoutInflater inflater = getLayoutInflater();


        // Add num_products view to the viewflipper

        for(int i=0; i<num_products; i++){
            ScrollView product_detail = (ScrollView) inflater.inflate(R.layout.produit_detail, null);
            product_viewflipper.addView(product_detail);
        }


        // Set data and show current product

        current_product_layout = (ScrollView) product_viewflipper.getChildAt(position);
        product_viewflipper.setDisplayedChild(position);

        setProductData();


        // Set swipe listener to switch product

        productGestureDetector = new GestureDetector(new ProductGestureListener());
        productGestureListener = new View.OnTouchListener() 
        {
            public boolean onTouch(View v, MotionEvent event) 
            {
                if (productGestureDetector.onTouchEvent(event)) 
                {
                    return true;
                }
                else{
                    return false;
                }
            }
        };

        product_viewflipper.setOnTouchListener(productGestureListener);


        // Set switch product animation

        next_product_out = AnimationUtils.loadAnimation(this, R.anim.next_product_out);
        next_product_in = AnimationUtils.loadAnimation(this, R.anim.next_product_in);
        previous_product_in = AnimationUtils.loadAnimation(this, R.anim.previous_product_in);
        previous_product_out = AnimationUtils.loadAnimation(this, R.anim.previous_product_out);

    }


    class VerticalSwipeListener extends SimpleOnGestureListener {

        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

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

            final int SWIPE_MIN_DISTANCE = 80;
            final int SWIPE_MAX_OFF_PATH = 250;
            final int SWIPE_THRESHOLD_VELOCITY = 200; 

            try {
                if (Math.abs(e1.getX() - e2.getX()) > SWIPE_MAX_OFF_PATH)
                    return false;                

                ViewFlipper gallery = (ViewFlipper)current_product_layout.findViewById(R.id.product_gallery);

                if(e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
                    gallery.showNext();                    
                }  else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
                    gallery.showPrevious();
                }
                ((RadioGroup)current_product_layout.findViewById(R.id.gallery_nav)).check(gallery.getDisplayedChild());
            } catch (Exception e) {
            }
            return false;
        }
    }


    class ProductGestureListener extends SimpleOnGestureListener {

        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

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

            final int SWIPE_MIN_DISTANCE = 120;
            final int SWIPE_MAX_OFF_PATH = 250;
            final int SWIPE_THRESHOLD_VELOCITY = 200; 

            if(!Utils.IsOnline(ProduitHome.this)){
                SRPDialogs.show(ProduitHome.this, SRPDialogs.NOT_CONNECTED);
            }
            else{

                try {
                    if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
                        return false;
                    if(e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {

                        // show next product

                    }  else if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {

                     // show previous product

                    }
                } catch (Exception e) {
                }
            }
            return false;
        }
    }

    public void setProductData(){

        produit = GlobalData.map_produits.get(GlobalData.map_list_produits.get(parent_id).get(position).id); 

        TextView name = (TextView) current_product_layout.findViewById(R.id.name);
        name.setText(produit.libelle);

        // Load gallery

        int nPics = produit.list_url_pic.size();

        if(nPics>0){

            ViewFlipper gallery = (ViewFlipper) current_product_layout.findViewById(R.id.product_gallery);
            gallery.removeAllViews();           

            mImageLoader = new ImageDownloader(res,
                    ((BitmapDrawable)res.getDrawable(R.drawable.default_row_pic)).getBitmap(), 1);          

            final ViewFlipper.LayoutParams params_vf = new ViewFlipper.LayoutParams(ViewFlipper.LayoutParams.FILL_PARENT, ViewFlipper.LayoutParams.FILL_PARENT);

            for(String url : produit.list_url_pic){

                // Add images to viewflipper
                ImageView imageView_p = new ImageView(this);
                imageView_p.setLayoutParams(params_vf);
                imageView_p.setScaleType(ImageView.ScaleType.CENTER_CROP);
                imageView_p.setTag(url);
                imageView_p.setImageResource(R.drawable.default_row_pic);
                mImageLoader.download(url, imageView_p);
                gallery.addView(imageView_p);
            } 

            // Swipe detector to switch picture in gallery

            galleryGestureDetector = new GestureDetector(new VerticalSwipeListener());
            galleryGestureListener = new View.OnTouchListener() 
            {
                public boolean onTouch(View v, MotionEvent event) 
                {
                    if (galleryGestureDetector.onTouchEvent(event)) 
                    {
                        return true;
                    }
                    else{
                        return false;
                    }
                }
            };

        }
    }
}

Родительский макет

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

    <!-- more stuff -->

    <ViewFlipper android:id="@+id/product_viewflipper"
        android:layout_width="fill_parent" android:layout_height="fill_parent"
        android:layout_below="@id/header_logo" />

    <!-- more stuff -->

</RelativeLayout>

Макет детства ViewFlipper

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent" android:layout_height="fill_parent"
    android:background="@color/grey_bg">

    <LinearLayout android:layout_width="fill_parent"
        android:layout_height="wrap_content" android:orientation="vertical"
        android:gravity="center_horizontal">

        <!-- more stuff -->

        <RelativeLayout android:layout_below="@id/bg_content_top"
            android:layout_above="@id/bg_content_bottom"
            android:layout_width="300dp" android:layout_height="fill_parent"
            android:background="@drawable/bg_content"
            android:paddingRight="3dp" android:paddingLeft="3dp"
            android:layout_centerHorizontal="true">

           <!-- more stuff -->

            <RelativeLayout android:id="@+id/content"
                android:layout_below="@id/title_container"
                android:layout_above="@id/bg_content_bottom"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:paddingLeft="7dp" android:paddingRight="7dp"
                android:paddingTop="10dp" android:paddingBottom="10dp">               

                <ViewFlipper android:id="@+id/product_gallery"
                    android:clickable="true" android:focusable="false"
                    android:layout_width="100dp" android:layout_height="150dp"
                    android:layout_marginRight="10dp"
                    android:layout_below="@id/title_container"
                    android:layout_toRightOf="@id/gallery_nav" />

                <!-- more stuff -->

            </RelativeLayout>

        </RelativeLayout>

        <!-- more stuff -->

    </LinearLayout>

</ScrollView>

Ответ 1

Мне пришлось добавить

@Override
public boolean dispatchTouchEvent(MotionEvent ev){
    super.dispatchTouchEvent(ev);    
    return productGestureDetector.onTouchEvent(ev); 
}

в моей работе.

Ответ 2

Мой ответ такой же, как мой последний, но я буду более явным.

Изменить

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent" android:layout_height="fill_parent"
    android:background="@color/grey_bg">

to

<your.packagename.CustomScrollView ... etc>

Создать класс

public class CustomScrollView extends ScrollView {
    private GestureDetector gestureDetector;
    View.OnTouchListener gestureListener;

    public CustomScrollView(Context context, AttributeSet attrs) {
          super(context, attrs);
          gestureDetector = new GestureDetector(new YScrollDetector());
          setFadingEdgeLength(0);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //Call super first because it does some hidden motion event handling
        boolean result = super.onInterceptTouchEvent(ev);
       //Now see if we are scrolling vertically with the custom gesture detector
       if (gestureDetector.onTouchEvent(ev)) {
            return result;
       } 
       //If not scrolling vertically (more y than x), don't hijack the event.
        else {
            return false;
       }
    }

    // Return false if we're scrolling in the x direction  
    class YScrollDetector extends SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float     distanceY) {
        try {
            if (Math.abs(distanceY) > Math.abs(distanceX)) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            // nothing
        }
        return false;
    }
}

Этот код исходит из главного ответа здесь: HorizontalScrollView в области обработки прокрутки ScrollView (Так что дайте ему проголосовать, если ответ будет полезен).

Если вы хотите получить перпендикулярное направление, измените

if (Math.abs(distanceY) > Math.abs(distanceX)) {

к

if (Math.abs(distanceY) < Math.abs(distanceX)) {

CustomScrollView будет только перехватывать swipes на одной оси, горизонтально или вертикально, в зависимости от двух строк кода выше. Так как он только перехватывает swipes на одной оси, остальные события будут переданы его дочерним элементам, теперь вы можете обработать событие своим слушателем жестов/касанием в своей деятельности.

Вам также нужно будет изменить любые ссылки/отливки на ScrollView на новый пользовательский (CustomScrollView).

Ответ 3

parentScrollView.setOnTouchListener(new View.OnTouchListener() {

                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    return productGestureDetector.onTouchEvent(event);
                }
            });

установите свой основной прокрутка на TouchListener и реализуйте этот код для этой работы. Я надеюсь, что это будет полезно для вас.