HorizontalScrollView:当添加新视图时自动滚动到末尾?

60

我有一个包含LinearLayout的HorizontalScrollView。屏幕上有一个按钮,会在运行时向LinearLayout添加新视图,并且当添加新视图时,我希望滚动视图滚动到列表的末端。

我已经接近成功了——除了它总是滚动到倒数第二个视图。似乎在计算新视图的包含之前就已经开始滚动了。

在我的应用程序中,我使用了自定义的View对象,但我制作了一个使用ImageView并具有相同症状的小测试应用程序。我尝试过各种方法,例如在布局和ScrollView上请求requestLayout(),我尝试了scrollTo(Integer.MAX_VALUE),但它滚动到了虚无之地:) 我是否违反了UI线程问题或其他问题?

  • Rick

======

    public class Main extends Activity {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);

             Button b = (Button) findViewById(R.id.addButton);
             b.setOnClickListener(new AddListener());

             add();
        }

        private void add() {
             LinearLayout l = (LinearLayout) findViewById(R.id.list);
             HorizontalScrollView s = 
                 (HorizontalScrollView) findViewById(R.id.scroller);

             ImageView i = new ImageView(getApplicationContext());
             i.setImageResource(android.R.drawable.btn_star_big_on);
             l.addView(i);

             s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
        }

        private class AddListener implements View.OnClickListener {
             @Override
             public void onClick(View v) {
                 add();
             }
        }
    }

布局XML:

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

        <HorizontalScrollView
            android:id="@+id/scroller"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scrollbarSize="50px">
            <LinearLayout
                android:id="@+id/list"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="4px"/>
        </HorizontalScrollView>

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"            
            android:layout_gravity="center">
            <Button
                android:id="@+id/addButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:paddingLeft="80px"
                android:paddingRight="80px"
                android:paddingTop="40px"
                android:paddingBottom="40px"
                android:text="Add"/>
        </LinearLayout>

    </LinearLayout>
10个回答

146

我认为这是一个时间问题。当视图添加时,布局尚未完成。它在稍后请求并完成。当您立即在添加视图后调用fullScroll时,LinearLayout的宽度还没有扩展的机会。

尝试替换:

s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);

使用:

s.postDelayed(new Runnable() {
    public void run() {
        s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
    }
}, 100L);

短暂的延迟应该给系统足够的时间来稳定。

另外,可能只需要将滚动延迟到当前UI循环的下一次迭代即可。我尚未测试这个理论,但如果正确,只需执行以下操作即可:

s.post(new Runnable() {
    public void run() {
        s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
    }
});

我更喜欢这个方法,因为对我来说引入任意延迟似乎有点糟糕(尽管这是我的建议)。


5
太准确了,Ted!非常感谢,这个问题已经困扰我好几天了! :) - Rick Barkhouse
嗨Ted...当我尝试使用上面的代码时,它显示错误无法解析方法'postDelayed(java.lang.runnable,long)'... 请帮帮我... - Vinit ...
@VinitVikash - 这是View类中的一个方法。你可能需要执行s.postDelayed(...)。或者,创建一个Handler并使用它的postDelayed()方法。 - Ted Hopp
如果您不知道如何使用postDelayed(),可以尝试使用new Handler().postDelayed() - Pratik Butani
2
@PratikButani - 这样做是可行的,但更有效率的方法是将Handler分配为一个字段并使用它,而不是每次都动态创建一个。然而,由于我们已经有了视图引用s,最好只使用s.postDelayed(...),这样可以很好地避免需要自己创建Handler的需求。 - Ted Hopp

20

我同意@monxalo和@granko87的看法,更好的方法是使用侦听器而不是假设布局将在任意时间间隔后完成。

如果像我一样不需要使用自定义HorizontalScrollView子类,则可以添加OnLayoutChangeListener以保持简单:

mTagsScroller.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        mTagsScroller.removeOnLayoutChangeListener(this);
        mTagsScroller.fullScroll(View.FOCUS_RIGHT);
    }
});

