【发布时间】:2010-10-05 18:18:07
【问题描述】:
我想要一个线性布局,顶部有一个标题部分,下面有一个 web 视图。标题会很短,webview 可能会比屏幕更长更宽。
获得水平和垂直滚动的最佳方式是什么? ScrollView 嵌套在 HorizontalScrollView 中是个好主意吗?
【问题讨论】:
标签: android scroll android-layout
我想要一个线性布局,顶部有一个标题部分,下面有一个 web 视图。标题会很短,webview 可能会比屏幕更长更宽。
获得水平和垂直滚动的最佳方式是什么? ScrollView 嵌套在 HorizontalScrollView 中是个好主意吗?
【问题讨论】:
标签: android scroll android-layout
ScrollView 嵌套在 HorizontalScrollView 中是个好主意吗?
是的,也不是。
是的,我的理解是ScrollView和HorizontalScrollView可以嵌套。
不,AFAIK,ScrollView 和 HorizontalScrollView 都不能与 WebView 一起使用。
我建议您将WebView 放在屏幕上。
【讨论】:
还有另一种方式。将 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);
}
【讨论】:
onTouchEvent 和 onInterceptTouchEvent 并将 MotionEvent 对象转发到两个滚动视图,正如本答案中所述。流畅的体验,双向。
再过两年,我认为开源社区可能需要拯救你: 2D Scroll View.
编辑:链接不再起作用,但 here is a link to an old version of the blogpost;
【讨论】:
availableToScroll变量等于 0。
我搜索了很久才完成这项工作,终于在这里找到了这个线程。 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;
... 这就是它的工作原理。至少对我来说。
【讨论】:
有一个简单的解决方法: 在您的活动中,获取对外部滚动视图的引用(我将假设为垂直滚动视图)和对该滚动视图的第一个子视图的引用。
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;
}
有人可能会说这个解决方案有点老套。但它在几个应用程序中对我来说效果很好!
【讨论】:
回答迟了,但希望对某人有所帮助。
您可以查看droid-uiscrollview。这在很大程度上基于@MrCeeJ's 的答案,但我似乎在获取要呈现的实际内容时遇到了很多麻烦。因此,我从HorizontalScrollView 和ScrollView 中提取了最新的源代码来创建droid-uiscrollview。剩下几个todo's 我还没来得及完成,但它足以让内容同时水平和垂直滚动
【讨论】:
我尝试了wasikuss 和user1684030 两种解决方案,但由于一个警告日志: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);
【讨论】:
一段时间以来,我一直在尝试这里的解决方案,但效果最好的解决方案仍然存在一个问题:它吞噬了所有事件,没有一个能够通过滚动条中的元素。
所以我得到了……另一个答案,在 Github 中,至少希望得到很好的评论:https://github.com/Wilm0r/giggity/blob/master/app/src/main/java/net/gaast/giggity/NestedScroller.java
与所有解决方案一样,它是一个嵌套的 HorizontalScrollview(外部)+ 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);
}
【讨论】:
我知道你已经接受了你的回答,但这可能会给你一些想法。
<?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>
【讨论】: