【问题标题】:two directional scroll view两个方向滚动视图
【发布时间】:2010-10-05 18:18:07
【问题描述】:

我想要一个线性布局,顶部有一个标题部分,下面有一个 web 视图。标题会很短,webview 可能会比屏幕更长更宽。

获得水平和垂直滚动的最佳方式是什么? ScrollView 嵌套在 Horizo​​ntalScrollView 中是个好主意吗?

【问题讨论】:

    标签: android scroll android-layout


    【解决方案1】:

    ScrollView 嵌套在 Horizo​​ntalScrollView 中是个好主意吗?

    是的,也不是。

    是的,我的理解是ScrollViewHorizontalScrollView可以嵌套。

    不,AFAIK,ScrollViewHorizontalScrollView 都不能与 WebView 一起使用。

    我建议您将WebView 放在屏幕上。

    【讨论】:

    • 我可以肯定地说 ScrollView 和 Horizo​​ntalScrollView 可以嵌套,因为我已经这样做了,但是用户体验很差,因为很难让它向你想要的方向滚动,并且没有对角线的解释——你要么得到所有的垂直运动,要么得到所有的水平运动。
    • "让您的 WebView 适合屏幕。"不幸的是我没有办法做到这一点:(
    • @Blumer 我注意到了。这对我来说是可以接受的,但不是最佳的。
    • @GeorgeBailey:“很遗憾我没有办法做到这一点”——是什么阻止了你?
    • 这些页面是用户生成的,如果它包含一长串破折号或图像,它就会太宽,如果它有太多内容,它就会太长。
    【解决方案2】:

    还有另一种方式。将 Horizo​​ntalScrollView 修改为 ScrollView 的包装器。正常的 Horizo​​ntalScrollView 捕捉触摸事件时不会将它们转发到 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 滚动的视图。我覆盖了视图的 onTouchEventonInterceptTouchEvent 并将 MotionEvent 对象转发到两个滚动视图,正如本答案中所述。流畅的体验,双向。
    • 为了让它工作,我不得不调整 onTouchEvent 以始终​​调用 sv.onTouchEvent,并将返回值修改为 true。另请注意,您必须扩展外部视图。就我而言,我有一个水平滚动视图嵌套在(垂直)滚动视图中,所以我必须扩展垂直滚动视图。
    • 可以提供xml吗?这个怎么用?
    【解决方案3】:

    再过两年,我认为开源社区可能需要拯救你: 2D Scroll View.

    编辑:链接不再起作用,但 here is a link to an old version of the blogpost;

    【讨论】:

    • 又过了 3 年,链接消失了
    • 谢谢!很高兴看到人们关心:)
    • 链接又死了。我用这个类创建了一个库项目并将其推送到 maven 供其他人使用:github.com/jaredrummler/TwoDScrollView
    • 上面链接的 TwoDScrollView 类可能会出现问题。例如,当滚动视图的父级是 FrameLayout 时,其某些维度设置为常量值 [不换行/匹配],滚动视图在此维度中的大小将等于其 [滚动视图] 子级的完整大小,这反过来将使 availableToScroll变量等于 0。
    【解决方案4】:

    我搜索了很久才完成这项工作,终于在这里找到了这个线程。 wasikuss' answer 非常接近解决方案,但仍然无法正常工作。以下是它运作良好的方式(至少对我而言(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;
    

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

    【讨论】:

    • 这个支持对角滚动吗?
    【解决方案5】:

    有一个简单的解决方法: 在您的活动中,获取对外部滚动视图的引用(我将假设为垂直滚动视图)和对该滚动视图的第一个子视图的引用。

    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;
    }
    

    有人可能会说这个解决方案有点老套。但它在几个应用程序中对我来说效果很好!

    【讨论】:

      【解决方案6】:

      回答迟了,但希望对某人有所帮助。 您可以查看droid-uiscrollview。这在很大程度上基于@MrCeeJ's 的答案,但我似乎在获取要呈现的实际内容时遇到了很多麻烦。因此,我从HorizontalScrollViewScrollView 中提取了最新的源代码来创建droid-uiscrollview。剩下几个todo's 我还没来得及完成,但它足以让内容同时水平和垂直滚动

      【讨论】:

        【解决方案7】:

        我尝试了wasikussuser1684030 两种解决方案,但由于一个警告日志:HorizontalScrollView: Invalid pointerId=-1 in onTouchEvent,并且我不喜欢创建 2 个滚动视图的需求,因此我不得不对其进行调整。

        这是我的课:

        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);
        

        【讨论】:

          【解决方案8】:

          一段时间以来,我一直在尝试这里的解决方案,但效果最好的解决方案仍然存在一个问题:它吞噬了所有事件,没有一个能够通过滚动条中的元素。

          所以我得到了……另一个答案,在 Github 中,至少希望得到很好的评论:https://github.com/Wilm0r/giggity/blob/master/app/src/main/java/net/gaast/giggity/NestedScroller.java

          与所有解决方案一样,它是一个嵌套的 Horizo​​ntalScrollview(外部)+ ScrollView(内部),外部接收来自 Android 的触摸事件,内部仅在内部从外部视图接收它们。

          然而,我依靠 ScrollViews 来决定触摸事件是否有趣,并且在他们接受之前,什么都不做,因此触摸(即点击打开链接/等)仍然可以到达子元素。

          (视图还支持我需要的捏缩放。)

          在外滚动条中:

          @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 是“InnerScroller”,它是 ScrollView 的子类,对事件处理进行了一些更改:我做了一些糟糕的事情来确保直接从 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 中的一些相关代码添加到您的答案中。
          【解决方案9】:

          我知道你已经接受了你的回答,但这可能会给你一些想法。

          <?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 嵌套在 Horizo​​ntalScrollView" 的反面。有什么我想念的吗?
          • 是的,LinearLayout 和 RelativeLayout 在这个解决方案中到底在做什么?除非它们用于某种无法解释的目的,否则应将它们删除以保持视图层次结构尽可能平坦。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-08-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多