使用按钮滚动图库

发布于 2024-12-15 11:50:07 字数 2966 浏览 10 评论 0原文

我有一个 Gallery,其 Adapter 创建了多个 LinearLayout 实例。然而,这些线性布局实例有按钮,当有人单击按钮时,他们无法拖动图库。

我的想法是有一个用户可以滚动的菜单。通常使用 ScrollView 完成这种事情,但因为我希望滚动视图“捕捉”到当前按钮页面,所以 Gallery 效果更好。

这个问题与这个问题类似: Android gallery of LinearLayouts

但是,虽然我已经修复了拖动问题时“按钮显示为已单击”,我似乎无法通过让按钮作为拖动区域的一部分来使其像 ScrollView 那样工作。

有什么提示吗?

不确定代码是否相关,但它就在这里。

包含画廊的布局:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

        <Gallery
            android:id="@+id/gallery"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" android:fadingEdge="none"
            android:spacing="0dp"/>

</FrameLayout>

填充画廊的测试活动:

import com.zehfernando.display.widgets.ZGallery;

public class ScrollTestActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.scrolltest);

        Gallery gallery = (Gallery) findViewById(R.id.gallery);
        gallery.setAdapter(new LayoutAdapter(this));
    }

    public class LayoutAdapter extends BaseAdapter {
        private Context mContext;

        public LayoutAdapter(Context c) {
            mContext = c;
        }

        public int getCount() {
            return 3;
        }

        public Object getItem(int position) {
            return position;
        }

        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            LayoutInflater vi = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View v = vi.inflate(R.layout.scrolllayout, null);
            v.setMinimumWidth(getWindowManager().getDefaultDisplay().getWidth());
            return v;
        }
    }
}

进入画廊的框架的布局:

<?xml version="1.0" encoding="utf-8"?>
<com.zehfernando.display.widgets.ScrollableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/buttonRegister"
        android:layout_width="200dp"
        android:layout_height="72dp"
        android:text="REGISTER"/>

    <Button
        android:id="@+id/buttonUnregister"
        android:layout_width="match_parent"
        android:layout_height="72dp"
        android:text="UNREGISTER" />

</com.zehfernando.display.widgets.ScrollableLinearLayout>

“ScrollableLinearLayout”只是我的类,它扩展了 LinearLayout 以覆盖 onPressed。

I have a Gallery whose Adapter creates several LinearLayout instances. Those linear layout instances have buttons, however, and when someone clicks the buttons, they can't drag the gallery.

My idea is having a menu that the user can scroll through. The kind of thing that would normally be done with a ScrollView, but because I want the scrolled view to 'Snap' to the current button pages, a Gallery works better.

This question is similar to this one: Android gallery of LinearLayouts

However, while I've fixed the 'buttons appear clicked' when dragging issue, I can't seem to make it work like a ScrollView does, by having the buttons work as part of the dragging area.

Any hints?

Not sure if the code is relevant, but here it is.

Layout that contains the gallery:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

        <Gallery
            android:id="@+id/gallery"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" android:fadingEdge="none"
            android:spacing="0dp"/>

</FrameLayout>

The test activity that populates the gallery:

import com.zehfernando.display.widgets.ZGallery;

public class ScrollTestActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.scrolltest);

        Gallery gallery = (Gallery) findViewById(R.id.gallery);
        gallery.setAdapter(new LayoutAdapter(this));
    }

    public class LayoutAdapter extends BaseAdapter {
        private Context mContext;

        public LayoutAdapter(Context c) {
            mContext = c;
        }

        public int getCount() {
            return 3;
        }

        public Object getItem(int position) {
            return position;
        }

        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            LayoutInflater vi = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View v = vi.inflate(R.layout.scrolllayout, null);
            v.setMinimumWidth(getWindowManager().getDefaultDisplay().getWidth());
            return v;
        }
    }
}

The layout for the frames that go inside the gallery:

<?xml version="1.0" encoding="utf-8"?>
<com.zehfernando.display.widgets.ScrollableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/buttonRegister"
        android:layout_width="200dp"
        android:layout_height="72dp"
        android:text="REGISTER"/>

    <Button
        android:id="@+id/buttonUnregister"
        android:layout_width="match_parent"
        android:layout_height="72dp"
        android:text="UNREGISTER" />

</com.zehfernando.display.widgets.ScrollableLinearLayout>

"ScrollableLinearLayout" is just my class that extends LinearLayout to override onPressed.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

云裳 2024-12-22 11:50:07

好吧,我想我明白了,所以这是以防将来有人遇到这种情况。

