双向滚动视图

12

我希望拥有一个线性布局,顶部有一个标题栏,下面是一个网页视图。标题栏很短,网页视图可能比屏幕更长和更宽。

如何最好地实现水平和垂直滚动?在HorizontalScrollView中嵌套一个ScrollView是否是一个好主意?

9个回答

9
一个ScrollView嵌套在HorizontalScrollView里是个好主意吗?
是的,也不是。
是的,据我了解,ScrollView和HorizontalScrollView可以嵌套。
不是的,据我所知,无论是ScrollView还是HorizontalScrollView都不能与WebView一起使用。
建议您让WebView适应屏幕大小。

1
我可以确定地说,ScrollView和HorizontalScrollView是可以嵌套的,因为我已经这样做了,但用户体验很差,因为很难使其在你想要的方向上滚动,并且没有对角线解释 - 你只能得到全部垂直或全部水平移动。 - Blumer
把您的 WebView 调整至屏幕大小。很不幸,我没有任何方法可以做到那样 :( - 700 Software
@Blumer 我注意到了。这对我来说是可以接受的,但不是最优的选择。 - 700 Software
@cjavapro:所以呢?WebView内部滚动,就像普通的Web浏览器一样。 - CommonsWare
@CommonsWare 请帮帮我 https://stackoverflow.com/questions/47952573/scrollview-not-working-when-rotate-the-layout-dynamically?noredirect=1#comment82874486_47952573 - kartheeki j
显示剩余4条评论

8

还有一种方法。将修改过的HorizontalScrollView作为ScrollView的包装器。当捕获触摸事件时,正常的HorizontalScrollView不会将其转发到ScrollView,因此您只能单向滚动。这里是解决方案:

package your.package;

import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.view.MotionEvent;
import android.content.Context;
import android.util.AttributeSet;

public class WScrollView extends HorizontalScrollView
{
    public ScrollView sv;
    public WScrollView(Context context)
    {
        super(context);
    }

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

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

    @Override public boolean onTouchEvent(MotionEvent event)
    {
        boolean ret = super.onTouchEvent(event);
        ret = ret | sv.onTouchEvent(event);
        return ret;
  }

    @Override public boolean onInterceptTouchEvent(MotionEvent event)
    {
        boolean ret = super.onInterceptTouchEvent(event);
        ret = ret | sv.onInterceptTouchEvent(event);
        return ret;
    }
}

使用:

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

/*BIDIRECTIONAL SCROLLVIEW*/
        ScrollView sv = new ScrollView(this);
        WScrollView hsv = new WScrollView(this);
        hsv.sv = sv;
/*END OF BIDIRECTIONAL SCROLLVIEW*/

        RelativeLayout rl = new RelativeLayout(this);
        rl.setBackgroundColor(0xFF0000FF);
        sv.addView(rl, new LayoutParams(500, 500));
        hsv.addView(sv, new LayoutParams(WRAP_CONTENT, MATCH_PARENT /*or FILL_PARENT if API < 8*/));
        setContentView(hsv);
    }

我可以确认这个可行!在我的版本中,我有一个包含嵌套X和Y滚动的视图。我重写了View的onTouchEventonInterceptTouchEvent方法,并将MotionEvent对象转发到两个滚动视图,就像这个答案中所描述的那样。流畅的体验,双向滚动。 - sulai
1
为了使其正常工作,我不得不调整onTouchEvent以始终调用sv.onTouchEvent,并将返回值修改为true。还要注意,您必须扩展外部视图。在我的情况下,我有一个水平滚动视图嵌套在(垂直)滚动视图中,因此我必须扩展垂直滚动视图。 - jeroent
你能提供 XML 吗?如何使用它? - Selman Tosun

8

