在UI上显示Bitmap

这一课会演示如何运用前面几节课的内容,使用后台线程与Cache机制来加载图片到 ViewPager 与 GridView 组件,并且学习处理并发与配置改变问题。

实现加载图片到ViewPager(Load Bitmaps into a ViewPager Implementation)

swipe view pattern是一个用来切换显示不同详情界面的很好的方法。(关于这种效果请先参看Android Design: Swipe Views).

你可以通过 PagerAdapter 与 ViewPager 组件来实现这个效果。 然而,一个更加合适的Adapter是PagerAdapter 的子类 FragmentStatePagerAdapter:它可以在某个ViewPager中的子视图切换出屏幕时自动销毁与保存 Fragments 的状态。这样能够保持消耗更少的内存。

Note: 如果你只有为数不多的图片并且确保不会超出程序内存限制,那么使用 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);

}

}

}

Fragment 里面包含了ImageView 的子组件:

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 Thread中读取图片可能会导致程序ANR。使用在Lesson 2中学习的 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 中做一些例如resizing or fetching images from the network,不会卡到UI Thread。如果后台线程不仅仅是做个简单的直接加载动作,增加一个内存Cache或者磁盘Cache会比较好[参考Lesson 3] ,下面是一些为了内存Cache而附加的内容:

public class ImageDetailActivity extends FragmentActivity {

...

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

}

实现加载图片到GridView(Load Bitmaps into a GridView Implementation)

