two directional scroll view

2019-01-17 17:26发布

I would like to have a linearlayout with a header section on top and a webview below. The header will be short and the webview may be longer and wider than the screen.

What is the best way to get horizontal and vertical scrolling? Is a ScrollView nested inside a HorizontalScrollView a good idea?

9条回答
The star\"
2楼-- · 2019-01-17 17:44

Is a ScrollView nested inside a HorizontalScrollView a good idea?

Yes, and no.

Yes, my understanding is that ScrollView and HorizontalScrollView can be nested.

No, AFAIK, neither ScrollView nor HorizontalScrollView work with WebView.

I suggest that you have your WebView fit on the screen.

查看更多
倾城 Initia
3楼-- · 2019-01-17 17:45

For a while I've been trying solutions from here, but the one that worked best still had one problem: It ate all events, none were making it through to elements within the scroller.

So I've got ... yet another answer, in Github and well-commented at least hopefully: https://github.com/Wilm0r/giggity/blob/master/app/src/main/java/net/gaast/giggity/NestedScroller.java

Like all solutions, it's a nested HorizontalScrollview (outer) + ScrollView (inner), with the outer receiving touch events from Android, and the inner receiving them only internally from the outer view.

Yet I'm relying on the ScrollViews to decide whether a touch event is interesting and until they accept it, do nothing so touches (i.e. taps to open links/etc) can still make it to child elements.

(Also the view supports pinch to zoom which I needed.)

In the outer scroller:

@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 here is the "InnerScroller", subclassed from ScrollView, with a few changes to event handling: I've done some terrible things to ensure incoming touch events directly from Android are discarded, and instead it will only take them from the outer class - and only then pass those on to the superclass:

    @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);
    }
查看更多
趁早两清
4楼-- · 2019-01-17 17:48

I've try both wasikuss and user1684030 solutions and I had to adapt them because of one warning log: HorizontalScrollView: Invalid pointerId=-1 in onTouchEvent, and because I wasn't fan of this need of creating 2 scroll views.

So here is my class:

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

}

And when using it in XML, you have nothing to do if the content of this scroll view is set in here. Otherwise, you just need to call the method setContent(View content) in order to let this ScrollView2D knows what is its content.

For instance:

// 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);
查看更多
叼着烟拽天下
5楼-- · 2019-01-17 17:54

Two years further down the line I think the open source community might have to your rescue: 2D Scroll View.

Edit: The Link doesn't work anymore but here is a link to an old version of the blogpost;

查看更多
做自己的国王
6楼-- · 2019-01-17 17:54

There is an easy workaround: In you activity get a reference to the outer scrollView (I'm going to assume a vertical scrollview) and a reference to the first child of that scroll view.

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

One could argue that this solution is a bit hacky. But it has worked great for me in several applications!

查看更多
Viruses.
7楼-- · 2019-01-17 17:54

Late to answer, but hopefully might be helpful to someone. You can check out droid-uiscrollview. This is heavily based on @MrCeeJ's answer, but I seemed to have a lot of trouble getting the actual content to be rendered. Hence I pulled in the latest source from HorizontalScrollView & ScrollView to create droid-uiscrollview. There are a few todo's left which I haven't gotten around to finish, but it does suffice to get content to scroll both horizontally & vertically at the same time enter image description here

查看更多
登录 后发表回答