这也是我想出来的解决方案。代码更少,完成了工作。 - Paul Woitaschek
这绝对是该问题的正确答案。随意等待时间是毫无意义的。 等待布局引擎完成其工作才是正确的做法。 - DrAL3X

12

只是一个建议,因为这个问题帮了我很多 :).

你可以在视图完成其布局阶段时放置监听器,然后立即执行fullScroll,尽管你需要扩展该类。

我之所以这样做,是因为我想在onCreate()之后立即滚动到某个部分,以避免从起始点到滚动点的闪烁。

类似于:

public class PagerView extends HorizontalScrollView {

    private OnLayoutListener mListener;
    ///...
    private interface OnLayoutListener {
        void onLayout();
    }

    public void fullScrollOnLayout(final int direction) {
        mListener = new OnLayoutListener() {            
            @Override
            public void onLayout() {
                 fullScroll(direction)
                 mListener = null;
            }
        };
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if(mListener != null)
             mListener.onLayout();
    }
}

4

使用View方法smoothScrollTo

postDelayed(new Runnable() {
    public void run() {
       scrollView.smoothScrollTo(newView.getLeft(), newView.getTop());
 }
 }, 100L);

您需要放置一个可运行的代码,以便为布局过程留出时间来定位新视图。

无法正常工作。View#getTop()返回相对于父级的位置,因此在更深层次的布局中将无法正常工作。 - Vouskopes

3

这是我所做的。

//Observe for a layout change    
ViewTreeObserver viewTreeObserver = yourLayout.getViewTreeObserver();
   if (viewTreeObserver.isAlive()) {
     viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                yourHorizontalScrollView.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
            }
     });
   }

3

使用以下代码实现水平滚动视图 {向右滚动}

view.post(new Runnable() {
                @Override
                public void run() {
                    ((HorizontalScrollView) Iview
                            .findViewById(R.id.hr_scroll))
                            .fullScroll(HorizontalScrollView.FOCUS_RIGHT);
                }
            });

2

我认为monxalo发布的方法是最好的,因为你无法知道布局完成需要多长时间。我尝试了它,效果非常完美。唯一的区别是,我只添加了一行到我的自定义水平滚动视图:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    this.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
}

我认为时间的数量并不重要。问题在于滚动不应该发生在当前 UI 线程循环的迭代中。一旦当前迭代完成,视图层次结构应该是最新的并且已经布局好了。 - Ted Hopp

0

View可能无法立即添加到ViewGroup中。因此,需要发布一个Runnable,该Runnable将在其他挂起的任务完成后运行。

因此,在添加View之后,请使用:

horizontalScrollView.post(new Runnable() {
    @Override
    public void run() {
        horizontalScrollView.fullScroll(View.FOCUS_RIGHT);
    }
});

0
mainView.horizontalScrollImgs.postDelayed(Runnable { kotlin.run { mainView.horizontalScrollImgs.fullScroll(HorizontalScrollView.FOCUS_RIGHT) } },500)

Stack Overflow建议不要仅发布代码。请提供您的代码的解释和原因。此外,格式化您的代码将使其更易于阅读。 - Hamish
当然,我会做的。 - praveensmedia

0

这是我在 Kotlin 中使用 3 个 ImageView 的方法:

var p1 = true; var p2 = false; var p3 = false
val x = -55
val handler = Handler()
handler.postDelayed(object : Runnable {
    override fun run() {

        if (p1){
            mainView.picsScrollViewID.smoothScrollTo(mainView.bigPic1ID.x.toInt() + x, 0)
            p1 = false
            p2 = true
            p3 = false
        }
        else if(p2){
            mainView.picsScrollViewID.smoothScrollTo(mainView.bigPic2ID.x.toInt() + x, 0)
            p1 = false
            p2 = false
            p3 = true
        }
        else if(p3){
            mainView.picsScrollViewID.smoothScrollTo(mainView.bigPic3ID.x.toInt() + x, 0)
            p1 = true
            p2 = false
            p3 = false
        }
        handler.postDelayed(this, 2000)
    }
}, 1400)

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