Android自定义无压缩加载超清大图

简介: 版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/u013132758。 https://blog.csdn.net/u013132758/article/details/80878256 自定义无压缩加载超清大图前言  已经很久没有写博客了,前段时间做项目就遇到加载超大图时系统内存溢出,我们一般处理加载图片时OOM的方法都是对图片进行压缩。
版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/u013132758。 https://blog.csdn.net/u013132758/article/details/80878256

自定义无压缩加载超清大图

前言

  已经很久没有写博客了,前段时间做项目就遇到加载超大图时系统内存溢出,我们一般处理加载图片时OOM的方法都是对图片进行压缩。但是发现手机系统相册是可以打开大图的,今天就分享一波自定义无压缩加载超清大图。
这里写图片描述

BitmapRegionDecoder

  BitmapRegionDecoder用来解码一张图片的某个矩形区域,通常用于加载某个图片的指定区域。通过调用该类提供的一系列newInstance(...)方法可获得BitmapRegionDecoder对象,该类提供的主要构造方法如下:
这里写图片描述

获取该对象后我们可以通过decodeRegion(rect,mOptions)方法传入需要显示的指定区域,就可以得到指定区域的Bitmap。这个方法的第一个参数就是要显示的矩形区域,第二个参数是BitmapFactory.Options(这个
类是BitmapFactory对图片进行解码时使用的一个配置参数类,其中定义了一系列的public成员变量,每个成员变量代表一个配置参数。)

自定义控件

要自定义这个控件,我们主要分以下几个步骤:
1. 提供一个图片入口
2. 自定义手势监听,通过手势上下左右滑动,更新显示图片的区域
3. 自定义显示指定区域图片,即通过手势滑动传入的区域显示大图在该区域的内容

自定义加载大图控件(LargeImageView)

package com.example.bthvi.bigpictureloading;

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.media.ExifInterface;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

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

/**
 * 自定义加载超大图片的View
 *
 * create by bthvi on 2018/06/29
 */
public class LargeImageView extends View {
    /**
     *用来解码一张图片的某个矩形区域
     */
    private BitmapRegionDecoder mDecoder;

    /**
     * 图片的宽度和高度
     */
    private int mImageWidth,mImageHeight;

    /**
     *绘制图片区域
     */
    private volatile Rect rect = new Rect();
    /**
     * 手势监听器
     */
    private MoveGestureDetector moveGestureDetector;
    /**
     * 图片解码时的参数配置类
     */
    private static final BitmapFactory.Options mOptions = new BitmapFactory.Options();

    /**
     * 图片解码时所用的颜色模式
     */
    static {
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
    }

    /**
     * 构造方法
     * @param context
     */
    public LargeImageView(Context context) {
        super(context);
        initView();
    }

    public LargeImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public LargeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

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

    @Override
    protected void onDraw(Canvas canvas)
    {
        Bitmap bm = mDecoder.decodeRegion(rect, mOptions);
        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;

        //默认直接显示图片的中心区域,可以自己去调节
        rect.left = imageWidth / 2 - width / 2;
        rect.top = imageHeight / 2 - height / 2;
        rect.right = rect.left + width;
        rect.bottom = rect.top + height;

    }
    /**
     * 初始化
     */
    private void initView() {
        moveGestureDetector = new MoveGestureDetector(getContext());
        SimpleMoveGestureDetector simpleMoveGestureDetector = new SimpleMoveGestureDetector(){
            @Override
            public boolean onMove(MoveGestureDetector detector){
                int moveX = (int) detector.getMoveX();
                int moveY = (int) detector.getMoveY();

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

                return true;
            }
        };
        moveGestureDetector.setOnMoveGestureListener(simpleMoveGestureDetector);

    }
    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 = mDecoder.getWidth();
            mImageHeight = mDecoder.getHeight();

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

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


    private void checkWidth() {
        Rect rect2 = rect;
        int imageWidth = mImageWidth;
        if (rect2.right > imageWidth){
            rect2.right = imageWidth;
            rect2.left = imageWidth - getWidth();
        }

        if (rect2.left < 0){
            rect2.left = 0;
            rect2.right = getWidth();
        }
    }
    private void checkHeight()
    {

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

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

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

}

上述源码的几个主要方法是setInputStream(InputStream),onTouchEvent(MotionEvent),onMeasure(int,int),onDraw(Canvas),下面我们看下这几个方法的主要逻辑:

  • setInputStream的主要作用是通过传入的图片输入流来获取图片真实的宽和高。
  • onTouchEvent这个方法主要监听我们的手势,通过手势监听回调,在滑动时改变图片显示的区域。
  • onMeasure这个方法是给显示区域的上下左右边届赋值,图片的大小就是显示的大小。
  • onDraw(Canvas)就是根据上面的指定区域拿到bitmap并绘制在自定义控件上。

自定义手势监听(MoveGestureDetector)

package com.example.bthvi.bigpictureloading;

import android.content.Context;
import android.graphics.PointF;
import android.view.MotionEvent;
/**
 * 手势处理
 * create by bthvi on 2018/06/29
 */
public class MoveGestureDetector extends BaseGestureDetector{
    /**
     * 当前点
     */
    private PointF mCurrentPointer;
    /**
     * 上次触摸点
     */
    private PointF mPrePointer;
    //仅仅为了减少创建内存
    private PointF mDeltaPointer = new PointF();

    //用于记录最终结果,并返回
    private PointF mExtenalPointer = new PointF();

    private OnMoveGestureListener mListenter;

    public MoveGestureDetector(Context context) {
        super(context);
    }
    public void setOnMoveGestureListener(OnMoveGestureListener listener){
        this.mListenter = listener;
    }

    public OnMoveGestureListener getmListenter(){
        return this.mListenter;
    }

    @Override
    protected void handleInProgressEvent(MotionEvent event) {
        /**
         * 这里就是处理多点触控,MotionEvent.ACTION_MASK(0x000000ff)与触摸事件进行逻辑运算and。
         * 无论多少手指在屏幕上操作,进过逻辑and运算后 都是一个手指
         */
        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:
                /**防止没收到 UP 或 CANCEL*/
                resetState();
                mPreMotionEvent = MotionEvent.obtain(event);
                updateStateByEvent(event);
                break;
            case MotionEvent.ACTION_MOVE:
                mGestureInProgress = mListenter.onMove(this);
                break;
        }
    }

    @Override
    protected void updateStateByEvent(MotionEvent event) {
        final MotionEvent prev = mPreMotionEvent;

        mPrePointer = calculateCenterPointer(prev);
        mCurrentPointer = calculateCenterPointer(event);

        boolean mSkipThisEvent = prev.getPointerCount() != event.getPointerCount();

        mExtenalPointer.x = mSkipThisEvent ? 0 : mCurrentPointer.x - mPrePointer.x;
        mExtenalPointer.y = mSkipThisEvent ? 0 : mCurrentPointer.y - mPrePointer.y;

    }

    /**
     * 计算多指触控中心点
     * @param event
     * @return
     */
    private PointF calculateCenterPointer(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;
    }

}

这个类主要有四个方法,他们的作用主要是:

  • handleInProgressEventhandleStartProgressEvent处理手势触摸事件,这里我们注意到int actionCode = event.getAction() & MotionEvent.ACTION_MASK;很多同学可能跟我一样会想为啥要这样写呢?首先,我们知道MotionEvent.ACTION_MASK的值为(0x000000ff),我们的触摸事件跟它做逻辑与运算的结果一定会小于它,这里就是将多个手指的触摸事件转为一个手指触摸。
  • calculateCenterPointer这个方法就是计算多个手指在屏幕上的中心位置。
  • updateStateByEvent这个方法主要是更新当前手指的中心位置。

BaseGestureDetector

package com.example.bthvi.bigpictureloading;

import android.content.Context;
import android.view.MotionEvent;
/**
 * 手势处理抽象类
 * create by bthvi on 2018/06/29
 */
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;
    }


}

OnMoveGestureListener

package com.example.bthvi.bigpictureloading;
/**
 * 手势监听接口
 * create by bthvi on 2018/06/29
 */
public interface OnMoveGestureListener {
    public boolean onMoveBegin(MoveGestureDetector detector);
    public boolean onMove(MoveGestureDetector detector);
    public void onMoveEnd(MoveGestureDetector detector);
}

SimpleMoveGestureDetector

package com.example.bthvi.bigpictureloading;

/**
 * 手势监听接口实现
 * create by bthvi on 2018/06/29
 */
public class SimpleMoveGestureDetector implements OnMoveGestureListener {
    @Override
    public boolean onMoveBegin(MoveGestureDetector detector) {
        return false;
    }

    @Override
    public boolean onMove(MoveGestureDetector detector) {
        return false;
    }

    @Override
    public void onMoveEnd(MoveGestureDetector detector) {

    }
}

调用

首先在xml中调用LargeImageView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <com.example.bthvi.bigpictureloading.LargeImageView
        android:id="@+id/largeImageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

然后在Activity里面去将图片的输入流设置给LargeImageView

package com.example.bthvi.bigpictureloading;

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

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

/**
 *
 * create by bthvi on 2018/06/29
 */
public class MainActivity extends AppCompatActivity {

    LargeImageView largeImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        largeImageView = findViewById(R.id.largeImageView);
        try {
            InputStream stream = getAssets().open("world.jpg");
            largeImageView.setInputStream(stream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

最后附上源码地址点击查看源码

特别感谢

Android 高清加载巨图方案 拒绝压缩图片

相关文章
|
7月前
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
549 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
7月前
|
Android开发
Android自定义view之利用PathEffect实现动态效果
本文介绍如何在Android自定义View中利用`PathEffect`实现动态效果。通过改变偏移量,结合`PathEffect`的子类(如`CornerPathEffect`、`DashPathEffect`、`PathDashPathEffect`等)实现路径绘制的动态变化。文章详细解析了各子类的功能与参数,并通过案例代码展示了如何使用`ComposePathEffect`组合效果,以及通过修改偏移量实现动画。最终效果为一个菱形图案沿路径运动,源码附于文末供参考。
140 0
|
7月前
|
Android开发 开发者
Android自定义view之利用drawArc方法实现动态效果
本文介绍了如何通过Android自定义View实现动态效果,重点使用`drawArc`方法完成圆弧动画。首先通过`onSizeChanged`进行测量,初始化画笔属性,设置圆弧相关参数。核心思路是不断改变圆弧扫过角度`sweepAngle`,并调用`invalidate()`刷新View以实现动态旋转效果。最后附上完整代码与效果图,帮助开发者快速理解并实践这一动画实现方式。
194 0
|
7月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
141 0
|
7月前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
373 65
Android自定义view之网易云推荐歌单界面
|
7月前
|
XML 前端开发 Android开发
一篇文章带你走近Android自定义view
这是一篇关于Android自定义View的全面教程,涵盖从基础到进阶的知识点。文章首先讲解了自定义View的必要性及简单实现(如通过三个构造函数解决焦点问题),接着深入探讨Canvas绘图、自定义属性设置、动画实现等内容。还提供了具体案例,如跑马灯、折线图、太极图等。此外,文章详细解析了View绘制流程(measure、layout、draw)和事件分发机制。最后延伸至SurfaceView、GLSurfaceView、SVG动画等高级主题,并附带GitHub案例供实践。适合希望深入理解Android自定义View的开发者学习参考。
705 84
|
7月前
|
前端开发 Android开发 UED
讲讲Android为自定义view提供的SurfaceView
本文详细介绍了Android中自定义View时使用SurfaceView的必要性和实现方式。首先分析了在复杂绘制逻辑和高频界面更新场景下,传统View可能引发卡顿的问题,进而引出SurfaceView作为解决方案。文章通过Android官方Demo展示了SurfaceView的基本用法,包括实现`SurfaceHolder.Callback2`接口、与Activity生命周期绑定、子线程中使用`lockCanvas()`和`unlockCanvasAndPost()`方法完成绘图操作。
221 3
|
7月前
|
Android开发 开发者
Android自定义view之围棋动画(化繁为简)
本文介绍了Android自定义View的动画实现,通过两个案例拓展动态效果。第一个案例基于`drawArc`方法实现单次动画,借助布尔值控制动画流程。第二个案例以围棋动画为例,从简单的小球直线运动到双向变速运动,最终实现循环动画效果。代码结构清晰,逻辑简明,展示了如何化繁为简实现复杂动画,帮助读者拓展动态效果设计思路。文末提供完整源码,适合初学者和进阶开发者学习参考。
145 0
Android自定义view之围棋动画(化繁为简)
|
7月前
|
Java Android开发 开发者
Android自定义view之围棋动画
本文详细介绍了在Android中自定义View实现围棋动画的过程。从测量宽高、绘制棋盘背景,到创建固定棋子及动态棋子,最后通过属性动画实现棋子的移动效果。文章还讲解了如何通过自定义属性调整棋子和棋盘的颜色及动画时长,并优化视觉效果,如添加渐变色让白子更明显。最终效果既可作为围棋动画展示,也可用作加载等待动画。代码完整,适合进阶开发者学习参考。
164 0
|
7月前
|
传感器 Android开发 开发者
Android自定义view之3D正方体
本文介绍了如何通过手势滑动操作实现3D正方体的旋转效果,基于Android自定义View中的GLSurfaceView。相较于使用传感器控制,本文改用事件分发机制(onTouchEvent)处理用户手势输入,调整3D正方体的角度。代码中详细展示了TouchSurfaceView的实现,包括触控逻辑、OpenGL ES绘制3D正方体的核心过程,以及生命周期管理。适合对Android 3D图形开发感兴趣的开发者学习参考。
132 0