简体   繁体   English

放大视图并调用滚动时,Scrollview闪烁

[英]Scrollview flashing when inflating view and calling scrollBy

I am developing an Android application where an activity displays content in a scrollview. 我正在开发一个Android应用程序,其中活动在滚动视图中显示内容。 At the top of the content there is a placeholder for an image to be displayed. 在内容的顶部,有一个占位符,用于显示图像。 The image is downloaded from the Internet and may take a few seconds until it is ready to be displayed. 该图像是从Internet下载的,可能要花几秒钟才能显示出来。 The image placeholder is initially empty. 图像占位符最初为空。 When the image is downloaded, it is dynamically added to the placeholder. 下载图像后,它会动态添加到占位符。

Initially I had the following problem. 最初我遇到以下问题。

  • The user starts the activity and scrolls down 用户开始活动并向下滚动
  • The image starts to download in the background. 图像开始在后台下载。 When available, it is added to the placeholder 可用时,将其添加到占位符
  • When the image is added to the placeholder, the contents of the scrollview change and the user experience is disrupted by the unwanted scrolling that occurs 将图像添加到占位符后,滚动视图的内容会更改,并且用户体验会因发生不必要的滚动而中断

To fix this, I added code to adjust the scroll position once the image view is added to the placeholder. 为了解决这个问题,我在图像视图添加到占位符后添加了代码来调整滚动位置。 The problem with this is that a flickering is caused on the scrollview during the display-image and adjust-scrollview process. 问题是在显示图像和adjust-scrollview过程中在滚动视图上引起闪烁。 The reason is that the scrollBy function is called from a runnable. 原因是从可运行对象调用了scrollBy函数。 Calling scrollBy outside the runnable does not cause flickering but the scroll position is incorrect - the reason for this is that there is not enough time for the items on the scroll view to recalculate/measure their dimensions/heights. 在可运行对象外部调用scrollBy不会引起闪烁,但是滚动位置不正确-原因是滚动视图上的项目没有足够的时间来重新计算/测量其尺寸/高度。

Here is a sample application the illustrates this problem: 这是一个示例应用程序,它说明了此问题:

public class MainActivity extends AppCompatActivity {

    ScrollView scrollView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        scrollView = findViewById(R.id.scrollview);

        startImageDownload();
        simulateImageScroll();
    }

    private void simulateImageScroll() {
        // scroll to the bottom of the scroll view
        scrollView.post(new Runnable() {
            @Override
            public void run() {
                scrollView.scrollTo(0, scrollView.getMaxScrollAmount());
            }
        });
    }

    private void startImageDownload() {
        Handler handler = new Handler(getMainLooper());
        // simulate a delay for the image download to illustrate the flashing problem in the scrollview
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                displayImage("");
            }
        }, 2000);

    }

    // when the image is downloaded we add it to the image container
    private void displayImage(String imageFilename) {
        // dynamically create an image and add it to the image container layout
        RelativeLayout container = findViewById(R.id.imageContainer);
        ImageView img = new ImageView(this);

        // image should be loaded from the given filename - for now use a solid background and fixed height
        img.setBackgroundColor(Color.BLUE);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
                RelativeLayout.LayoutParams.MATCH_PARENT, 500);
        container.addView(img, params);

        adjustScrolling(container);
    }

    private void adjustScrolling(RelativeLayout container) {
        // adjust scroll if the image is loaded before the current content
        if (scrollView.getScrollY() > container.getTop()) {
            container.measure(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
            final int amountToScroll = container.getMeasuredHeight();

            // the following does not cause flickering but scrolls to the wrong position
            //scrollView.scrollBy(0, amountToScroll);

            // adjust the scrollview so that it keeps the current view unchanged
            scrollView.post(new Runnable() {
                @Override
                public void run() {
                    // this causes flickering but scrolls to the correct position
                    scrollView.scrollBy(0, amountToScroll);
                }
            });
        }
    }

}

