BitmapFactory.Options.inBitmap causes tearing when switching ImageView bitmap often

后端 未结 5 948
野的像风
野的像风 2021-02-05 16:31

I\'ve encountered a situation where I have to display images in a slideshow that switches image very fast. The sheer number of images makes me want to store the JPEG data in mem

5条回答
  •  感动是毒
    2021-02-05 17:11

    You should use the onDraw() method of the ImageView since that method is called when the view needs to draw its content on screen.

    I create a new class named MyImageView which extends the ImageView and override the onDraw() method which will trigger a callback to let the listener knows that this view has finished its drawing

    public class MyImageView extends ImageView {
    
        private OnDrawFinishedListener mDrawFinishedListener;
    
        public MyImageView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            if (mDrawFinishedListener != null) {
                mDrawFinishedListener.onOnDrawFinish();
            }
        }
    
        public void setOnDrawFinishedListener(OnDrawFinishedListener listener) {
            mDrawFinishedListener = listener;
        }
    
        public interface OnDrawFinishedListener {
            public void onOnDrawFinish();
        }
    
    }
    

    In the MainActivity, define 3 bitmaps: one reference to the bitmap which is being used by the ImageView to draw, one for decoding and one reference to the bitmap that is recycled for the next decoding. I reuse the synchronized block from vminorov's answer, but put in different places with explanation in the code comment

    public class MainActivity extends Activity {
    
        private Bitmap mDecodingBitmap;
        private Bitmap mShowingBitmap;
        private Bitmap mRecycledBitmap;
    
        private final Object lock = new Object();
    
        private volatile boolean ready = true;
    
        ArrayList images = new ArrayList();
        int position = 0;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            images.add(R.drawable.black);
            images.add(R.drawable.blue);
            images.add(R.drawable.green);
            images.add(R.drawable.grey);
            images.add(R.drawable.orange);
            images.add(R.drawable.pink);
            images.add(R.drawable.red);
            images.add(R.drawable.white);
            images.add(R.drawable.yellow);
    
            final MyImageView imageView = (MyImageView) findViewById(R.id.image);
            imageView.setOnDrawFinishedListener(new OnDrawFinishedListener() {
    
                @Override
                public void onOnDrawFinish() {
                    /*
                     * The ImageView has finished its drawing, now we can recycle
                     * the bitmap and use the new one for the next drawing
                     */
                    mRecycledBitmap = mShowingBitmap;
                    mShowingBitmap = null;
                    synchronized (lock) {
                        ready = true;
                        lock.notifyAll();
                    }
                }
            });
    
            final Button goButton = (Button) findViewById(R.id.button);
    
            goButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            while (true) {
                                BitmapFactory.Options options = new BitmapFactory.Options();
                                options.inSampleSize = 1;
    
                                if (mDecodingBitmap != null) {
                                    options.inBitmap = mDecodingBitmap;
                                }
    
                                mDecodingBitmap = BitmapFactory.decodeResource(
                                        getResources(), images.get(position),
                                        options);
    
                                /*
                                 * If you want the images display in order and none
                                 * of them is bypassed then you should stay here and
                                 * wait until the ImageView finishes displaying the
                                 * last bitmap, if not, remove synchronized block.
                                 * 
                                 * It's better if we put the lock here (after the
                                 * decoding is done) so that the image is ready to
                                 * pass to the ImageView when this thread resume.
                                 */
                                synchronized (lock) {
                                    while (!ready) {
                                        try {
                                            lock.wait();
                                        } catch (InterruptedException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                    ready = false;
                                }
    
                                if (mShowingBitmap == null) {
                                    mShowingBitmap = mDecodingBitmap;
                                    mDecodingBitmap = mRecycledBitmap;
                                }
    
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        if (mShowingBitmap != null) {
                                            imageView
                                                    .setImageBitmap(mShowingBitmap);
                                            /*
                                             * At this point, nothing has been drawn
                                             * yet, only passing the data to the
                                             * ImageView and trigger the view to
                                             * invalidate
                                             */
                                        }
                                    }
                                });
    
                                try {
                                    Thread.sleep(5);
                                } catch (InterruptedException e) {
                                }
    
                                position++;
                                if (position >= images.size())
                                    position = 0;
                            }
                        }
                    };
                    Thread t = new Thread(runnable);
                    t.start();
                }
            });
    
        }
    }
    

提交回复
热议问题