Grid list building block 是一种有效显示大量图片的方式。这样能够一次显示许多图片,而且那些即将被显示的图片也处于准备显示状态。如果你想要实现这种效果,你必须确保UI是流畅的,能够控制内存使用,并且正确的处理并发问题(因为 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线程中被设置。当处理小的图片时可以,但其他需要额外操作的处理,都会使你的UI慢下来。

与前面加载到图片到ViewPager一样,如果setImageResource的操作会比较耗时,有可能会卡到UI Thread。可以使用类似前面异步处理图片与增加缓存的方法来解决那个问题。然而,我们还需要考虑GridView的循环机制所带来的并发问题。为了处理这个问题,请参考前面的课程 。下面是一个更新的解决方案:

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

public AsyncDrawable(Resources res, Bitmap bitmap,

BitmapWorkerTask bitmapWorkerTask) {

super(res, bitmap);

bitmapWorkerTaskReference =

new WeakReference(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

Note:对于 ListView 同样可以套用上面的方法。

上面的方法提供了足够的弹性,使得你可以做从网络加载与Resize大的数码照片等操作而不至于卡到UI Thread。

时间: 2024-08-06 07:49:03

在UI上显示Bitmap的相关文章

Flash Stage3D 在2D UI 界面上显示3D模型问题完美解决

一直以来很多Stage3D开发者都在为3D模型在2DUI上显示的问题头疼.Stage3D一直是在 Stage2D下面.为了做到3D模型在2DUI上显示通常大家有几种实现方式,下面来说说这几种实现方式吧. 实现方式1: 在2DUI上挖个洞透过去显示3D层.这种做法的缺陷在于如果两个UI界面同时打开就会UI错层显示错乱.为了解决这问题很多程序员选择了当挖洞显示3D的UI打时把其他界面隐藏掉,用户体验超差. 实现方式2: 利用Context3D 的 drawToBitmapData API 实时将3D

高效显示Bitmap

这一章节会介绍一些处理与加载Bitmap对象的常用方法,这些技术能够使得程序的UI不会被阻塞,并且可以避免程序超出内存限制.如果我们不注意这些,Bitmaps会迅速的消耗掉可用内存从而导致程序崩溃,出现下面的异常:java.lang.OutofMemoryError: bitmap size exceeds VM budget. 在Android应用中加载Bitmaps的操作是需要特别小心处理的,有下面几个方面的原因: 移动设备的系统资源有限.Android设备对于单个程序至少需要16MB的内存

Android手势识别 Camera 预览界面上显示文字 布局注意事项(merge布局)

通常在Surfaceview作为预览视频帧的载体,有时需在上面显示提示文字.以前我弄的都好好的,今天忽然发现叠加的TextView不管咋弄都出不来文字了,跟Surfaceview一起放在FrameLayout也不行,后来想到merge布局,发现也不行.大爷的,奇了怪了,最后发现了原因,原来是顺序问题.也即无论是在RelativeLayout里还是merge布局里,View是逐个叠加上去的,一层一层铺上去的.如果你先放TextView在最前面,那肯定被后面的全屏Surfaceview覆盖了.用常规

unity区分点击在3D物体还是2D UI上

当场景中的3D物体需要响应点击,但同时有UI显示时,存在判断点击是在3D物体上还是UI上的问题,办法如下: 1. 射线检测所有2D 3D物体,有2D物体被检测到时表明当前有UI.但无论Physics2D.Raycast()还是Physics.Raycast()都只能检测到含有Collider组件的物体,普通UI如Image Button等一般用射线是不起作用的.EventSystem.current.RaycastAll()可以将当前屏幕上的所有可检测的物体全部检测到,该方法需要自己构造一个Po

在小米 三星 索尼 手机 :图标上显示数字

在小米 三星  索尼 手机 :图标上显示数字(未读消息数):这部分代码,是从QQ5.0.apk中找的. 小米已经测试通过了, 三星和索尼的,由于没有相应的手机,没有测试,有的,可能修改一下代码(判断是什么手机的代码), 测试一下,可以在回复一下测试结果,谢谢 1.原生系统(原生的Launcher ),只能修改快捷方式,增加和删除都会有toast提示 2.小米 三星  索尼 手机: 自定义的launcher:  发送显示未读消息数的action已经不同了.具体可以去看代码... 判断手机的代码:

在地图上显示当前的位置

1 package com.aihunqin.crazy; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.view.View; 6 import android.view.View.OnClickListener; 7 import android.widget.TextView; 8 9 import com.baidu.location.BDLocation; 10 import c

8位灰度图在LCD上显示

一.概述 1.灰度 灰度使用黑色调表示物体,即用黑色为基准色,不同的饱和度的黑色来显示图像.每个灰度对象都具有从 0%(白色)到灰度条100%(黑色)的亮度值. 使用黑白或灰度扫描仪生成的图像通常以灰度显示. 像素值量化后用一个字节(8 bits)来表示.如把有黑-灰-白连续变化的灰度值量化为256个灰度级,灰度值的范围为0~255,表示亮度从深到浅,对应图像中的颜色为从黑到白.黑白照片包含了黑白之间的所有的灰度色调,每个像素值都是介于黑色和白色之间的256种灰度中的一种. 2.灰度图bmp文件

7.2.2 在窗体上显示绘图

绘图与第四章的示例类似.因为绘图需要一定的时间,我们将在内存中创建位图,绘制好文档,然后,在窗体上显示位图,而不是每次窗体失效时都绘制文档.我们先看一下非常有用的函数式编程模式,这一节就将使用. "Hole in the Middle(中间有洞)"模式 [真心不知道,Hole in the Middle 是什么意思?] 写代码的一个常见情形,是首先进行初始化,然后,是函数的核心部分,最后,是一些清理工作:在程序中的多个位置,重复执行类似的操作,初始化和清理部分没有变化,而只有核心部分不

UGUI 特效怎样在UI上裁剪

刚好碰到有人问怎样把粒子特效放到 UI 上并且能正确被 Mask 裁剪, 首先想到给粒子效果的 Shader 添加模板模仿一般 UI 的模板方式, 应该就能正确裁剪了吧, 不过没那么简单, 我们看到在一般 UI 上, 没在 Mask 下的 UI 对比在 Mask 下的 UI, 它们的材质设置是不一样的, 也就是说有哪个组件对材质进行了修改, 查了一下就是 Mask 组件, 它会收集子节点下的所有对象, 然后对可以修改的材质进行修改, 所以才能自动控制裁剪. 为了搞清楚, 这里做了一下测试: 1.