Android 开发中如何利用拖动的方式显示高清图片?

发布于:2023-01-22 ⋅ 阅读:(286) ⋅ 点赞:(0)

1、概述html

在android开发的过程当中,有时候会遇到这样的需求,须要显示很大的图片,而且还不容许压缩。好比显示:世界地图、微博长图等,那么如何完成这个需求呢?

首先咱们分析一下,图片很是大,考虑到内存的状况,咱们不能一次将整个图片加载到内存中,由于这样会OOM,而后图片的宽或者高超出了手机屏幕的尺寸,要想显示整张没有压缩过的图片,咱们只能每次加载图片的局部,而后监听控件的滑动事件,获得滑动的方向和距离,来从新加载图片的局部,这样咱们就能够利用拖动的方式显示整张图片了

2、BitmapRegionDecoderandroid

android加载图片的局部须要用到BitmapRegionDecoder类,BitmapRegionDecoder类主要用来加载图片的某一块矩形区域,若是你想显示图片的某一块矩形区域,那么这个类很是合适,下面来看一下它的用法:git

BitmapRegionDecoder提供了一系列的newInstance方法来构造实例,支持传入文件路径,文件描述符,文件的数据流等,以下:

public static BitmapRegionDecoder newInstance(InputStream is, boolean isShareable)
    public static BitmapRegionDecoder newInstance(String pathName, boolean isShareable)
    public static BitmapRegionDecoder newInstance(FileDescriptor fd, boolean isShareable)
    public static BitmapRegionDecoder newInstance(byte[] data, int offset, int length, boolean isShareable)

利用图片构造BitmapRegionDecoder对象后,咱们能够调用它的decodeRegion方法来加载图片中的指定区域,以下:

Bitmap bitmap = mBitmapRegionDecoder.decodeRegion(mRect, mOptions);

第一个参数指定加载区域,第二个参数是BitmapFactory.options,能够用来指定图片的解码格式,取样率等

3、开发代码ide

了解了以上内容后,咱们就能够开发加载高清大图的控件了,下面是自定义的代码,注释很详细:

package com.liunian.androidbasic.addbigimage.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.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import com.liunian.androidbasic.utils.CloseUtils;

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

/**
 * Created by dell on 2018/4/12.
 */

public class BigImageView extends View {
    private Context mContext;
    private BitmapRegionDecoder mBitmapRegionDecoder;
    private Rect mRect = new Rect();
    private int mImageWidth = 0;
    private int mImageHeight = 0;
    private BitmapFactory.Options mOptions;

    public BigImageView(Context context) {
        this(context, null);
    }