And here is the layout file: 这是布局文件:

<ScrollView
    android:id="@+id/scrollview"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <RelativeLayout
            android:id="@+id/imageContainer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:gravity="center"
            android:background="#aa0000" >

        </RelativeLayout>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:gravity="center"
            android:background="#aa0000" >
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="1"
                android:textColor="#ffffff"
                android:textSize="128dp"/>
        </RelativeLayout>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:gravity="center"
            android:background="#aa0000" >
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="2"
                android:textColor="#ffffff"
                android:textSize="128dp"/>
        </RelativeLayout>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:gravity="center"
            android:background="#aa0000" >
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="3"
                android:textColor="#ffffff"
                android:textSize="128dp"/>
        </RelativeLayout>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:gravity="center"
            android:background="#aa0000" >
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="4"
                android:textColor="#ffffff"
                android:textSize="128dp"/>
        </RelativeLayout>

    </LinearLayout>
</ScrollView>

Any ideas on how to fix this problem? 关于如何解决此问题的任何想法?

Edited: Currently, your layout is flickering, because adding blue view cause redraw layout (and scroll). 编辑:当前,您的布局闪烁,因为添加蓝色视图会导致重绘布局(和滚动)。 So scroll occurred once, and next you scrolled to the position you want. 因此滚动一次,然后滚动到所需位置。 That's the second moving. 这是第二步。

To solve this problem, you need to know how android draws view. 要解决此问题,您需要了解android如何绘制视图。 https://developer.android.com/guide/topics/ui/how-android-draws.html https://developer.android.com/guide/topics/ui/how-android-draws.html

Simply, onMeasure() - onLayout() - onDraw() . 简单地说, onMeasure() - onLayout() - onDraw() And you can add your layout code between onLayout() and onDraw() , by ViewTreeObserver().addOnGlobalLayoutListener() . 您还可以通过ViewTreeObserver().addOnGlobalLayoutListener()onLayout()onDraw()之间添加布局代码。

https://developer.android.com/reference/android/view/ViewTreeObserver.OnGlobalLayoutListener.html https://developer.android.com/reference/android/view/ViewTreeObserver.OnGlobalLayoutListener.html

ps: I still recommend using nice and lovely image library, Picasso. ps:我仍然建议使用漂亮可爱的图片库Picasso。

Fixed code is: Set scroll before draw() called. 固定的代码是:在调用draw()之前设置滚动。 By this, you can draw only once. 这样,您只能绘制一次。

public class MainActivity extends AppCompatActivity {

    ScrollView scrollView;
    int amountToScroll = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        scrollView = findViewById(R.id.scrollview);
        scrollView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                scrollView.scrollBy(0, amountToScroll);
                amountToScroll = 0;
            }
        });
        startImageDownload();
        simulateImageScroll();
    }

    private void simulateImageScroll() {
        // scroll to the bottom of the scroll view
        scrollView.post(new Runnable() {
            @Override
            public void run() {
                scrollView.scrollTo(0, scrollView.getMaxScrollAmount());
            }
        });
    }

    private void startImageDownload() {
        Handler handler = new Handler(getMainLooper());
        // simulate a delay for the image download to illustrate the flashing problem in the scrollview
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                displayImage("");
            }
        }, 2000);

    }

    // when the image is downloaded we add it to the image container
    private void displayImage(String imageFilename) {
        // dynamically create an image and add it to the image container layout
        RelativeLayout container = findViewById(R.id.imageContainer);
        ImageView img = new ImageView(this);

        // image should be loaded from the given filename - for now use a solid background and fixed height
        img.setBackgroundColor(Color.BLUE);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
                RelativeLayout.LayoutParams.MATCH_PARENT, 500);
        container.addView(img, params);

        adjustScrolling(container);
    }

    private void adjustScrolling(RelativeLayout container) {
        // adjust scroll if the image is loaded before the current content
        if (scrollView.getScrollY() > container.getTop()) {
            container.measure(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
            amountToScroll = container.getMeasuredHeight();
        }
    }
}