我不太清楚触摸事件是如何沿着显示列表传播的,所以这比我愿意承认的要多得多的尝试和错误,但基本上:我们可以拦截父级上的触摸事件而不让它传播到子级,基本上转动按钮毫无用处(允许用户单击并拖动它,将事件发送到父级的 onTouchEvent )。这是通过 onInterceptTouchEvent 方法完成的。

因此,我没有使用 Gallery,而是对其进行了扩展(暂时将其称为 ZGallery)。这足以使包含的按钮变得无用:

@Override
public boolean onInterceptTouchEvent(MotionEvent __e) {
    return true;
}

但是,当然,我想确保按钮有效(它们是可点击的),同时还允许拖动。

可能有一个更聪明的方法来做到这一点,但我所做的是拦截我的新画廊上的触摸事件(如上所示),但允许它通过(返回false)直到用户将“光标”移动给定的阈值 - 然后我将其解释为拖动意图,开始正确拦截触摸事件。这会导致触摸事件发送到我自己的画廊,按预期工作。

您可以修改它以使其也仅适用于垂直或水平拖动。

无论如何,这是一个 Gallery 类的简化版本,允许拖动其中的任何元素:

public class ZGallery extends Gallery {

    // Constants
    protected static final float DRAG_THRESHOLD = 10; // If dragging for more than this amount of pixels, means it's a scroll

    // Properties
    protected boolean isPressed;
    protected float startPressX;
    protected float startPressY;
    protected boolean isDragging;

    // ================================================================================================================
    // CONSTRUCTOR ----------------------------------------------------------------------------------------------------

    public ZGallery(Context context) {
        this(context, null);
    }

