如何优化要在GridView中使用的自定义ImageView?

时间:2012-10-15 14:28:03

标签: android optimization android-imageview custom-component android-gridview

我开发了这个自定义ImageView类来覆盖一些默认行为以满足我的需求。让我来描述一下这个自定义ImageView的作用......

我们假设您在GridViewdrawable-mdpi文件夹中的drawable-hdpi中都有一堆图标,分别为48x48px和72x72px。 drawable-xhdpi文件夹中没有可用的图标。 GridView属性使得所有图标大小都是48x48dp(对于mpdi,hdpi和xhdpi密度,这将分别转换为48px,72px和96px)。

由于drawable-xhdpi文件夹中没有图标,因此在具有此类密度的设备上运行此应用时,图标将从drawable-hdpi文件夹中提取。 由于它们只有72像素而且xhdpi设备需要96像素的图像,因此图标将被拉伸以填充剩余的像素。

这是我的自定义ImageView尝试覆盖的行为。使用我的自定义组件,会发生的是图像根本不会被拉伸。例如,在上面使用我的类的示例中,ImageView内的每个GridView仍然是96x96px(因为定义了48x48dp大小)但是使用的图像来自drawable-hdpi文件夹。是72x72px。 将会发生drawable-hdpi文件夹中的这些图片将放置在ImageView的中心,其大小为96x96像素,而不会拉伸图像以适合整个视图大小。

如果上述情况令人困惑,请尝试使用几张照片。以下示例不使用GridView,我试图简化自定义类背后的想法。这些是我在这个例子中使用的源图片:

source-files

这是 HDPI 设备的结果:

hdpi-demo

这是 XHDPI 设备上的结果:

xhdpi-demo

上面屏幕截图的布局代码如下:

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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Standard ImageView:"
        android:textAppearance="?android:attr/textAppearanceLarge"/>

    <ImageView
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_margin="10dp"
        android:scaleType="center"
        android:background="#FFEEEE"
        android:src="@drawable/ic_female"/>
    <ImageView
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_margin="10dp"
        android:scaleType="center"
        android:background="#FFEEEE"
        android:src="@drawable/ic_male"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Custom UnscaledImageView:"
        android:textAppearance="?android:attr/textAppearanceLarge"/>

    <com.sampleapp.widget.UnscaledImageView
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_margin="10dp"
        android:scaleType="center"
        android:background="#FFEEEE"
        android:src="@drawable/ic_female"/>
    <com.sampleapp.widget.UnscaledImageView
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_margin="10dp"
        android:scaleType="center"
        android:background="#FFEEEE"
        android:src="@drawable/ic_male"/>

</LinearLayout>

现在更清楚了吗?这就是我想要做的事情,除了一个小的性能问题之外,它还能很好地工作......现在让我发布我用于这种自定义组件的代码:

attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="UnscaledImageView">
        <attr name="android:src" />
    </declare-styleable>

</resources>

UnscaledImageView.java:

public class UnscaledImageView extends ImageView {

    private int mDeviceDensityDpi;

    public UnscaledImageView(Context context) {
        super(context);

        mDeviceDensityDpi = getResources().getDisplayMetrics().densityDpi;
    }

    public UnscaledImageView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mDeviceDensityDpi = getResources().getDisplayMetrics().densityDpi;

        TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.UnscaledImageView);
        int resourceId = styledAttrs.getResourceId(R.styleable.UnscaledImageView_android_src, 0);

        if(resourceId != 0) {
            setUnscaledImageResource(resourceId);
        }

        styledAttrs.recycle();
    }

    public void setUnscaledImageResource(int resId) {
        setImageBitmap(decodeBitmapResource(resId));
    }

    @SuppressWarnings("deprecation")
    public void setUnscaledBackgroundResource(int resId) {
        BitmapDrawable drawable = new BitmapDrawable(null, decodeBitmapResource(resId));
        drawable.setTargetDensity(mDeviceDensityDpi);
        drawable.setGravity(Gravity.CENTER);

        setBackgroundDrawable(drawable);
    }

    private Bitmap decodeBitmapResource(int resId) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inDensity = mDeviceDensityDpi;

        return BitmapFactory.decodeResource(getResources(), resId, options);
    }

}

因此,如果UnscaledImageView视图用于XML布局或直接在代码中初始化,则此类将执行此操作。我还提供了2种方法,因此可以在代码中更改图像,同时防止图像被拉伸。正如您所看到的,这些方法只占用资源ID,到目前为止我还没有感觉到需要直接使用drawables或bitmaps。

现在我遇到的真正问题是......

如果在某些布局中将此类用作单个图像视图,没问题,它只能解码一个图像。但是,如果它在GridView中使用,其中可以有40个图标(我从我的xhdpi设备上运行的应用程序中实际发生的事情中获取此值)同时可见,滚动GridView会非常慢,因为decodeBitmapResource()正在为每张图片调用BitmapFactory.decodeResource()

这是我的问题,这是我的问题。 如何优化此功能?如果可能的话......

2 个答案:

答案 0 :(得分:1)

将这些图像放入res/drawable-nodpi/可以做你想要的(我说要并排放置不同的分辨率图像)。

这可能有点棘手,因为您可能必须遵循命名约定才能找到您尝试绘制的给定图像的最佳资源。可能这需要您尝试按名称查找图像,这不是检索资源的有效方法。

我想象的方式是:在布局(或其他任何地方)上,指定要使用的图像资源的名称(字符串,而不是id!)。

在该nodpi文件夹中,您的图像后缀为预期的屏幕密度。

然后,在setter方法中,您必须尝试不同的组合才能找到最佳可用资源。

你要思考的问题:如果缩小图像怎么办?资源将比您绘制它的视图更大!

答案 1 :(得分:0)

虽然 Pedro Loureiro 的答案可能是一个可能的解决方案,但我决定在意识到某事后采取不同的方法......

我已经在ImageView中加载了原始UnscaledImageView加载和我的GridView加载,当然非科学地我已经意识到我的班级会加载所有图片比原生的快一点。也许本机方法除了解码资源之外还有别的东西(他们仍然必须这样做,对吗?)而我的课只是解码一个资源(使用BitmapFactory),基本上就是这样。

我认为我的班级在滚动时让GridView有点慢,但经过几次测试后,使用原始ImageView没有任何调整,也显示有点不稳定而滚动GrivView

我发现解决此问题的解决方案(使用我的类或本机问题)是在GridView中缓存图标,为此我使用了LruCache这是推荐的方法。在GridView中缓存图像。

这就是我将用来解决我的问题的解决方案。有关详细信息,请参阅官方培训指南:http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

作为参考,我还发现以下教程很有用:http://andrewbrobinson.com/2012/03/05/image-caching-in-android/