1.简介

图片浏览在app中是很常见的,本文使用android的recyclerView ,viewPager, Bitmap,等一系列组件开发的一个浏览器;

实现大图无损加载,大图压缩成小图浏览;

2.开发要点:

使用recycleview显示小图浏览,使用自定义的GridlayoutManager,设置了滑动速度;

    @Override    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {        int a = super.scrollVerticallyBy((int)(speedRatio*dy), recycler, state);//屏蔽之后无滑动效果,证明滑动的效果就是由这个函数实现        if(a == (int)(speedRatio*dy)){            //Log.e("speed",dy+"---"+a);            return dy;        }        return a;    }    /**     * 设置滑动速度因子;     * @param speedRatio     */    public void setSpeedRatio(float speedRatio){        this.speedRatio = speedRatio;    }

上面的dy就是滑动速度实时变化值,通过滑动因子改变它;监听recycleView的onTouchListener,返回false不拦截事件,继续想上层传递;

这里做到了不抬起手的时候滑动速度不变,当抬起手指的时候滑动因为为原来速度的0.3倍;

为什么要设置滑动速度?因为速度太快的话,突然滑到底部的时候,图片加载很慢的话会卡顿;

        recycleview.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View view, MotionEvent motionEvent) {                if(motionEvent.getAction()==MotionEvent.ACTION_UP)                {                    myGridLayoutManager.setSpeedRatio(0.3f);                }else{                    myGridLayoutManager.setSpeedRatio(1f);                }                return false;            }        });



数据:使用相册的mediastota数据库中保存的图片路径; 通过内容解析者去获取,存到一个list中,刷新adapter显示数据;

        Cursor cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null);        while (cursor.moveToNext())        {            int index = cursor                    .getColumnIndexOrThrow(MediaStore.Images.Media.DATA);            String path = cursor.getString(index); // 文件地址            //Log.e("address",path);            list.add(path);        }        pictureAdapter.notifyDataSetChanged();


至于适配器中的优化是recycleView的适配器这里不多讲;直接看代码:

    public class PictureAdapter extends RecyclerView.Adapter    {        @Override        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {            ImageView inflate = (ImageView) LayoutInflater.from(MainActivity.this).inflate(R.layout.item_picture, parent,false);            inflate.setLayoutParams(new RecyclerView.LayoutParams(bianchang,bianchang));            return new MyViewHolder(inflate);        }        @Override        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {            MyViewHolder myViewHolder= (MyViewHolder) holder;            myViewHolder.setData(position);        }        @Override        public int getItemCount() {            return list.size();        }    }    public class MyViewHolder extends RecyclerView.ViewHolder    {        private ImageView imageView;        private String picturePath;        private int position;        public MyViewHolder(View itemView) {            super(itemView);            imageView = itemView.findViewById(R.id.iv);            imageView.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View view) {                    float x = imageView.getX();                    float y = imageView.getY();                    Bundle bundle=new Bundle();                    bundle.putFloat("x",x);                    bundle.putFloat("y",y);                    scroolPosition = position;                    bundle.putInt("position",position);                    bundle.putString("picturepath",picturePath);                    Intent intent = new Intent(MainActivity.this, PictureInfoActivity.class);                    intent.putExtra("positon",bundle);                    startActivity(intent);                    overridePendingTransition(0,0);                }            });        }        public void setData(final int data) {            imageView.setImageBitmap(null);            position = data;            picturePath = list.get(data);            LoadPictureUtils.loadPicture(bianchang,picturePath,imageView);        }    }

上面的代码中,加载图片使用线程池异步加载图片,而且进行缩放成360*360的尺寸;所以这里要进行大量的计算必须放在子线程中;线程中执行的是Runable类对象的任务;所以我们要继承runable类,重写run方法:下面有一个复用bitmap技术,从一个set集合中取出可以复用的bitmap对象,这个set是当bitmap缓存LruCache中移除多余的bitmap放到set中的,使用的是弱引用的bitmap;

