【Android Developers Training】 60. 在你的UI

系统 1601 0

注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。

原文链接: http://developer.android.com/training/displaying-bitmaps/display-bitmap.html


这节课程将结合之前所有课程所学习的知识,向你展示如何使用后台线程和位图缓存,在 ViewPager GridView 中展示多幅图片,并解决并发和配置变更的问题。


一). 实现向一个ViewPager加载位图

该滑动视图模式( swipe view pattern )用来作为图库的阅览室再好不过的了。你可以通过 PagerAdapter 来支持 ViewPager 实现这个阅览模式。然而, 一个更合适的支持适配器是其子类 FragmentStatePagerAdapter ,它自动销毁 ViewPager 中的 Fragments ,并保存其状态(当它从屏幕消失时),从而保持内存使用不会太高。

Note:

如果你有更小数量的图片,并确信他们能满足应用的内存限制,那么使用平常的 PagerAdapter FragmentPagerAdapter 会更合适一些。

下面是一个具有 ImageView ViewPager 实现。主activity持有 ViewPager 和它的适配器:

      
        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线程上进行读取的,因此可能会导致应用停止响应从而崩溃。使用一个在 Processing Bitmaps Off the UI Thread (博客链接: http://www.cnblogs.com/jdneo/p/3521195.html 中说的 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://www.cnblogs.com/jdneo/p/3522538.html )。下面是内存缓存的修改代码:

      
        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 building block 对于展示图片数据集是很有用的,并且可以通过一个 GridView 组件来实现。 GridView 组件允许同一时间在屏幕上显示许多图片,同时还有很多备用图片(当用户上下滑动页面时,备用图片将会被显示)。当实现这个控制类型时,你必须保证UI的流畅性,内存使用可控,并发处理正确(根据 GridView 回收它的子View)。

我们首先来看一下一个标准的 GridView 实现,它有一个置于 Fragment 中的子 ImageView 。还是像之前一样,请读者思考这一看上去完美的实现是否有进一步提升的空间?

      
        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会戛然而止。

之前章节的异步处理和缓存的方法可以在这里实现。然而,你也需要警惕并发的问题,因为 GridView 会回收它的子View。要处理这个问题,使用在 Processing Bitmaps Off the UI Thread 课程(博客链接: http://www.cnblogs.com/jdneo/p/3521195.html )中所讲的知识。下面是升级后的解决方案:

      
        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
      
    

Note:

同样的代码也可以应用于 ListView

这样的实现对于如何处理和加载图片保留了足够的弹性,从而不会引起UI的卡顿。在后台任务中,你可以从网络上读取图片或者将大尺寸的数码照片进行缩放,在任务完成后,图像会显示出来。

要看这系列课程的完整代码样例,请下载 http://www.cnblogs.com/jdneo/p/3512517.html 中的样例代码

【Android Developers Training】 60. 在你的UI中显示位图


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论