Android High Definition Loading Giant Map Scheme Refuses to Compress Pictures

Android High Definition Loading Giant Map Scheme Refuses to Compress Pictures

For reprinting, please indicate the source:
http://blog.csdn.net/lmj623565791/article/details/49300989; 
This article is from: [Zhang Hongyang's blog]

I. overview

I haven't updated my last blog for a while. It's mainly caused by some private affairs recently. So I'll start with a simple blog pulse.

Everyone is familiar with loading pictures. In order to avoid OOM as much as possible, we usually do the following:

  1. For picture display: according to the need to display the size of the picture control to compress the picture display.
  2. If the number of pictures is very large, it will use caching mechanisms such as LruCache to keep the content occupied by all pictures within one range.

In fact, there is another case for image loading, that is, a single image is very large, and compression is not allowed. For example, display: world map, Qingming River map, microblog map, etc.

So what should we do about this demand?

Firstly, without compression and loading according to the size of the original image, the screen is certainly not large enough, and considering the memory situation, it is impossible to load the whole picture into memory at one time, so it must be partial loading, so a class is needed:

  • BitmapRegionDecoder

Secondly, since the screen display is not complete, at least add a drag gesture, so that users can drag to view.

In summary, the purpose of this blog post is to customize a View that displays a giant picture, and to support users to drag and View. The general effect chart is as follows:

Okay, this Qingming River Map is too long. I want to see the full picture. Download it at the end of the article. The picture is in the assets directory. Of course, if your chart is very high, it can be dragged up and down.

Second, First Understanding of BitmapRegionDecoder

BitmapRegionDecoder is mainly used to display a rectangular area of an image. If you need to display a specific area of an image, this class is very suitable.

For this kind of usage, it is very simple, since it is a display image of a certain area, then at least one method is needed to set the image; a method can be imported into the display area; see:

  • BitmapRegionDecoder provides a series of new Instance methods to construct objects, supporting the path of incoming files, file descriptors, inputstrem of files, etc.

    For example:

     BitmapRegionDecoder bitmapRegionDecoder =
      BitmapRegionDecoder.newInstance(inputStream, false);
    
    • 1
    • 2
    • 3
  • This solves the problem of passing in the image we need to process, then the next step is to display the specified area.

    bitmapRegionDecoder.decodeRegion(rect, options);
    • 1

    Parametric 1 is obviously a rect, parameter 2 is BitmapFactory.Options, you can control the image in SampleSize, in Preferred Config and so on.

So let's look at a super simple example:

package com.zhy.blogcodes.largeImage;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;

import com.zhy.blogcodes.R;

import java.io.IOException;
import java.io.InputStream;

public class LargeImageViewActivity extends AppCompatActivity
{
    private ImageView mImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_large_image_view);

        mImageView = (ImageView) findViewById(R.id.id_imageview);
        try
        {
            InputStream inputStream = getAssets().open("tangyan.jpg");

            //Get the width and height of the picture
            BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
            tmpOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(inputStream, null, tmpOptions);
            int width = tmpOptions.outWidth;
            int height = tmpOptions.outHeight;

            //Set the center area of the display image
            BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.RGB_565;
            Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
            mImageView.setImageBitmap(bitmap);


        } catch (IOException e)
        {
            e.printStackTrace();
        }


    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

The above code uses BitmapRegionDecoder to load the images in assets, calls bitmap RegionDecoder. decodeRegion to parse the middle rectangular area of the image, returns bitmap, and finally displays it on the ImageView.

Design sketch:

The small picture above shows the middle area of the larger picture below.

ok, so now we know the basic users of BitmapRegionDecoder, so to spread out, we need to customize a control to display the macrograph is very simple. First, the scope of Rect is the size of our View, and then according to the user's mobile gesture, we can constantly update the parameters of our Rect.

3. Custom Display Big Picture Control

According to the above analysis, the idea of our custom control is very clear.

  • Provide an entry for setting up pictures
  • Rewrite onTouchEvent to update the parameters of the display area according to the user's mobile gesture
  • After each update of the area parameters, call invalidate, go to the regionDecoder.decodeRegion in onDraw, get the bitmap, go to draw.

Clear up, find so easy, the following code:

package com.zhy.blogcodes.largeImage.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import java.io.IOException;
import java.io.InputStream;

/**
 * Created by zhy on 15/5/16.
 */
public class LargeImageView extends View
{
    private BitmapRegionDecoder mDecoder;
    /**
     * Width and Height of Pictures
     */
    private int mImageWidth, mImageHeight;
    /**
     * Drawn area
     */
    private volatile Rect mRect = new Rect();

    private MoveGestureDetector mDetector;


    private static final BitmapFactory.Options options = new BitmapFactory.Options();

    static
    {
        options.inPreferredConfig = Bitmap.Config.RGB_565;
    }

    public void setInputStream(InputStream is)
    {
        try
        {
            mDecoder = BitmapRegionDecoder.newInstance(is, false);
            BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
            // Grab the bounds for the scene dimensions
            tmpOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(is, null, tmpOptions);
            mImageWidth = tmpOptions.outWidth;
            mImageHeight = tmpOptions.outHeight;

            requestLayout();
            invalidate();
        } catch (IOException e)
        {
            e.printStackTrace();
        } finally
        {

            try
            {
                if (is != null) is.close();
            } catch (Exception e)
            {
            }
        }
    }


    public void init()
    {
        mDetector = new MoveGestureDetector(getContext(), new MoveGestureDetector.SimpleMoveGestureDetector()
        {
            @Override
            public boolean onMove(MoveGestureDetector detector)
            {
                int moveX = (int) detector.getMoveX();
                int moveY = (int) detector.getMoveY();

                if (mImageWidth > getWidth())
                {
                    mRect.offset(-moveX, 0);
                    checkWidth();
                    invalidate();
                }
                if (mImageHeight > getHeight())
                {
                    mRect.offset(0, -moveY);
                    checkHeight();
                    invalidate();
                }

                return true;
            }
        });
    }


    private void checkWidth()
    {


        Rect rect = mRect;
        int imageWidth = mImageWidth;
        int imageHeight = mImageHeight;

        if (rect.right > imageWidth)
        {
            rect.right = imageWidth;
            rect.left = imageWidth - getWidth();
        }

        if (rect.left < 0)
        {
            rect.left = 0;
            rect.right = getWidth();
        }
    }


    private void checkHeight()
    {

        Rect rect = mRect;
        int imageWidth = mImageWidth;
        int imageHeight = mImageHeight;

        if (rect.bottom > imageHeight)
        {
            rect.bottom = imageHeight;
            rect.top = imageHeight - getHeight();
        }

        if (rect.top < 0)
        {
            rect.top = 0;
            rect.bottom = getHeight();
        }
    }


    public LargeImageView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        init();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        mDetector.onToucEvent(event);
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        Bitmap bm = mDecoder.decodeRegion(mRect, options);
        canvas.drawBitmap(bm, 0, 0, null);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width = getMeasuredWidth();
        int height = getMeasuredHeight();

        int imageWidth = mImageWidth;
        int imageHeight = mImageHeight;

         //By default, the central area of the image can be displayed directly, which can be adjusted by itself.
        mRect.left = imageWidth / 2 - width / 2;
        mRect.top = imageHeight / 2 - height / 2;
        mRect.right = mRect.left + width;
        mRect.bottom = mRect.top + height;

    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184

According to the above source code:

  1. setInputStream to get the true width and height of the image and initialize our mDecoder
  2. In onMeasure, we assign rect values to our display area, which is the size of view.
  3. In onTouchEvent, we listen for motion gestures, change rect parameters in listened callbacks, and do boundary checks, and finally invalidate.
  4. In onDraw, you get a bitmap based on rect, and draw it.

ok, it's not complicated, but have you noticed that the code that monitors the user's move gesture is a bit strange? Well, here's the Move Gesture Detector, which imitates the Scale Gesture Detector of the system. The code is as follows:

  • MoveGestureDetector

    package com.zhy.blogcodes.largeImage.view;
    
    import android.content.Context;
    import android.graphics.PointF;
    import android.view.MotionEvent;
    
    public class MoveGestureDetector extends BaseGestureDetector
    {
    
        private PointF mCurrentPointer;
        private PointF mPrePointer;
        //Just to reduce the creation of memory
        private PointF mDeltaPointer = new PointF();
    
        //Used to record the final result and return it
        private PointF mExtenalPointer = new PointF();
    
        private OnMoveGestureListener mListenter;
    
    
        public MoveGestureDetector(Context context, OnMoveGestureListener listener)
        {
            super(context);
            mListenter = listener;
        }
    
        @Override
        protected void handleInProgressEvent(MotionEvent event)
        {
            int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
            switch (actionCode)
            {
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    mListenter.onMoveEnd(this);
                    resetState();
                    break;
                case MotionEvent.ACTION_MOVE:
                    updateStateByEvent(event);
                    boolean update = mListenter.onMove(this);
                    if (update)
                    {
                        mPreMotionEvent.recycle();
                        mPreMotionEvent = MotionEvent.obtain(event);
                    }
                    break;
    
            }
        }
    
        @Override
        protected void handleStartProgressEvent(MotionEvent event)
        {
            int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
            switch (actionCode)
            {
                case MotionEvent.ACTION_DOWN:
                    resetState();//To prevent CANCEL or UP from not being received, for insurance purposes
                    mPreMotionEvent = MotionEvent.obtain(event);
                    updateStateByEvent(event);
                    break;
                case MotionEvent.ACTION_MOVE:
                    mGestureInProgress = mListenter.onMoveBegin(this);
                    break;
            }
    
        }
    
        protected void updateStateByEvent(MotionEvent event)
        {
            final MotionEvent prev = mPreMotionEvent;
    
            mPrePointer = caculateFocalPointer(prev);
            mCurrentPointer = caculateFocalPointer(event);
    
            //Log.e("TAG", mPrePointer.toString() + " ,  " + mCurrentPointer);
    
            boolean mSkipThisMoveEvent = prev.getPointerCount() != event.getPointerCount();
    
            //Log.e("TAG", "mSkipThisMoveEvent = " + mSkipThisMoveEvent);
            mExtenalPointer.x = mSkipThisMoveEvent ? 0 : mCurrentPointer.x - mPrePointer.x;
            mExtenalPointer.y = mSkipThisMoveEvent ? 0 : mCurrentPointer.y - mPrePointer.y;
    
        }
    
        /**
         * Calculating multi-finger centers based on event
         *
         * @param event
         * @return
         */
        private PointF caculateFocalPointer(MotionEvent event)
        {
            final int count = event.getPointerCount();
            float x = 0, y = 0;
            for (int i = 0; i < count; i++)
            {
                x += event.getX(i);
                y += event.getY(i);
            }
    
            x /= count;
            y /= count;
    
            return new PointF(x, y);
        }
    
    
        public float getMoveX()
        {
            return mExtenalPointer.x;
    
        }
    
        public float getMoveY()
        {
            return mExtenalPointer.y;
        }
    
    
        public interface OnMoveGestureListener
        {
            public boolean onMoveBegin(MoveGestureDetector detector);
    
            public boolean onMove(MoveGestureDetector detector);
    
            public void onMoveEnd(MoveGestureDetector detector);
        }
    
        public static class SimpleMoveGestureDetector implements OnMoveGestureListener
        {
    
            @Override
            public boolean onMoveBegin(MoveGestureDetector detector)
            {
                return true;
            }
    
            @Override
            public boolean onMove(MoveGestureDetector detector)
            {
                return false;
            }
    
            @Override
            public void onMoveEnd(MoveGestureDetector detector)
            {
            }
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
  • BaseGestureDetector

    package com.zhy.blogcodes.largeImage.view;
    
    import android.content.Context;
    import android.view.MotionEvent;
    
    
    public abstract class BaseGestureDetector
    {
    
        protected boolean mGestureInProgress;
    
        protected MotionEvent mPreMotionEvent;
        protected MotionEvent mCurrentMotionEvent;
    
        protected Context mContext;
    
        public BaseGestureDetector(Context context)
        {
            mContext = context;
        }
    
    
        public boolean onToucEvent(MotionEvent event)
        {
    
            if (!mGestureInProgress)
            {
                handleStartProgressEvent(event);
            } else
            {
                handleInProgressEvent(event);
            }
    
            return true;
    
        }
    
        protected abstract void handleInProgressEvent(MotionEvent event);
    
        protected abstract void handleStartProgressEvent(MotionEvent event);
    
        protected abstract void updateStateByEvent(MotionEvent event);
    
        protected void resetState()
        {
            if (mPreMotionEvent != null)
            {
                mPreMotionEvent.recycle();
                mPreMotionEvent = null;
            }
            if (mCurrentMotionEvent != null)
            {
                mCurrentMotionEvent.recycle();
                mCurrentMotionEvent = null;
            }
            mGestureInProgress = false;
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    You might say that it's too much trouble for a move gesture to do so much code. Yes, the detection of motion gestures is very simple, so the main reason for writing this is to be reusable, for example, there are a lot of XXX Gesture Detector, when we need to listen to what gestures, we directly take a detector to detect many conveniences. I'm sure you've been frustrated with Google too. Why only Scale Gesture Detector and not Rotate Gesture Detector?

According to the above, we should understand why we did this. It was not compulsory at that time. Everyone had his own personality.

But it is worth mentioning that the above gesture detection method is not what I thought, but an open source project. https://github.com/rharter/android-gesture-detectors It contains a lot of gesture detection. The corresponding blog posts are: http://code.almeros.com/android-multitouch-gesture-detectors#.VibzzhArJXg On the other hand, I learned the two categories secretly.~ Ha

Four, test

In fact, the test didn't say much. We put our LargeImage View in the layout file, and then we set up the inputstream in Activity.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent">


    <com.zhy.blogcodes.largeImage.view.LargeImageView
        android:id="@+id/id_largetImageview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Then set up the picture in Activity:

package com.zhy.blogcodes.largeImage;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import com.zhy.blogcodes.R;
import com.zhy.blogcodes.largeImage.view.LargeImageView;

import java.io.IOException;
import java.io.InputStream;

public class LargeImageViewActivity extends AppCompatActivity
{
    private LargeImageView mLargeImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_large_image_view);

        mLargeImageView = (LargeImageView) findViewById(R.id.id_largetImageview);
        try
        {
            InputStream inputStream = getAssets().open("world.jpg");
            mLargeImageView.setInputStream(inputStream);

        } catch (IOException e)
        {
            e.printStackTrace();
        }


    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

Design sketch:

ok, so at this point, the scheme of displaying the giant map and the detailed code are described. Overall, it is very simple.  
However, in the actual project, there may be more demand, such as increasing enlargement, shrinking, increasing fast skating gestures and so on, so you can refer to this library: https://github.com/johnnylambada/WorldMap The library basically meets most of the requirements. It will be much simpler and easier to customize if you look at the library according to the idea of this article. The map of my map is provided in the library.

Ha, master this, in the future interview process can also be quietly installed, when you gracefully answer the android loading plan, and then, one after another, in fact, there is another situation, that is, high-definition display of giant maps, then we should __________. I believe the interviewer will have a much better impression of you.~

Source Click Download

Reference link

Keywords: Android Java github Mobile

Added by rinteractive on Sat, 18 May 2019 12:40:24 +0300