当图片加载缩放的bitmap存放到内存缓存中LurCaChe中;LurCaChe是一个内存缓存结合,设置缓存大小,超过缓存则移除之前的,存入新bitmap;

    @Override    public void run() {        if (biancheng == 1) {            loadBigpicture();        } else {            loadscalepicture();        }    }    /**     * 加载缩略图     */    public void loadscalepicture() {        BitmapFactory.Options options = new BitmapFactory.Options();        options.inJustDecodeBounds = true;        Bitmap bitmap = BitmapFactory.decodeFile(path, options);        int outWidth = options.outWidth;        int outHeight = options.outHeight;        int size = 1;        if (outWidth > outHeight) {            size = outWidth / biancheng;        } else {            size = outHeight / biancheng;        }        options.inSampleSize = size;        options.inJustDecodeBounds = false;        options.inPreferredConfig = Bitmap.Config.RGB_565;        options.inMutable = true;//设置成异变得才能复用内存;        Bitmap reusable = BitmapCache.getInstence().getReusable(options);        if (reusable != null) {            Log.e("hah", "复用bitmap了");        }        options.inBitmap = reusable;        bitmap = BitmapFactory.decodeFile(path, options);        //Log.e("tag","--"+size+"--"+bitmap.getWidth()+"--"+bitmap.getHeight());        Message message = new Message();        message.what = 0;        Bundle bundle = new Bundle();        MyData myData = new MyData();        myData.setBitmap(bitmap);        myData.setmImageView(mImageView);        myData.setPath(path);        bundle.putSerializable("bitmap", myData);        message.setData(bundle);        mHandler.sendMessage(message);        BitmapCache.getInstence().putBitmapToCache(path, bitmap);    }


获取可以复用的bitmap对象:

 /**     * 可被复用的Bitmap必须设置inMutable为true;     Android4.4(API 19)之前只有格式为jpg、png,同等宽高(要求苛刻),     inSampleSize为1的Bitmap才可以复用;     Android4.4(API 19)之前被复用的Bitmap的inPreferredConfig     会覆盖待分配内存的Bitmap设置的inPreferredConfig;     Android4.4(API 19)之后被复用的Bitmap的内存     必须大于等于需要申请内存的Bitmap的内存;     */    public synchronized Bitmap getReusable(BitmapFactory.Options options){        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB){            return null;        }        Bitmap reusable = null;        Iterator> iterator = setpool.iterator();        //迭代查找符合复用条件的Bitmap        try{            while (iterator.hasNext()){                Bitmap bitmap = iterator.next().get();                if (null != bitmap){                    //检查是否可以被复用                    if (checkInBitmap(bitmap,options)){                        reusable = bitmap;                        //移出复用池                        iterator.remove();                        break;                    }                }else{                    iterator.remove();                }            }        }catch (Exception e){            e.printStackTrace();        }        return reusable;    }    static boolean checkInBitmap(Bitmap bitmap,BitmapFactory.Options targetOptions){        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {            // From Android 4.4 (KitKat) onward we can re-use if the byte size of            // the new bitmap is smaller than the reusable bitmap candidate            // allocation byte count.            int width = targetOptions.outWidth / targetOptions.inSampleSize;            int height = targetOptions.outHeight / targetOptions.inSampleSize;            int byteCount = width * height * getBytesPerPixel(bitmap.getConfig());            Log.e("haha",byteCount+"----"+bitmap.getAllocationByteCount());            return byteCount <= bitmap.getAllocationByteCount();        }        // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1        return bitmap.getWidth() == targetOptions.outWidth                && bitmap.getHeight() == targetOptions.outHeight                && targetOptions.inSampleSize == 1;    }    static int getBytesPerPixel(Bitmap.Config config){        if (config == Bitmap.Config.ARGB_8888) {            return 4;        } else if (config == Bitmap.Config.RGB_565) {            return 2;        } else if (config == Bitmap.Config.ARGB_4444) {            return 2;        } else if (config == Bitmap.Config.ALPHA_8) {            return 1;        }else{            return 2;        }    }


lrucache对象的实例化:

    public void init(Context context)    {        //创建一个set集合存放bitmap的弱引用;        setpool =Collections.synchronizedSet(new HashSet>());        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);        int memoryClass = am.getMemoryClass();        Log.e("HHHH",memoryClass+"M");        lruCache = new LruCache((memoryClass/5)*1024*1024)        {            @Override            protected int sizeOf(String key, Bitmap value) {                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT){                    return value.getAllocationByteCount();                }                return value.getByteCount();            }            /**             * 移除bitmap的时候回调这个函数;             * @param evicted             * @param key             * @param oldValue             * @param newValue             */            @Override            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {                //super.entryRemoved(evicted, key, oldValue, newValue);                if(oldValue.isMutable())                {                    //3.0一下,bitmap的内存实在native中申请的;                    //3.0以上是在java层申请的;                    //8.0又到了native层;                    setpool.add(new WeakReference(oldValue,initReferenceQueue()));                }else{                    oldValue.recycle();                }            }        };    }    public void addSetPool(Bitmap bitmap)    {        setpool.add(new WeakReference(bitmap,initReferenceQueue()));    }    public void putBitmapToCache(String key,Bitmap bitmap)    {        lruCache.put(key,bitmap);    }    public Bitmap getBitmapFromCache(String key)    {         return  lruCache.get(key);    }    public void clearCache()    {        lruCache.evictAll();    }



在RecycleView的adapter中的Viewholder中item的点击事件,获取点击view的xy值和当前位置position,已经当前view的图片路径传给下一个显示大图的activity中;这里禁止了系统activity的跳转动画效果,因为我们自己要定义共享元素动画效果,可以兼容到android5.0以下;

            imageView.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View view) {                    float x = imageView.getX();                    float y = imageView.getY();                    Bundle bundle=new Bundle();                    bundle.putFloat("x",x);                    bundle.putFloat("y",y);                    scroolPosition = position;                    bundle.putInt("position",position);                    bundle.putString("picturepath",picturePath);                    Intent intent = new Intent(MainActivity.this, PictureInfoActivity.class);                    intent.putExtra("positon",bundle);                    startActivity(intent);                    overridePendingTransition(0,0);                }            });