    public ZGallery(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.galleryStyle);
    }

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

    // ================================================================================================================
    // EVENT INTERFACE ------------------------------------------------------------------------------------------------

    @Override
    public boolean onInterceptTouchEvent(MotionEvent __e) {
        // Intercepts all touch screen motion events.  This allows you to watch events as they are dispatched to your children, and take ownership of the current gesture at any point.
        // Return true to steal motion events from the children and have them dispatched to this ViewGroup through onTouchEvent().
        // The current target will receive an ACTION_CANCEL event, and no further messages will be delivered here.

        //return super.onInterceptTouchEvent(__e); // super always returns false

        // If this function returns TRUE, NO children get dragging events. This only happens
        // the first interception (mouse down); if true is returned, nothing is intercepted anymore, and
        // events are passed to onTouchEvent directly.
        // If FALSE is returned, this may be called again, but only if there's a children receiving the
        // events instead of this.
        // In sum, once onTouchEvent is called here, onInterceptTouchEvent is not called anymore.

        // Interprets drag data
        return evaluateTouchEvent(__e);

    }

    @Override
    public boolean onTouchEvent(MotionEvent __e) {
        // Interprets drag data
        evaluateTouchEvent(__e);

        // Properly lets superclass interpret touch events (for dragging, fling, etc)
        return super.onTouchEvent(__e);
    }

    protected boolean evaluateTouchEvent(MotionEvent __e) {
        // Interprets motion to see if the user is dragging the View
        // This will run in parallel with the children events
        float dragDeltaX;
        float dragDeltaY;

        switch (__e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // Pressing...
                isPressed = true;
                startPressX = __e.getX();
                startPressY = __e.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                // Moving...
                if (isPressed && !isDragging) {
                    dragDeltaX = __e.getX() - startPressX;
                    dragDeltaY = __e.getY() - startPressY;

                    if (Math.abs(dragDeltaX) > DRAG_THRESHOLD || Math.abs(dragDeltaY) > DRAG_THRESHOLD) {
                        // Moved too far, means it's dragging!

                        // Inject click from correct position so superclass code knows where to drag from
                        MotionEvent me = MotionEvent.obtain(__e);
                        me.setAction(MotionEvent.ACTION_DOWN);
                        me.setLocation(__e.getX() - dragDeltaX, __e.getY() - dragDeltaY);
                        super.onTouchEvent(me);

                        isDragging = true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                // Releasing...
                if (isPressed) {
                    isPressed = false;
                    // Let go while pressed
                    if (isDragging) {
                        // Was dragging, so just go back
                        isDragging = false;
                    } else {
                        // Was not dragging, this will trigger a click
                    }
                }
                break;
        }


        // If not dragging, event should be passed on
        // If dragging, the event should be intercepted and interpreted by this gallery's onTouchEvent instead
        return isDragging;
    }
}

它似乎工作得很好。希望它对其他人有帮助!

Ok, I think I got it, so here it is in case anyone runs into this in the future.

I didn't know very well how touch events propagated down the display list, so this took a lot more trial and error than I'd like to admit, but basically: one can intercept touch events on a parent and not let it propagate to children, basically turning buttons useless (allowing users to click and drag it, sending the events to the parent's onTouchEvent instead). This is done with the onInterceptTouchEvent method.

So instead of having a Gallery, I've extended it (calling it a ZGallery for now). This is enough to make the contained buttons useless:

@Override
public boolean onInterceptTouchEvent(MotionEvent __e) {
    return true;
}

But of course, I wanted to make sure the buttons worked (that they were clickable), while also allowing drag.

There's probably a more clever way to do this, but what I do is intercept the touch event on my new Gallery (like above), but allowing it to go through (returning false) until the user has moved the 'cursor' by a given threshold - then I interpret it as a dragging intention, start properly intercepting the touch event. This causes touch events to be sent to my own gallery, working as intended.

You can modify it to make it work for vertical or horizontal drags only too.

So anyway, this is a simplified version of a Gallery class that allows dragging on any element inside of it:

public class ZGallery extends Gallery {

    // Constants
    protected static final float DRAG_THRESHOLD = 10; // If dragging for more than this amount of pixels, means it's a scroll

    // Properties
    protected boolean isPressed;
    protected float startPressX;
    protected float startPressY;
    protected boolean isDragging;

    // ================================================================================================================
    // CONSTRUCTOR ----------------------------------------------------------------------------------------------------

    public ZGallery(Context context) {
        this(context, null);
    }

    public ZGallery(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.galleryStyle);
    }

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

    // ================================================================================================================
    // EVENT INTERFACE ------------------------------------------------------------------------------------------------

    @Override
    public boolean onInterceptTouchEvent(MotionEvent __e) {
        // Intercepts all touch screen motion events.  This allows you to watch events as they are dispatched to your children, and take ownership of the current gesture at any point.
        // Return true to steal motion events from the children and have them dispatched to this ViewGroup through onTouchEvent().
        // The current target will receive an ACTION_CANCEL event, and no further messages will be delivered here.

        //return super.onInterceptTouchEvent(__e); // super always returns false

        // If this function returns TRUE, NO children get dragging events. This only happens
        // the first interception (mouse down); if true is returned, nothing is intercepted anymore, and
        // events are passed to onTouchEvent directly.
        // If FALSE is returned, this may be called again, but only if there's a children receiving the
        // events instead of this.
        // In sum, once onTouchEvent is called here, onInterceptTouchEvent is not called anymore.

        // Interprets drag data
        return evaluateTouchEvent(__e);

    }

    @Override
    public boolean onTouchEvent(MotionEvent __e) {
        // Interprets drag data
        evaluateTouchEvent(__e);

        // Properly lets superclass interpret touch events (for dragging, fling, etc)
        return super.onTouchEvent(__e);
    }

    protected boolean evaluateTouchEvent(MotionEvent __e) {
        // Interprets motion to see if the user is dragging the View
        // This will run in parallel with the children events
        float dragDeltaX;
        float dragDeltaY;

        switch (__e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // Pressing...
                isPressed = true;
                startPressX = __e.getX();
                startPressY = __e.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                // Moving...
                if (isPressed && !isDragging) {
                    dragDeltaX = __e.getX() - startPressX;
                    dragDeltaY = __e.getY() - startPressY;

                    if (Math.abs(dragDeltaX) > DRAG_THRESHOLD || Math.abs(dragDeltaY) > DRAG_THRESHOLD) {
                        // Moved too far, means it's dragging!

                        // Inject click from correct position so superclass code knows where to drag from
                        MotionEvent me = MotionEvent.obtain(__e);
                        me.setAction(MotionEvent.ACTION_DOWN);
                        me.setLocation(__e.getX() - dragDeltaX, __e.getY() - dragDeltaY);
                        super.onTouchEvent(me);

                        isDragging = true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                // Releasing...
                if (isPressed) {
                    isPressed = false;
                    // Let go while pressed
                    if (isDragging) {
                        // Was dragging, so just go back
                        isDragging = false;
                    } else {
                        // Was not dragging, this will trigger a click
                    }
                }
                break;
        }


        // If not dragging, event should be passed on
        // If dragging, the event should be intercepted and interpreted by this gallery's onTouchEvent instead
        return isDragging;
    }
}

It seems to work well. Hopefully it'll be helpful to someone else!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文