膨胀视图和调用 scrollBy 时滚动视图闪烁

Scrollview flashing when inflating view and calling scrollBy

我正在开发一个 Android 应用程序,其中 activity 在滚动视图中显示内容。在内容的顶部有一个用于显示图像的占位符。该图像是从 Internet 下载的,可能需要几秒钟才能显示出来。图像占位符最初是空的。下载图像时,它会动态添加到占位符中。

最初我遇到了以下问题。

为解决此问题,我添加了代码以在将图像视图添加到占位符后调整滚动位置。这样做的问题是在显示图像和调整滚动视图过程中,滚动视图会出现闪烁。原因是 scrollBy 函数是从可运行对象中调用的。在 runnable 外部调用 scrollBy 不会导致闪烁但滚动位置不正确 - 原因是滚动视图上的项目没有足够的时间 recalculate/measure 它们的 dimensions/heights.

这是一个示例应用程序,它说明了这个问题:

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

}

这是布局文件:

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

关于如何解决这个问题有什么想法吗?

已编辑: 目前,您的布局在闪烁,因为添加蓝色视图会导致重绘布局(和滚动)。所以滚动发生一次,接下来你滚动到你想要的位置。这是第二次搬家。

要解决这个问题,您需要了解android如何绘制视图。 https://developer.android.com/guide/topics/ui/how-android-draws.html

简单地说,onMeasure() - onLayout() - onDraw()。您可以通过 ViewTreeObserver().addOnGlobalLayoutListener().

onLayout()onDraw() 之间添加布局代码

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

ps:我还是推荐使用漂亮可爱的图片库,毕加索

固定代码是:在 draw() 调用之前设置滚动。这样,你只能画一次。

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

我强烈推荐使用 Picasso。 http://square.github.io/picasso/

这一行将解决您所有的问题。

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

您可以将本地图像文件或网络图像 (url) 加载到您的 imageView 中。


在您的情况下,删除 startImageDownload()simulateImageScroll(),并在 onResume() 上调用 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);
}


或者,如果出于学术原因想直接解决这个问题,

  1. 不要调整滚动条。使用 scrollBy 来解决您的问题似乎不是真正的解决方案。真正的原因是导致 UI 重绘的代码。可能正在调用 invalidate() 或类似的东西。

  2. 以编程方式添加 ImageView 不是一个好主意。因为您的 RecyclerView 或 ListView 的 ViewHolder 无法重用视图,所以会导致性能下降。如果可以避免,就这样做。 (例如,使用 xml)

  3. 看来把你的ImageView加入imageContainer确实是个问题。 imageContainer有android:layout_height="wrap_content"属性,这意味着它没有固定的高度,它取决于它自己的child。尝试改为固定值,例如:android:layout_height="500dp"

首先,如果它是顶部的单个图像,那么您不必动态创建图像视图,只需在没有相对布局的 XML 文件中使用它即可。设置为默认图像。使用带有 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" />

您可以按照 "Stanley Kou" 的建议使用 Picasso http://square.github.io/picasso/ 库来加载图像。

我的建议是使用进度条,当图像开始下载时启动进度条,并在图像加载完成后隐藏它,然后让用户看到 activity。

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

更多详情请查看-

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