跳转过去后的activity中是一个ViewPager,再次要给activity设置透明的,通过主题设置;然后获取viewpager中的当前显示的view做输定动画效果;

这里有一个难点,如何在activity打开的时候获取ViewPager当前要显示的view对象呢?大概过程是这样现场见viewpager的item,然后在绘制view;所以在绘制之前要给view设置属性动画;view绘制的时候可以给view设置视图树监听,但是Item的创建的时候怎么样去拿到这个view呢;看下面的代码就知道了,自定义adapter然后写一个监听方法,设置监听,item view在创建的时候会调用setPrimaryItem()这个方法,在这个方法中传递view到这个接口中;

 /**         * 这个方法当页面改变的时候会调用四次,第一次的position是上个页面的,后面三个的position是当前页面的;         * 当第一进入的viewpager的时候四次的position都是当前显示的位置;         * @param container         * @param position         * @param object         */        @Override        public void setPrimaryItem(ViewGroup container, int position, Object object) {            super.setPrimaryItem(container, position, object);            if(position!=currentPosition)            {                //滑到下一页面的时候;                currentPosition=position;                mCurrentView= (View) object;                mCurrentpath = list.get(position);                Log.e("setPrimaryItem",position+"---"+mCurrentpath+"----"+mCurrentView);                if(!isFirst)                {                    Log.e("hhah","动画没有结束调用");                    LoadPictureUtils.loadBigPicture(mCurrentpath, (ImageView) mCurrentView);                }                MMsg mMsg=new MMsg();                mMsg.setWhat(3);                mMsg.setObject(currentPosition);                MsgEventManager.getInstance().sendMsg(mMsg,"MainActivity");            }            if(listener!=null)            {                listener.currentItem((ImageView) mCurrentView);            }            Log.e("setPrimaryItem",""+position);        }

再通过view设置视图树监听view的绘制,这样共享动画就做好了;

        vpAdapter.setGetCurrentListener(new GetCurrentItme() {            @Override            public void currentItem(ImageView imageView) {                vpAdapter.removeGetCurrentListener();                iv=imageView;                Log.e("xy", iv.getX()+"---"+ iv.getY());                iv.setTop((int) y);//margin和xy不要混用;                iv.setLeft((int) x);                iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {                    @Override                    public void onGlobalLayout() {                        Log.e("xy", iv.getX()+"---"+ iv.getY()+"---"+iv.getWidth());                        int width = rl_content.getWidth();                        int height = rl_content.getHeight();                        ObjectAnimator oa = ObjectAnimator.ofInt(iv, "top", (int)y, 0);                        ObjectAnimator oa2 = ObjectAnimator.ofInt(iv, "left", (int)x, 0);                        ObjectAnimator oa3 = ObjectAnimator.ofInt(iv, "bottom", (int)(y+360), height);                        ObjectAnimator oa4 = ObjectAnimator.ofInt(iv, "right", (int)(x+360), width);                        ValueAnimator colorAnim = ObjectAnimator.ofInt(rl_content, "backgroundColor", start, end);                        colorAnim.setEvaluator(new ArgbEvaluator());                        ValueAnimator valueAnimator = ValueAnimator.ofInt(1);                        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {                            @Override                            public void onAnimationUpdate(ValueAnimator valueAnimator) {                                iv.setImageBitmap(bitmapFromCache);                            }                        });                        AnimatorSet as=new AnimatorSet();                        as.playTogether(oa,oa2,oa3,oa4,valueAnimator,colorAnim);//一起飞。                        as.setDuration(300);                        as.start();                        as.addListener(new Animator.AnimatorListener() {                            @Override                            public void onAnimationStart(Animator animator) {                            }                            @Override                            public void onAnimationEnd(Animator animator) {                                vpAdapter.loadFirstBigPicture();                            }                            @Override                            public void onAnimationCancel(Animator animator) {                            }                            @Override                            public void onAnimationRepeat(Animator animator) {                            }                        });                        if (Build.VERSION.SDK_INT >= 16) {                            iv.getViewTreeObserver().removeOnGlobalLayoutListener(this);                        } else {                            iv.getViewTreeObserver().removeGlobalOnLayoutListener(this);                        }                    }                });            }        });


在SetPrimaryItem()中没切换都会调用加载大图的方法,加载大图先要判断当前内存是否够用,够用的话直接加载原图进来,但是在绘制原图的时候如果图片的尺寸过大的会绘制不了的;需要view关系硬件加速;下面的方法也是在runable的run方法中执行的;加载后把bitmap引用保存在本类中,设置为静态的,等下次加载原图的时候复用这个bitmap对象;

   /**     * 加载原图     */    public void loadBigpicture() {        int[] maxTextureSize = new int[1];//canvs中一次绘制的最大尺寸;        GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);        float maxSize = 4048;        BitmapFactory.Options options = new BitmapFactory.Options();        options.inJustDecodeBounds = true;        Bitmap bitmap = BitmapFactory.decodeFile(path, options);        int outWidth = options.outWidth;        int outHeight = options.outHeight;        if (outWidth * outHeight * 4 < (am.getMemoryClass() * 1024 * 1024)) {            Log.e("getMemoryClass", am.getMemoryClass() + "M" + (outWidth * outHeight * 4 / 1024 / 1024));        }        //Log.e("tag","--"+size+"--"+bitmap.getWidth()+"--"+bitmap.getHeight());        options.inSampleSize = 1;        if(cachebitmap!=null)        {            boolean b = BitmapCache.checkInBitmap(cachebitmap, options);            if (b) {                Log.e("hah", "大图复用bitmap了");                options.inBitmap = cachebitmap;            }else if(!cachebitmap.isRecycled()){                cachebitmap.recycle();            }        }        options.inMutable = true;//设置成异变得才能复用内存;        options.inJustDecodeBounds = false;        options.inPreferredConfig = Bitmap.Config.ARGB_8888;        bitmap = BitmapFactory.decodeFile(path, options);        Message message = new Message();        message.what = 1;        Bundle bundle = new Bundle();        MyData myData = new MyData();        myData.setUitils(uitils);        myData.setBitmap(bitmap);        myData.setmImageView(mImageView);        myData.setPath(path);        bundle.putSerializable("bitmap", myData);        message.setData(bundle);        mHandler.sendMessage(message);        cachebitmap = bitmap;    }


当ViewPager进行切换的时候,小图浏览的activity中也需要更新的,这里使用了发消息的机制,去通知小图更新,这个消息机制是类似于系统的广播;小图的activiy中通过recycleView的方法设置滚动的位置;这里设置了+-5的处理方法,保证显示的位置是在中间;

    /**     * 消息接受者     */    MsgEventManager.MsgEvent event=new MsgEventManager.MsgEvent() {        @Override        public void msg(Object msg) {            MMsg mMsg= (MMsg) msg;            if(mMsg.what==1)            {                MsgEventManager.getInstance().sendMsg(list,"PictureInfoActivity");            }else if(mMsg.what==2)            {                int position = (int) mMsg.getObject();                View itemView = myGridLayoutManager.findViewByPosition(position);                ImageView imageView = itemView.findViewById(R.id.iv);                float x = imageView.getX();                float y = imageView.getY();                Bundle bundle=new Bundle();                bundle.putFloat("x",x);                bundle.putFloat("y",y);                mMsg.setBundle(bundle);            }else if(mMsg.what==3)            {                //动态自动的滑动recycleView到指定位置;                int position = (int) mMsg.getObject();                if(position>scroolPosition)                {                    scroolPosition = position;                    position+=5;                }else{                    scroolPosition = position;                    position-=5;                }                recycleview.scrollToPosition(position);            }        }    };


然后在监听返回按键,给当前的view设置属性动画,动画结束的时候finish掉activity,当然在finish的时候方法当前显示的大图bitmap的recyc方法;

但是这里有个问题,设置动画后没有效果,其实需要把这个item单独从viewPager中移除,然后添加在一个布局中,当然也可以添加到系统android.R.id.content布局中,在做动画效果就可以了;

   /**     * 关闭动画效果;     */    public void closeAnimation()    {        final Bitmap bitmapFromCache = BitmapCache.getInstence().getBitmapFromCache(mCurrentpath);        final MyImageView imageView= (MyImageView) mCurrentView;        imageView.setLayerType(View.LAYER_TYPE_HARDWARE,null);        ViewGroup parent = (ViewGroup) imageView.getParent();        parent.removeView(imageView);        ViewGroup viewGroup = (ViewGroup) findViewById(android.R.id.content);        viewGroup.addView(imageView);        MMsg mMsg=new MMsg();        mMsg.what=2;        mMsg.setObject(currentPosition);        MsgEventManager.getInstance().sendMsg(mMsg,"MainActivity");        Bundle bundle = mMsg.getBundle();        int x = (int) bundle.getFloat("x");        int y = (int) bundle.getFloat("y");        int width = rl_content.getWidth();        int height = rl_content.getHeight();        ObjectAnimator oa = ObjectAnimator.ofInt(imageView, "left", 0, x);        ObjectAnimator oa2 = ObjectAnimator.ofInt(imageView, "top", 0, y);        ObjectAnimator oa3 = ObjectAnimator.ofInt(imageView, "right",width, (x+360));        ObjectAnimator oa4 = ObjectAnimator.ofInt(imageView, "bottom", height,(y+360));        ValueAnimator colorAnim = ObjectAnimator.ofInt(rl_content, "backgroundColor", end, start);        colorAnim.setEvaluator(new ArgbEvaluator());        ValueAnimator valueAnimator = ValueAnimator.ofInt(1);        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator valueAnimator) {                imageView.setImageBitmap(bitmapFromCache);            }        });        AnimatorSet as=new AnimatorSet();        as.playTogether(oa,oa2,oa3,oa4,valueAnimator,colorAnim);//一起飞。        as.setDuration(300);        as.start();        as.addListener(new Animator.AnimatorListener() {            @Override            public void onAnimationStart(Animator animator) {            }            @Override            public void onAnimationEnd(Animator animator) {                Log.e("zxq",imageView.getTop()+"---"+imageView.getLeft()+"--"+imageView.getRight()+"--"+imageView.getBottom());                PictureInfoActivity.this.finish();                overridePendingTransition(0,0);            }            @Override            public void onAnimationCancel(Animator animator) {            }            @Override            public void onAnimationRepeat(Animator animator) {            }        });    }


做到这里图片浏览器基本上已经做完了,代码编写的时间较短,可以会存在很多不合理的地方,有待优化;

使用的技术点大概也就上面讲述的,关键是在于细节的处理;



更多相关文章

  1. Android使用Fragment嵌套Fragment的方式实现界面滑动
  2. Android中adapter的原理简单说明
  3. android 显示进度的按钮
  4. ImageView显示图片时,上下出现多余空白。
  5. 28. android——miniTwitter登录界面 详解
  6. Android文本输入框EditText方法说明和属性
  7. android:bug Fragment not attached to Activity ,fragment not a
  8. Tabhost+picture
  9. Dialog详解

随机推荐

  1. Android中常见的内存泄漏之上下文对象
  2. [Android]反编译查看、修改源码、逆向分
  3. Android仿UC浏览器左右上下滚动功能(附源
  4. Android(安卓)如何正确使用我们的图片资
  5. Android学习笔记之界面控件大小dip及布局
  6. Android(安卓)Studio apk打包,keystore.jk
  7. Kotlin系列——在Android中使用协程以及
  8. AsyncTask的使用和原理探究(一)
  9. android解决屏幕适配问题
  10. 教你如何在Android(安卓)6.0上创建系统悬