Android HD giant image loading solution rejects Image Compression

Source: Internet
Author: User

Android HD giant image loading solution rejects Image Compression
I. Overview

I haven't updated my previous blog for a while, mainly because of some recent private affairs. So let's start with a simple blog.

You are familiar with image loading. Generally, to avoid OOM as much as possible, follow these steps:

For Image Display: compress the image according to the size of the image control. If the number of images is very large: LruCache and other caching mechanisms will be used to maintain the content occupied by all images within the same range.

In fact, there is another situation for image loading, that is, a single image is huge and cannot be compressed. For example, display: world map, Qingming River Map, Weibo long chart, etc.

So what should we do with this demand?

First, do not compress, load according to the source image size, then the screen is certainly not big enough, and considering the memory, it is impossible to load the whole graph into the memory at a time, so it must be partial loading, you need to use a class:

BitmapRegionDecoder

Secondly, since the screen is not displayed, you must add at least a gesture of dragging up, down, and left, so that you can drag and view.

In summary, the purpose of this blog post is to customize a View that displays a huge image. You can drag and drop the View, which is roughly as follows:

Okay, it's too long to see the full graph. Download the image at the end of the article and find it in the assets Directory. Of course, if your graph has a large height, you can drag it up or down.

Ii. First knowledge of BitmapRegionDecoder

BitmapRegionDecoderThis class is mainly used to display a rectangular area of an image. If you want to display a specified area of an image, this class is very suitable.

The usage of this class is very simple. Since it is to display an image area, at least one method is required to set the image; one method is used to input the display area. For details, see:

BitmapRegionDecoder provides a series of newInstance methods to construct objects. It supports passing in file paths, file descriptors, and inputstrem of files.

For example:

 BitmapRegionDecoder bitmapRegionDecoder =  BitmapRegionDecoder.newInstance(inputStream, false);

The above solves the problem of passing in the image that we need to process, and the next step is to display the specified area.

bitmapRegionDecoder.decodeRegion(rect, options);

Parameter 1 is obviously a rect, and parameter 2 is BitmapFactory. Options. You can control the imageinSampleSize,inPreferredConfig.

The following is a 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); // obtain the width and height of the image BitmapFactory. options tmpOptions = new BitmapFactory. options (); tmpOptions. inJustDecodeBounds = true; BitmapFactory. decodeStream (inputStream, null, tmpOptions); int width = tmpOptions. outWidth; int height = tmpOptions. outHeight; // set BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder in the center of the displayed image. 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 ();}}}

The above code is to use BitmapRegionDecoder to load images in assets, callbitmapRegionDecoder.decodeRegionParse the middle rectangle area of the image, return bitmap, and display it on the ImageView.

:

The thumbnail above shows the middle area of the big image below.

OK, now we knowBitmapRegionDecoderSo it is easy to customize a control to display a giant image. First, the Rect range is the size of our View, and then according to the user's mobile gesture, update the Rect parameters constantly.

3. Custom big chart control

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

Provide an entry for setting the image to override onTouchEvent. Update the parameters of the display area based on the user's movement gesture. After each region parameter is updated, call invalidate and onDraw to go to regionDecoder. decodeRegion get bitmap, go to draw

Clear up and find so easy. The code below is as follows:

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;/*** Creat Ed by zhy on 15/5/16. */public class LargeImageView extends View {private BitmapRegionDecoder mDecoder;/*** image width and height */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 (); invalida Te ();} 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 is displayed directly. You can adjust the mRect by yourself. left = imageWidth/2-width/2; mRect. top = imageHeight/2-height/2; mRect. right = mRect. left + width; mRect. bottom = mRect. top + height ;}}

Based on the above source code:

SetInputStream obtains the true width and height of the image, and initializes our mDecoder onMeasure to assign values to the rect of our display area, in onTouchEvent, which is the size of view, we listen to the move gesture, change the rect parameters in the listener callback, and perform a boundary check. Finally, invalidate in onDraw obtains bitmap Based on rect, then draw

Okay, the above is not complicated, but have you noticed that the code for listening to the user's move gesture is a bit strange? Well, it imitates the system'sScaleGestureDetector, CompiledMoveGestureDetectorThe 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 extends BaseGestureDetector {private PointF mCurrentPointer; private PointF mPrePointer; // create private PointF mDeltaPointer = new PointF () only to reduce memory; // used to record the final result, and return private PointF mExtenalPointer = new PointF (); private OnMoveGestureLi Stener mListenter; public listener (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; c Ase 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 (); // prevents none When CANCEL or UP is received, mPreMotionEvent = MotionEvent is safe. obtain (event); updateStateByEvent (event); break; case MotionEvent. ACTION_MOVE: mGestureInProgress = mListenter. onMoveBegin (this); break;} protected void updateStateByEvent (MotionEvent event) {final MotionEvent prev = mPreMotionEvent; mPrePointer = marker (prev); mCurrentPointer = alert (event ); // Log. e (TAG, mPrePoi Nter. 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;}/*** calculate multiple Centers Based on the 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 (includetector); public void onMoveEnd (MoveGestureDetector detector );} public static class implements OnMoveGestureListener {@ Override public boolean onMoveBegin (MoveGestureDetector detector) {return true ;}@ Override public boolean onMove (includetector) {return false ;} @ Override public void onMoveEnd (MoveGestureDetector detector ){}}}

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

You may say that it is too troublesome to execute so much code with a move gesture. Indeed, the move gesture detection is very simple. The reason for this writing is mainly to be reusable. For example, there are a bunchXXXGestureDetectorWhen we need to monitor any gesture, it is convenient to use a detector to detect it. I believe everyone is also depressed about Google. Why is it onlyScaleGestureDetectorWithoutRotateGestureDetectorAnd.

According to the above, we should understand why we should do this. At that time, we were not forced and everyone had their own personality.

However, it is worth mentioning that the above gesture detection method is not what I think, but an open-source project https://github.com/rharter/android-gesture-detectors, which contains a lot of hand testing. The corresponding blog post is: Workshop ~ Ha

Iv. Test

The test is actually not easy to explain. We just put our LargeImageView into the layout file, and then set inputstream in the Activity.


      
   
  

Then set the image in the 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();        }    }}

:

OK. Now, the solution and detailed code of the giant graph are described, and the overall process is very simple.
However, in actual projects, there may be more needs, such as increasing or downgrading, and adding Fast Slide gestures. You can refer to this library as follows. The graph of my map is provided in this library.

Haha, you have mastered this. You can also install it in the interview process later. When you finish the android image loading scheme elegantly, You can say one more thing, is to display a huge image in high definition, so we should... I believe the interviewer will have a good impression on you ~ Have a nice day ~

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.