自定义控件之大图的加载

要求:加载一张大图片到APP中,用户手机仅显示图片的一部分,根据用户的交互,用户手机显示图片不同的部分.(使用分块的模式,加载一张大图片)
实现步骤:
1.创建资源目录,assets文件夹,把超大图片放入其中

2.创建一个完全的自定义控件,实现加载显示图片一部分的功能
    (1).继承View,覆写其3个构造方法
        public class BigViewextendsView {
publicBigView(Context context) {
    super(context);
}
publicBigView(Context context, AttributeSet attrs) {
   super(context, attrs);
}
publicBigView(Context context, AttributeSet attrs,int defStyleAttr) {
  super(context, attrs, defStyleAttr);
}
    .....
        }
    
    (2).创建设置图片参数对象,使用静态代码块,给图片设置颜色质量,降低图片中的颜色数量,来减少存储图片所需的内存,改变之后的图片和原图,人眼看不出来   
         //创建设置图片参数对象
private staticBitmapFactory.Options ops= newBitmapFactory.Options();
static{
//图片的颜色质量的参考网址:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1023/1825.html
//设置图片的颜色质量,使其不要太占内存
ops.inPreferredConfig= Bitmap.Config.RGB_565;
}

    (3).创建一个方法,从外界接收图片的字节流
/**
* @param inputStream代表着超大图片文件
*/
public voidsetInput(InputStream inputStream) {
//通过图片参数对象设置不会把这张图片加载到内存中,避免OOM的问题
ops.inJustDecodeBounds= true;
//只是单纯的将流和图片的参数设置对象进行关联 参数:1. 2.null 3.对图片的设置对象
BitmapFactory.decodeStream(inputStream,null,ops);
//通过图片的参数设置对象获取到图片的宽和高
imageWidth= ops.outWidth;
imageHeight= ops.outHeight;
try {
//BitmapRegionDecoder用于解码图像,把图片字节流其中一部分以矩形区域展示并换成Bitmap对象 参数:1.流资源 2.false会对加载的图片进行复制
decoder= BitmapRegionDecoder.newInstance(inputStream,false);
} catch(IOException e) {
   e.printStackTrace();
}
}

    (4).通过手机屏幕和 图片的 宽与高进行运算 , 绘制一个在图片有具体位置的矩形 ( 提示 : 一般测量等耗时操作不要在 onDraw 里执行 , 放到 onMeasure)
@Override
protected voidonMeasure(intwidthMeasureSpec, intheightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取手机屏幕的宽和高
measuredWidth= getMeasuredWidth();
measuredHeight= getMeasuredHeight();
//是控件加载大图片一部分,显示的是图片中心位置,所以设置左右和上下底部的点
int top = imageHeight/ 2- measuredHeight/ 2;
int bottom = imageHeight/ 2+ measuredHeight/ 2;
int left = imageWidth/ 2- measuredWidth/ 2;
int right = imageWidth/ 2+ measuredWidth/ 2;
//创建一个矩形,设置其最左边的点,最上面的点,最右边的点,最下面的点
currentRect= newRect(left, top, right, bottom);
}

    (5).画出自定义控件的最终样子.此逻辑在onDraw方法里进行
@Override
protected voidonDraw(Canvas canvas) {
super.onDraw(canvas);
//创建一个指定区域的矩形Bitmap。 参数1.矩阵(就是手机显示图片一部分的大小,以屏幕的宽和高为基准) 2.图片参数对象
Bitmap bitmap = decoder.decodeRegion(currentRect,ops);
//重新画一张图片图片 ,参数1:Bitmap 2,3设置为0 4画笔设置为null
canvas.drawBitmap(bitmap,0,0,null);
}

    (6).处理用户的触摸事件,使用户滑动屏幕时,加载的部分图片也跟着用户的滑动出现变化
@Override
public booleanonTouchEvent(MotionEvent event) {
switch(event.getAction()) {
//用户按下的回调
case MotionEvent.ACTION_DOWN:
//获取当前的x轴和Y(也就是起点)
downX= (int) event.getX();
downY= (int) event.getY();
break;

//用户移动的回调
case MotionEvent.ACTION_MOVE:
//获取移动后的x轴和y(也就是终点)
int moveX = (int) event.getX();
int moveY = (int) event.getY();
//起点-终点,得到距离(之所以是起点减,是因为终点是移动值不固定)
int diffX = downX- moveX;
int diffY = downY- moveY;
System.out.println("按下的x:"+ downX+ "移动后的x:"+ moveX);
//把终点变回起点
downX= moveX;
downY= moveY;
//加载区域根据距离进行移动
refreshRect(diffX, diffY);
break;
}
//请求重绘View,也就是再次调用Ondraw方法.
invalidate();
//返回值必须是true,代码才起效果.
return true;
}

    (7).限制用户移动范围,不能让用户一直拉,直到超出图片的范围.
