`
yhz61010
  • 浏览: 561487 次
  • 来自: -
博客专栏
63c13ecc-ef01-31cf-984e-de461c7dfde8
libgdx 游戏开发
浏览量:12251
社区版块
存档分类
最新评论

[原创] 连载 5 - 深入讨论 Android 关于高效显示图片的问题 - 如何在 UI 中显示位图

阅读更多
  更加详细的说明,可以参阅如下官网地址:http://developer.android.com/training/building-graphics.html

  快速导航
  1. 如何高效的加载大位图。(如何解码大位图,避免超过每个应用允许使用的最大内存)http://yhz61010.iteye.com/blog/1848337
  2. 如何在非 UI 线程处理位图。(如何使用 AsyncTask 在后台线程处理位图及处理并发问题)http://yhz61010.iteye.com/blog/1848811
  3. 如何对位图进行缓存。(如何通过创建内存缓存和磁盘缓存来流畅的显示多张位图)http://yhz61010.iteye.com/blog/1849645
  4. 如何管理位图内存。(如何针对不同的 Android 版本管理位图内存)http://yhz61010.iteye.com/blog/1850232
  5. 如何在 UI 中显示位图。(如何通过 ViewPager 和 GridView 显示多张图片)http://yhz61010.iteye.com/blog/1852927

  如何在 UI 中显示位图?
  本文将引导你将前四篇的文章结合在一起,为你演示如何使用 ViewPager 和 GridView 组件在后台线程组合缓存加载多张位图,并且还将为你演示如何处理并发及配置改变的问题。

  实现向 ViewPager 中添加位图
  在浏览大量图片时,若允许用户滑动屏幕就可以切换图片的话,是非常好的导航模式 http://developer.android.com/design/patterns/swipe-views.html。你可以使用 ViewPager 组件的 PagerAdapter 来实现这种导航方式。但若使用它的子类 FragmentStatePagerAdapter 可能更加合适,因为当图片不再被显示的时候,该类会自动清理并保存 ViewPager 中的 Fragments 的状态,以节约内存的使用量。

  注意,若你只想展示少量的图片,并且可以保证这些图片所占的内存不会超过应用程序的内存限制的话,那么你可以使用普通的 PagerAdapter 或 FragmentPagerAdapter 即可。

  现在将为你展示如何使用 ViewPager 显示多个 ImageView。在主 activity 中声明了 ViewPager 和 adapter:
public class ImageDetailActivity extends FragmentActivity {
    public static final String EXTRA_IMAGE = "extra_image";

    private ImagePagerAdapter mAdapter;
    private ViewPager mPager;

    // A static dataset to back the ViewPager adapter
    public final static Integer[] imageResIds = new Integer[] {
            R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,
            R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,
            R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image_detail_pager); // Contains just a ViewPager

        mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), imageResIds.length);
        mPager = (ViewPager) findViewById(R.id.pager);
        mPager.setAdapter(mAdapter);
    }

    public static class ImagePagerAdapter extends FragmentStatePagerAdapter {
        private final int mSize;

        public ImagePagerAdapter(FragmentManager fm, int size) {
            super(fm);
            mSize = size;
        }

        @Override
        public int getCount() {
            return mSize;
        }

        @Override
        public Fragment getItem(int position) {
            return ImageDetailFragment.newInstance(position);
        }
    }
}

  下面的代码为你详细的展现了持有 ImageView 的 Fragment 的实现。下面的代码看似是个完美的解决办法,但是你看出其中的不足了吗?应该如何改进呢?
public class ImageDetailFragment extends Fragment {
    private static final String IMAGE_DATA_EXTRA = "resId";
    private int mImageNum;
    private ImageView mImageView;

    static ImageDetailFragment newInstance(int imageNum) {
        final ImageDetailFragment f = new ImageDetailFragment();
        final Bundle args = new Bundle();
        args.putInt(IMAGE_DATA_EXTRA, imageNum);
        f.setArguments(args);
        return f;
    }

    // Empty constructor, required as per Fragment docs
    public ImageDetailFragment() {}

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : -1;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // image_detail_fragment.xml contains just an ImageView
        final View v = inflater.inflate(R.layout.image_detail_fragment, container, false);
        mImageView = (ImageView) v.findViewById(R.id.imageView);
        return v;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        final int resId = ImageDetailActivity.imageResIds[mImageNum];
        mImageView.setImageResource(resId); // Load image into ImageView
    }
}

  你可能注意到问题的所在,图片是在 UI 线程中被加载的,这可能导致你的程序没有响应或被强制关闭。使用前文 http://yhz61010.iteye.com/blog/1848811 提到的 AsyncTask 来解决这个问题,将加载图片的处理放到后台线程来处理:
public class ImageDetailActivity extends FragmentActivity {
    ...

    public void loadBitmap(int resId, ImageView imageView) {
        mImageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }

    ... // include BitmapWorkerTask class
}

public class ImageDetailFragment extends Fragment {
    ...

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (ImageDetailActivity.class.isInstance(getActivity())) {
            final int resId = ImageDetailActivity.imageResIds[mImageNum];
            // Call out to ImageDetailActivity to load the bitmap in a background thread
            ((ImageDetailActivity) getActivity()).loadBitmap(resId, mImageView);
        }
    }
}

  将任何耗时的处理(例如,获取来自网络中的图片或调整其大小)放到 BitmapWorkerTask 中,这样就不会给主 UI 线程带来任何响应的问题。后台线程的工作不仅仅是从磁盘中直接加载图片,而且还应该包括对图片进行缓存处理,这一点在 http://yhz61010.iteye.com/admin/blogs/1849645 一文中已经有了深入的讨论。下面,我们对代码做进一步的修改,以支持对内存缓存的处理:
public class ImageDetailActivity extends FragmentActivity {
    ...
    private LruCache<String, Bitmap> mMemoryCache;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        // initialize LruCache as per Use a Memory Cache section
    }

    public void loadBitmap(int resId, ImageView imageView) {
        final String imageKey = String.valueOf(resId);

        final Bitmap bitmap = mMemoryCache.get(imageKey);
        if (bitmap != null) {
            mImageView.setImageBitmap(bitmap);
        } else {
            mImageView.setImageResource(R.drawable.image_placeholder);
            BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
            task.execute(resId);
        }
    }

    ... // include updated BitmapWorkerTask from Use a Memory Cache section
}

  将上述代码结合在一起,你就得到了一个响应及时的 ViewPager 实现。在该实现中,你可以以最小的延迟在后台对图片进行各种各样的处理。

  实现向 GridView 中添加位图
  Grid List http://developer.android.com/design/building-blocks/grid-lists.html 特别适用于显示图片集。通过 GridView 组件可以实现一次性在屏幕中显示多张图片,并且可以提前为用户准备好上下滑动屏幕时需要显示的图片。若要实现这种类型的操作,我们必须保证用户使用的流畅性,并且内存的使用量是可控的,而且还要保证并发操作的正确性(因为 GridView 会重用它的子视图)。

  在开始了解具体的实现之前,我们先来看一个在 Fragment 中,使用带有 ImageView 的标准的 GridView 实现代码。然后再来分析下,这看似完美的实现方式背后还有没有更好的实现方式?
public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
    private ImageAdapter mAdapter;

    // A static dataset to back the GridView adapter
    public final static Integer[] imageResIds = new Integer[] {
            R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,
            R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,
            R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};

    // Empty constructor as per Fragment docs
    public ImageGridFragment() {}

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAdapter = new ImageAdapter(getActivity());
    }

    @Override
    public View onCreateView(
            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final View v = inflater.inflate(R.layout.image_grid_fragment, container, false);
        final GridView mGridView = (GridView) v.findViewById(R.id.gridView);
        mGridView.setAdapter(mAdapter);
        mGridView.setOnItemClickListener(this);
        return v;
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
        final Intent i = new Intent(getActivity(), ImageDetailActivity.class);
        i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position);
        startActivity(i);
    }

    private class ImageAdapter extends BaseAdapter {
        private final Context mContext;

        public ImageAdapter(Context context) {
            super();
            mContext = context;
        }

        @Override
        public int getCount() {
            return imageResIds.length;
        }

        @Override
        public Object getItem(int position) {
            return imageResIds[position];
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup container) {
            ImageView imageView;
            if (convertView == null) { // if it's not recycled, initialize some attributes
                imageView = new ImageView(mContext);
                imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                imageView.setLayoutParams(new GridView.LayoutParams(
                        LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            } else {
                imageView = (ImageView) convertView;
            }
            imageView.setImageResource(imageResIds[position]); // Load image into ImageView
            return imageView;
        }
    }
}

  上述代码的问题和之前的例子一样,对图片的处理依然放在了 UI 线程。对于展示小的图片来说,这么做可能不会产生任何问题,但是如果我们还需要对显示的图片进行额外的处理话,上面的代码很可能会导致你的程序崩溃。

  虽然这次我们依然可以使用刚才提到的异步处理及缓存处理,但是依然要谨慎的做好并发处理,因为 GridView 会重用它的子视图。为了处理这个问题,我们可以使用在 http://yhz61010.iteye.com/blog/1848811 一文中,“并发处理”章节中使用的技术。下面是对上述代码的改进版:
public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
    ...

    private class ImageAdapter extends BaseAdapter {
        ...

        @Override
        public View getView(int position, View convertView, ViewGroup container) {
            ...
            loadBitmap(imageResIds[position], imageView)
            return imageView;
        }
    }

    public void loadBitmap(int resId, ImageView imageView) {
        if (cancelPotentialWork(resId, imageView)) {
            final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
            final AsyncDrawable asyncDrawable =
                    new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
            imageView.setImageDrawable(asyncDrawable);
            task.execute(resId);
        }
    }

    static class AsyncDrawable extends BitmapDrawable {
        private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

        public AsyncDrawable(Resources res, Bitmap bitmap,
                BitmapWorkerTask bitmapWorkerTask) {
            super(res, bitmap);
            bitmapWorkerTaskReference =
                new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
        }

        public BitmapWorkerTask getBitmapWorkerTask() {
            return bitmapWorkerTaskReference.get();
        }
    }

    public static boolean cancelPotentialWork(int data, ImageView imageView) {
        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

        if (bitmapWorkerTask != null) {
            final int bitmapData = bitmapWorkerTask.data;
            if (bitmapData != data) {
                // Cancel previous task
                bitmapWorkerTask.cancel(true);
            } else {
                // The same work is already in progress
                return false;
            }
        }
        // No task associated with the ImageView, or an existing task was cancelled
        return true;
    }

    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
       if (imageView != null) {
           final Drawable drawable = imageView.getDrawable();
           if (drawable instanceof AsyncDrawable) {
               final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
               return asyncDrawable.getBitmapWorkerTask();
           }
        }
        return null;
    }

    ... // include updated BitmapWorkerTask class

  注意,将上述代码做适当修改还可以应用在 ListView 中。

  上述代码实现中,让我们可以灵活的加载并处理图片,而且还不影响用户的使用体验。在后台的任务中,你可以从网络中加载图片,并调整高清图片的大小,在任务结束后再展现处理后的图片。
4
0
分享到:
评论

相关推荐

    android UI界面开发图片

    开发者应遵循“密度独立像素”(DP,Density-independent Pixel)的原则,确保图片在各种屏幕尺寸下显示一致。通常,需要为每个密度提供相应尺寸的图片,避免因拉伸导致的模糊。 在UI设计中,图标的设计是一大重点...

    Android---UI篇

    •Android---UI篇---Tab Layout(选项卡布局) • •Andorid---UI篇---TableLayout(表格布局) • •Android---UI篇---RelativeLayout(相对布局) • •Android---UI篇---GridView(网格布局) • •Android---UI篇-...

    Android从网络加载图片并显示在ImageView控件上

    在Android中,最常用的是使用HttpURLConnection或者第三方库如OkHttp。HttpURLConnection是Android自带的API,可以用来发送HTTP请求。OkHttp则提供了更高效的网络通信,支持连接池和自动重试机制。在这个场景下,...

    android从指定文件夹中显示图片

    在Android平台上,从指定文件夹显示图片涉及到一系列的步骤和技术,包括文件系统操作、图片加载库的使用以及UI设计。以下是对这个主题的详细讲解: 首先,我们需要理解Android的文件系统结构。Android设备通常有两...

    Android 大位图压缩方法二

    在Android开发中,大位图(Bitmap)的处理是一个重要的技术点,特别是在处理高分辨率图像或者需要显示大量图片的应用中。由于Android系统对内存管理的限制,直接加载大位图可能导致应用崩溃,这就是著名的"Out of ...

    uiautomatorviewer 工具,可支持android 9.0

    - "eclipse":虽然Eclipse曾经是Android开发的主要IDE,但现在更常见的是使用Android Studio,不过uiautomatorviewer可以在两者中运行,为开发者提供了灵活性。 **压缩包子文件的文件名称解析** - "ddmlib.jar":...

    Android UI设计

    Android UI设计内容简介: 创造一个统一外观,感觉完整的用户界面会增加你的产品附加价值。 精炼的图形风格也使用户觉得用户界面更加专业。 帮助你如何在应用界面的不同部分创造图标来匹配 Android 2.x框架下的普遍...

    OpenCV-2.4.10-android-sdk (不包含apk)

    OpenCV(开源计算机视觉库)是计算机视觉和机器学习领域的一个强大工具,广泛应用于图像处理、...总之,OpenCV是一个强大的工具,通过深入理解和熟练运用,可以在Android平台上实现各种复杂且高效的计算机视觉应用。

    Android图片裁剪----移动、缩放图片进行裁剪

    本知识点将详细介绍如何在Android中实现图片的移动和缩放裁剪。 首先,我们需要了解Android中处理图片的基础知识。Android提供了多种库和API来处理图像,例如Bitmap类用于表示位图图像,Matrix类则用于处理图像的...

    Android UI开发专题

    `android.content.res.Resources`类在Android开发中扮演着重要的角色,它负责管理应用程序中的各种资源,包括但不限于二进制资产文件(如raw文件夹中的资源)、颜色定义(colors.xml)、图形文件(如drawable文件夹...

    fmx-android-imagelist获取图片

    接下来,我们要讨论如何在Android应用程序中从ImageList中获取并显示图片。通常,这涉及到以下几个步骤: 1. **添加图片到ImageList**:在运行时,可以使用`AddImage`方法动态加载图像。例如,如果有一个`ImageList...

    android多张图片展示

    在Android开发中,实现“多张图片展示”的功能是一个常见的需求,尤其在社交应用中,如微信的朋友圈功能。这个功能允许用户通过多种途径选择图片,例如从相册选取多张图片或者直接拍照,之后还能提供删除选项。下面...

    Android获取手机所有图片并显示

    考虑到Android设备的屏幕多样性,列表项的布局设计需考虑不同分辨率和屏幕比例,以保证图片在各种设备上显示正常。 11. **图片压缩**: 如果图片过大,可以使用第三方库如Luboo或Google的Glide库的压缩功能,减小...

    Android读取sdcard上的图片并用Gallery显示

    本文将深入探讨如何从SDCard读取图片并将其显示在Gallery组件中,以及实现点击Gallery中的图片后全屏显示的逻辑。首先,我们需要了解Android的文件系统和权限管理。 1. Android文件系统: Android系统的外部存储...

    Android studio中图片异步加载并缓存Android-Universal-Image-Loader

    这个库提供了丰富的功能,可以方便地在Android Studio项目中集成,使得图片的异步加载和缓存变得简单高效。 首先,我们要理解为什么需要图片异步加载。在Android设备上,如果直接在主线程中加载图片,尤其是大图或...

    android-ui-utils,从code.google.com/p/android-ui-utils自动导出.zip

    总的来说,“android-ui-utils”是Android开发者的重要参考资料,它将帮助我们解决UI设计中的常见问题,同时激发创新,推动我们构建出更优秀、更具有竞争力的应用。无论是初学者还是经验丰富的开发者,都值得花时间...

    android原生systemUI源码4.2.2

    在Android系统中,SystemUI是用户界面的重要组成部分,它包含了状态栏、通知栏、快速设置面板等关键元素的实现。Android 4.2.2版本的SystemUI源码为我们提供了深入理解Android UI系统工作原理的宝贵资料。接下来,...

    原生android SystemUI源码

    2. **NotificationPanel**: 通知中心是Android中展示应用程序通知的地方。在这里,用户可以查看并处理来自不同应用的通知。源码中包含了对通知的排序、显示、清除等功能的实现。 3. **QuickSettings**: 快速设置...

    Android进阶篇之RoundProgress(圆形进度条)使用两张图片叠加实现

    本篇文章将深入探讨如何在Android中实现一个名为RoundProgress的圆形进度条,并通过两张图片的叠加来达到独特的视觉效果。这个圆形进度条不仅能够显示进度,还能通过巧妙的设计增加界面的美观度。 首先,我们需要...

    Android获取图片的RGB值

    在Android中,我们通常通过以下方式加载图片: 1. 使用` BitmapFactory `类的` decodeResource() `或` decodeStream() `方法从资源文件或输入流中解码图片,得到Bitmap对象。 2. 或者使用第三方库如Glide、Picasso...

Global site tag (gtag.js) - Google Analytics