    public BigImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();
    }

    private void init() {
        // 指定图片的解码格式为RGB_565
        mOptions = new BitmapFactory.Options();
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
    }

    // 传入须要加载的图片的inputStream
    public void setInputStream(InputStream inputStream) {
        try {
            // 先得到图片的宽高(在华为的手机上若是在构造BitmapRegionDecoder对象后得到图片宽高将获取失败)
            BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
            tmpOptions.inJustDecodeBounds = true; // 将inJuestDecodeBounds设为true,这样BitmapFactory只会解析图片的原始宽/高信息,并不会真正的去加载图片
            BitmapFactory.decodeStream(inputStream, null, tmpOptions); // 解析图片得到图片的宽高
            mImageWidth = tmpOptions.outWidth; // 保存图片的宽
            mImageHeight = tmpOptions.outHeight; // 保存图片的搞

            // 根据图片对应的BitmapRegionDecoder对象
            mBitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
            requestLayout(); // 这里调用requestLayout方法,请求从新布局,触发调用控件的onMeasure,初始化加载区域
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            CloseUtils.closeIO(inputStream);
        }
    }

    // 保存上一次事件的发生位置
    float lastEventX = 0;
    float lastEventY = 0;
    // 保存当前事件的发生位置
    float eventX = 0;
    float eventY = 0;

    // 重载控件的onTouchEvent方法,监控控件的事件
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 这里只处理了控件的Move事件,若是有更复杂需求,好比说根据手势缩放图片,能够将事件交由给GestureDetector处理
        if (event != null) {
            // 获得当前事件的发生位置
            eventX = event.getX();
            eventY = event.getY();
            switch (event.getAction()) {
                case MotionEvent.ACTION_MOVE: // move事件
                    // 根据当前事件的发生位置和上一次事件的发生位置计算出用户的滑动距离,并调整图片的加载区域
                    move((int) (lastEventX - eventX), (int) (lastEventY - eventY));
                    break;
            }
            // 保存上一次事件的发生位置
            lastEventX = event.getX();
            lastEventY = event.getY();
        }
        return true;
    }

    // 根据滑动距离调整图片的加载区域
    private void move(int moveX, int moveY) {
        // 只有当图片的高大于控件的高,控件纵向显示不下图片时,才须要调整加载区域的上下位置
        if (mImageHeight > getHeight()) {
            mRect.top = mRect.top + moveY;
            mRect.bottom = mRect.top + getHeight();
        }
        // 只有当图片的宽大于控件的宽,控件横向显示不下图片时,才须要调整加载区域的左右位置
        if (mImageWidth > getWidth()) {
            mRect.left = mRect.left + moveX;
            mRect.right = mRect.left + getWidth();
        }
        invalidate();
    }

    // 重写onDraw方法,绘制图片的局部
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mBitmapRegionDecoder != null) {
            // 检查绘制区域的宽高,以避免绘制到图片觉得的区域
            checkHeight();
            checkWidth();
            // 加载图片的指定区域
            Bitmap bitmap = mBitmapRegionDecoder.decodeRegion(mRect, mOptions);
            Log.i("liunianprint:", "onDraw" + mRect.top + " " + mRect.left + " " + mRect.bottom + " " + mRect.right);
            // 绘制图片的局部到控件上
            if (bitmap != null) {
                canvas.drawBitmap(bitmap, 0, 0, null);
            }
        }
    }

    /**
     * 检查加载区域是否超出图片范围
     */
    private void checkWidth() {
        // 只有当图片的宽大于控件的宽,控件横向显示不下图片时,才须要调整加载区域的左右位置
        if (mImageWidth > getWidth()) {
            if (mRect.right > mImageWidth) {
                mRect.right = mImageWidth;
                mRect.left = mRect.right - getWidth();
            }

            if (mRect.left < 0) {
                mRect.left = 0;
                mRect.right = getWidth();
            }
        } else {
            mRect.left = (mImageWidth - getWidth()) / 2;
            mRect.right = mRect.left + getWidth();
        }
    }

    /**
     * 检查加载区域是否超出图片范围
     */
    private void checkHeight() {
        // 只有当图片的高大于控件的高,控件纵向显示不下图片时,才须要调整加载区域的上下位置
        if (mImageHeight > getHeight()) {
            if (mRect.bottom > mImageHeight) {
                mRect.bottom = mImageHeight;
                mRect.top = mRect.bottom - getHeight();
            }
            if (mRect.top < 0) {
                mRect.top = 0;
                mRect.bottom = getHeight();
            }
        } else {
            mRect.top = (mImageHeight - getHeight()) / 2;
            mRect.bottom = mRect.top + getHeight();
        }
    }

    /**
     * 重写测量控件大小的方法,初始化图片的加载区域
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 获得控件的宽高
        int width = getMeasuredWidth(); // 使用getMeasuredWidth,不要使用getWidth方法,getWidth可能为0
        int height = getMeasuredHeight(); // getMeasuredHeight,不要使用getHeight方法,getWidth可能为0

        // 初始化图片的加载区域为图片的中心,能够自行根据需求调整
        mRect.left = (mImageWidth - width) / 2;
        mRect.right = mRect.left + width;
        mRect.top = (mImageHeight - height) / 2;
        mRect.bottom = mRect.top + height;
    }
}

使用方法

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="com.liunian.androidbasic.MainActivity">
    <com.liunian.androidbasic.addbigimage.view.BigImageView
        android:id="@+id/big_image_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>
mBigImageView = (BigImageView) findViewById(R.id.big_image_view);
        try {
            mBigImageView.setInputStream(getAssets().open("ditu.jpg"));
        } catch (IOException e) {
            e.printStackTrace();
        }

使用效果以下:

滑动控件能够加载地图的其余区域

有需要文章中完整代码的同学 现在私信发送 “底层源码” 即可免费获取

现在私信发送 “进阶” 还可以获取《更多 Android 源码解析+核心笔记+面试真题》

4、总结

一、加载高清大图须要用到BitmapRegionDecoder类,这个类能够加载图片的指定区域;

二、因为图片超出了控件的大小,咱们须要监听控件的滑动事件,实现能够经过拖动的方式显示整张图片;

三、加载图片的指定区域,得到对应的Bitmap,而后经过重写onDraw方法将Bitmap绘制到控件上

本文含有隐藏内容,请 开通VIP 后查看