14
三年过去了,链接失效了。 - jiduvah
1
谢谢!很高兴看到有人关心 :) - MrCeeJ
2
链接又挂了。我创建了一个库项目,其中包含这个类,并将其推送到Maven供其他人使用:https://github.com/jaredrummler/TwoDScrollView - Jared Rummler
上面链接的TwoDScrollView类可能会出现问题。例如,当scrollview的父级是FrameLayout且某些尺寸设置为常量值[非wrap/match]时,在此维度中ScrollView的大小将等于其[scrollview的]子项的完整大小,这反过来将使availableToScroll变量等于0。 - Android developer

3

我花了很长时间搜索才使这个工作,并最终在这里找到了这个帖子。wasikuss的答案接近解决方案,但仍然不能正常工作。以下是它如何非常好地工作(至少对我来说(Android 2.3.7))。我希望它也适用于任何其他Android版本。

创建一个名为VScrollView的类:

package your.package.name;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;


public class VScrollView extends ScrollView {
    public HorizontalScrollView sv;

    public VScrollView(Context context) {
        super(context);
    }

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

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        sv.dispatchTouchEvent(event);
        return true;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        super.onInterceptTouchEvent(event);
        sv.onInterceptTouchEvent(event);
        return true;
    }
}

你的布局应该如下所示:
<your.package.name.VScrollView
    android:id="@+id/scrollVertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <HorizontalScrollView
        android:id="@+id/scrollHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <TableLayout
            android:id="@+id/table"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:clickable="false"
            android:stretchColumns="*" >
        </TableLayout>
    </HorizontalScrollView>
</your.package.name.VScrollView>

在您的活动中,您应该做类似以下的事情:
hScroll = (HorizontalScrollView) findViewById(R.id.scrollHorizontal);
vScroll = (VScrollView) findViewById(R.id.scrollVertical);
vScroll.sv = hScroll;

...这就是它的工作原理。至少对我来说是这样。


3
这个支持对角线滚动吗? - 700 Software

3

晚些回答,但希望对某人有帮助。 您可以查看droid-uiscrollview。这在很大程度上基于@MrCeeJ的答案,但我似乎遇到了许多问题,无法使实际内容呈现出来。因此,我从HorizontalScrollViewScrollView中提取了最新的源代码,创建了droid-uiscrollview。还有一些未完成的todo's,但它确实足以同时水平和垂直滚动内容。enter image description here

2

以下是简单的解决方法: 在你的活动中获取外部scrollView的引用(我假设是垂直scrollView),以及该scrollView的第一个子项的引用。

Scrollview scrollY = (ScrollView)findViewById(R.id.scrollY);
LinearLayout scrollYChild = (LinearLayout)findViewById(R.id.scrollYChild);

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    scrollYChild.dispatchTouchEvent(event);
    scrollY.onTouchEvent(event);
    return true;
}

有人可能会认为这种解决方案有点“hacky”。但是在我多个应用程序中,它都非常有效!

1
有一段时间我一直在尝试这里的解决方案,但最好的一个仍然存在一个问题:它会吞噬所有事件,没有一个事件能够到达滚动区域内的元素。
所以我又得到了一个答案,在Github上也有很好的注释,希望能够解决这个问题:https://github.com/Wilm0r/giggity/blob/master/app/src/main/java/net/gaast/giggity/NestedScroller.java 像所有的解决方案一样,它是一个嵌套的HorizontalScrollview(外部)+ ScrollView(内部),外部从Android接收触摸事件,内部只从外部视图内部接收触摸事件。
然而,我依靠ScrollView来决定一个触摸事件是否有趣,并且在它们接受之前不做任何事情,所以触摸(例如点击打开链接等)仍然可以传递给子元素。
(此外,该视图支持我需要的捏合缩放。)
在外部滚动器中:
@Override
public boolean onInterceptTouchEvent(MotionEvent event)
{
    if (super.onInterceptTouchEvent(event) || vscroll.onInterceptTouchEventInt(event)) {
        onTouchEvent(event);
        return true;
    }
    return false;
}

@Override
public boolean onTouchEvent(MotionEvent event)
{
    super.onTouchEvent(event);
    /* Beware: One ugliness of passing on events like this is that normally a ScrollView will
       do transformation of the event coordinates which we're not doing here, mostly because
       things work well enough without doing that.
       For events that we pass through to the child view, transformation *will* happen (because
       we're completely ignoring those and let the (H)ScrollView do the transformation for us).
     */
    vscroll.onTouchEventInt(event);
    return true;
}

这里的“vscroll”是从ScrollView继承而来的“InnerScroller”,在事件处理方面进行了一些更改:我做了一些可怕的事情,以确保直接来自Android的触摸事件被丢弃,并且它只会从外部类中接收它们 - 然后仅将其传递给超类:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        /* All touch events should come in via the outer horizontal scroller (using the Int
           functions below). If Android tries to send them here directly, reject. */
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        /* It will still try to send them anyway if it can't find any interested child elements.
           Reject it harder (but pretend that we took it). */
        return true;
    }

    public boolean onInterceptTouchEventInt(MotionEvent event) {
        return super.onInterceptTouchEvent(event);
    }

    public boolean onTouchEventInt(MotionEvent event) {
        super.onTouchEvent(event);
    }

请将您的Github中相关的代码添加到您的答案中。 - Antti Haapala -- Слава Україні

1
我会尽力帮忙翻译中文。这段内容与编程有关,讲述了作者尝试了两种解决方案wasikussuser1684030,但由于出现了一个警告日志HorizontalScrollView: Invalid pointerId=-1 in onTouchEvent,以及不喜欢创建两个滚动视图的需求,因此作者进行了修改。下面是作者的类代码:
public class ScrollView2D extends ScrollView {

    private HorizontalScrollView innerScrollView;

    public ScrollView2D(Context context) {
        super(context);

        addInnerScrollView(context);
    }

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


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

        if (getChildCount() == 1) {
            View subView = getChildAt(0);
            removeViewAt(0);
            addInnerScrollView(getContext());
            this.innerScrollView.addView(subView);
        } else {
            addInnerScrollView(getContext());
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean handled = super.onTouchEvent(event);
        handled |= this.innerScrollView.dispatchTouchEvent(event);
        return handled;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        super.onInterceptTouchEvent(event);
        return true;
    }


    public void setContent(View content) {
        if (content != null) {
            this.innerScrollView.addView(content);
        }
    }


    private void addInnerScrollView(Context context) {
        this.innerScrollView = new HorizontalScrollView(context);
        this.innerScrollView.setHorizontalScrollBarEnabled(false);
        addView(this.innerScrollView);
    }

}

在XML中使用它时,如果这个滚动视图的内容在此处设置,则无需进行任何操作。否则,您只需要调用方法setContent(View content),以便让此ScrollView2D知道它的内容是什么。
例如:
// Get or create a ScrollView2D.
ScrollView2D scrollView2D = new ScrollView2D(getContext());
scrollView2D.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
addView(scrollView2D);

// Set the content of scrollView2D.
RelativeLayout testView = new RelativeLayout(getContext());
testView.setBackgroundColor(0xff0000ff);
testView.setLayoutParams(new ViewGroup.LayoutParams(2000, 2000));
scrollView2D.setContent(testView);

-3

我知道你已经接受了你的答案,但也许这可以给你一些想法。

<?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"
>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>

<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<HorizontalScrollView
    android:layout_alignParentBottom="true"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
>
<ImageView

android:src="@drawable/device_wall"
android:scaleType="center"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</HorizontalScrollView>
</RelativeLayout>
</LinearLayout>
</ScrollView>

看起来像是“ScrollView嵌套在HorizontalScrollView内”的相反情况。我有什么遗漏的吗? - 700 Software
是啊,LinearLayout和RelativeLayout在这个解决方案中到底是做什么的?除非它们有某种未解释的目的,否则应该将它们移除以保持视图层次结构尽可能平坦。 - SilithCrowe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接