private voidrefreshRect(intdiffX, intdiffY) {
//矩形改变值的方法
currentRect.offset(diffX, diffY);
//限制用户移动范围,不能让用户一直拉,直到超出图片的范围.
//不让用户超出左边界,左边界是0
if (currentRect.left<= 0) {
//当到了左边界,把最左边的参数设置为0
currentRect.left= 0;
//最右边的参数设置为屏幕的宽即可
currentRect.right= measuredWidth;
}
//不让用户超出右边界,右边界图片的宽.
else if(currentRect.right>= imageWidth) {
//当到了右边界,把最左边的参数设置图片宽-屏幕宽
currentRect.left=imageWidth-measuredWidth;
//最右边的参数设置为图片的宽即可
currentRect.right= imageWidth;
}
//不让用户超出顶部,顶部边界是0.
if (currentRect.top<=0){
//当到了顶部,把最上边的参数设置为0
currentRect.top=0;
//最下面的参数为屏幕的高
currentRect.bottom=measuredHeight;
}
//不让用户超出底部,顶部边界是图片的高.
else if(currentRect.bottom>=imageHeight){
//当到了顶部,用图片的高-屏幕的高
currentRect.top=imageHeight-measuredHeight;
//最下面的参数为图片的高
currentRect.bottom=imageHeight;
}
}

3.在XML布局文件中使用自己自定义的控件.
<!--A.使用自己的自定义控件,加载大图片的一部分-->
<com.example.siyan.imagedemo.BigView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

4.java代码中初始化控件,拿到图片资源,使用自己的自定义控件进行图片加载
//初始化控件
BigView bigView = (BigView) findViewById(R.id.img);
//从资产目录把图片转换成字节流
InputStream inputStream = getAssets().open("world.jpg");
//使用自定义控件,调用方法传入图片的字节流,进行图片的展示
bigView.setInput(inputStream);

使用开源框架实现上面的逻辑:开源自定义控件实现的代码思路和我们的是一样的,只不过还可以实现两个手指进行对图片放到缩小的功能,对细节的处理也更周全.
1.从github下载WorldMap-master开源自定义控件(网址:https://github.com/johnnylambada/WorldMap )

2.找到library文件,从src代码中找到这三个类( ImageSurfaceView, InputStreamScene, Scene ) 拷贝 到我们的项目中
3. XML 布局文件中使用开源的自定义控件 .
<com.example.siyan.imagedemo.custom.ImageSurfaceView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

4. java代码中初始化控件,拿到图片资源,使用开源的自定义控件进行图片加载
ImageSurfaceView img = (ImageSurfaceView) findViewById(R.id.img);
InputStream inputStream = getAssets().open("world.jpg");
img.setInputStream(inputStream);

具体代码:

public class BigView extends View{

    private static BitmapFactory.Options ops = new BitmapFactory.Options();
    static {
//图片的颜色质量的参考网址:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1023/1825.html
        //设置图片的颜色质量,使其不要太占内存
        ops.inPreferredConfig = Bitmap.Config.RGB_565;
    }

    private int imageWidth;
    private int imageHeight;
    private BitmapRegionDecoder decoder;
    private int measuredWidth;
    private int measuredHeight;
    private Rect currentRect;
    private int downX;
    private int downY;

    public BigView(Context context) {
        super(context);
    }

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

    public BigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    /**
     * @param inputStream 代表着超大图片文件
     */
    public void setInput(InputStream inputStream) {
//通过图片参数对象设置不会把这张图片加载到内存中,避免OOM的问题
        ops.inJustDecodeBounds = true;
//只是单纯的将流和图片的参数设置对象进行关联 参数:1.流 2.为null 3.对图片的设置对象
        BitmapFactory.decodeStream(inputStream, null, ops);
//通过图片的参数设置对象获取到图片的宽和高
        imageWidth = ops.outWidth;
        imageHeight = ops.outHeight;
        try {
//BitmapRegionDecoder用于解码图像,把图片字节流其中一部分以矩形区域展示并换成Bitmap对象 参数:1.流资源 2.false会对加载的图片进行复制
            decoder = BitmapRegionDecoder.newInstance(inputStream, false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取手机屏幕的宽和高
        measuredWidth = getMeasuredWidth();
        measuredHeight = getMeasuredHeight();
//是控件加载大图片一部分,显示的是图片中心位置,所以设置左右和上下底部的点
        int top = imageHeight / 2 - measuredHeight / 2;
        int bottom = imageHeight / 2 + measuredHeight / 2;
        int left = imageWidth / 2 - measuredWidth / 2;
        int right = imageWidth / 2 + measuredWidth / 2;
//创建一个矩形,设置其最左边的点,最上面的点,最右边的点,最下面的点
        currentRect = new Rect(left, top, right, bottom);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//创建一个指定区域的矩形Bitmap。 参数1.矩阵(就是手机显示图片一部分的大小,以屏幕的宽和高为基准) 2.图片参数对象
        Bitmap bitmap = decoder.decodeRegion(currentRect, ops);
//重新画一张图片图片 ,参数1:Bitmap 2,3设置为0 4画笔设置为null
        canvas.drawBitmap(bitmap, 0, 0, null);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
//用户按下的回调
            case MotionEvent.ACTION_DOWN:
//获取当前的x轴和Y轴(也就是起点)
                downX = (int) event.getX();
                downY = (int) event.getY();
                break;
//用户移动的回调
            case MotionEvent.ACTION_MOVE:
//获取移动后的x轴和y轴(也就是终点)
                int moveX = (int) event.getX();
                int moveY = (int) event.getY();
//起点-终点,得到距离(之所以是起点减,是因为终点是移动值不固定)
                int diffX = downX - moveX;
                int diffY = downY - moveY;
                System.out.println("按下的x:" + downX + " 移动后的x:" + moveX);
//把终点变回起点
                downX = moveX;
                downY = moveY;
//加载区域根据距离进行移动
                refreshRect(diffX, diffY);
                break;
        }
//请求重绘View,也就是再次调用Ondraw方法.
        invalidate();
//返回值必须是true,代码才起效果.
        return true;
    }
    private void refreshRect(int diffX, int diffY) {
//矩形改变值的方法
        currentRect.offset(diffX, diffY);
//限制用户移动范围,不能让用户一直拉,直到超出图片的范围.
        //不让用户超出左边界,左边界是0
        if (currentRect.left <= 0) {
//当到了左边界,把最左边的参数设置为0
            currentRect.left = 0;
//最右边的参数设置为屏幕的宽即可
currentRect.right = measuredWidth;
        }
//不让用户超出右边界,右边界图片的宽.
        else if (currentRect.right >= imageWidth) {
//当到了右边界,把最左边的参数设置图片宽-屏幕宽
            currentRect.left=imageWidth-measuredWidth;
//最右边的参数设置为图片的宽即可
            currentRect.right = imageWidth;
        }
//不让用户超出顶部,顶部边界是0.
        if (currentRect.top<=0){
//当到了顶部,把最上边的参数设置为0
            currentRect.top=0;
//最下面的参数为屏幕的高
            currentRect.bottom=measuredHeight;
        }
//不让用户超出底部,顶部边界是图片的高.
        else if(currentRect.bottom>=imageHeight){
//当到了顶部,用图片的高-屏幕的高
            currentRect.top=imageHeight-measuredHeight;
//最下面的参数为图片的高
            currentRect.bottom=imageHeight;
        }



    }

}
/
public class MainActivity extends AppCompatActivity {

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

//初始化控件

//        ImageSurfaceView img = (ImageSurfaceView) findViewById(R.id.img);
//
//        InputStream inputStream = null;
//        try {
//            inputStream = getAssets().open("world.jpg");
//            img.setInputStream(inputStream);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }

//初始化控件



        BigView bigView = (BigView) findViewById(R.id.img);



//从资产目录把图片转换成字节流


        InputStream inputStream = null;
        try {
            inputStream = getAssets().open("world.jpg");
        } catch (IOException e) {
            e.printStackTrace();
        }


//使用自定义控件,调用方法传入图片的字节流,进行图片的展示



        bigView.setInput(inputStream);


    }
    }
 <test.bwie.com.datu.BigView
        android:id="@+id/img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <!--&lt;!&ndash;&ndash;&gt;<test.bwie.com.datu.ImageSurfaceView&ndash;&gt;-->
    <!--&lt;!&ndash;android:id="@+id/img"&ndash;&gt;-->
    <!--&lt;!&ndash;android:layout_width="wrap_content"&ndash;&gt;-->
    <!--&lt;!&ndash;android:layout_height="wrap_content" />-->
</RelativeLayout>


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值