I strongly recommend using Picasso. 我强烈建议您使用毕加索。 http://square.github.io/picasso/ http://square.github.io/picasso/

This one line will fix all of your problem. 这一行将解决您的所有问题。

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

You can load your local image file or network image (url) into your imageView. 您可以将本地图像文件或网络图像(url)加载到imageView中。


In your case, remove both startImageDownload() and simulateImageScroll() , and on onResume() , call displayImage() . 在你的情况下,删除这两个startImageDownload()simulateImageScroll()并在onResume()调用displayImage()

Fixed displayImage(): 修复了displayImage():

private void displayImage(String imageFilename) {
    // dynamically create an image and add it to the image container layout
    RelativeLayout container = findViewById(R.id.imageContainer);
    ImageView img = new ImageView(this);

    // image should be loaded from the given filename - for now use a solid background and fixed height
    img.setBackgroundColor(Color.BLUE);
    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
            RelativeLayout.LayoutParams.MATCH_PARENT, 500);
    container.addView(img, params);
    Picasso.with(this).load(imageFilename).into(img);

    adjustScrolling(container);
}


Or, if you want to solve this problem directly for academic reasons, 或者,如果您出于学术原因直接解决此问题,

  1. Do not adjust your scroll. 不要调整滚动。 It seems that it is not a real solution to use scrollBy to fix your problem. 似乎使用scrollBy解决您的问题不是真正的解决方案。 The real cause is the code that cause the UI to redraw. 真正的原因是导致UI重绘的代码。 May be calling invalidate() or something like that. 可能正在调用invalidate()东西。

  2. Adding ImageView programmatically is not a good idea. 以编程方式添加ImageView并不是一个好主意。 Because your RecyclerView or ViewHolder of ListView cannot reuse the view, so it cause degrade performance. 因为您的ListView的RecyclerView或ViewHolder无法重用该视图,所以会导致性能下降。 If you can avoid it, do that. 如果可以避免,请执行此操作。 (eg. use xml) (例如,使用xml)

  3. It seems that adding your ImageView to imageContainer is real problem. 看来将ImageView添加到imageContainer是真正的问题。 imageContainer has android:layout_height="wrap_content" property, and this means it has no fixed height, it depends on it's own child. imageContainer具有android:layout_height="wrap_content"属性,这意味着它没有固定的高度,这取决于它自己的孩子。 Try to change to fixed value, for example: android:layout_height="500dp" 尝试更改为固定值,例如: android:layout_height="500dp"

Well first if it's a single image on top then you don't have to create imageview dynamically just use it inside your XML file without Relative-layout. 好吧,首先,如果它是顶部的单个图像,那么您不必动态创建imageview,只需在XML文件中使用它而无需使用相对布局即可。 set to an default image. 设置为默认图像。 Use Image-View with adjustViewBounds="true" and scaleType="fitCenter" then you don't have to worry about the image scaling. 使用带有AdjustViewBounds =“ true”和scaleType =“ fitCenter”的Image-View,则不必担心图像缩放。

<ImageView
    android:id="@id/img"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:adjustViewBounds="true"
    android:scaleType="fitCenter" />

you can use Picasso http://square.github.io/picasso/ library as suggested by "Stanley Kou" for loading the image. 您可以使用“ Stanley Kou”建议的Picasso http://square.github.io/picasso/库加载图像。

My Suggestion is to use Progress Bar, Start the Progress bar when image starts downloading and hide it once the image load is complete then let the user see the activity. 我的建议是使用进度条,在开始下载图像时启动进度条,并在图像加载完成后将其隐藏,然后让用户查看活动。

  <ProgressBar
  android:id="@+id/indeterminateBar"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"/>

For more details, please check - 有关更多详细信息,请检查-

https://developer.android.com/reference/android/widget/ProgressBar.html https://developer.android.com/reference/android/widget/ProgressBar.html

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM