AndroidTV开发13——实现TV端大图浏览

最近TV开发需要加载的图片很长,大小也很大,并且还不允许压缩。比如显示:世界地图、清明上河图、微博长图、海报、活动照片等。

那么对于这种需求,该如何做呢?

首先不压缩,按照原图尺寸加载,那么屏幕肯定是不够大的,并且考虑到内存的情况,不可能一次性整图加载到内存中,所以肯定是局部加载,那么就需要用到一个类:

  • BitmapRegionDecoder

其次,既然屏幕显示不完,那么最起码要添加一个上下左右拖动的手势,让用户可以拖动查看。

实现方式有很多:

1.BitmapRegionDecoder:

分片加载,使用系统BitmapRegionDecoder去加载本地的图片,调用bitmapRegionDecoder.decodeRegion解析图片的矩形区域,返回bitmap,最终显示在ImageView上。这种方案需要手动处理滑动、缩放手势,网络图片还要处理缓存策略等问题。实现方式比较繁琐也不是很推荐。

2.SubsamplingScaleImageView

一款封装BitmapRegionDecoder的三方库,已经处理了滑动,缩放手势。我们可以考虑选择这个库来进行加载长图,但是官方上的Demo示例加载的长图均为本地图片。这可能并不符合我们的网络场景需求,所以对于网络图片,我们还要考虑不同的加载框架,

3.Glide+SubsamplingScaleImageView混合加载渲染

对于图片加载框架,Glide当然是首选,我们使用Glide进行网络图片的下载和缓存管理,FileTarget作为桥梁,SubsamplingScaleImageView进行本地资源图片的分片加载,看起来很靠谱,那么一起来实现吧。

4.自定义LongImageView,结合BitmapRegionDecoder使用

本文采取的第四种方式,代码如下:

public class LongImageView extends AppCompatImageView {private String TAG = getClass().getSimpleName();private int mTargetY = 0;private int scrollDistance = 0;private int imgWidth = 0, imgHeight = 0;private Bitmap imgBitmap = null;private Bitmap holderBitmap;private BitmapRegionDecoder bitmapRegionDecoder;private boolean isScrolling = false;private float startY = -1;private int mStartTargetY = -1;private BitmapFactory.Options scaleOptions = new BitmapFactory.Options();public Bitmap bitmap;public static final String EVENT_PROP_URL = "url";public static final String EVENT_PROP_BITMAP_WIDTH = "resourceWidth";public static final String EVENT_PROP_BITMAP_HEIGHT = "resourceHeight";
​public LongImageView(Context context) {super(context);init();}
​public LongImageView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}
​public LongImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}
​private void init() {//遥控器按键事件setOnKeyListener(new OnKeyListener() {@Overridepublic boolean onKey(View v, int keyCode, KeyEvent event) {scrollDistance = scrollDistance <= 0 ? getHeight() : scrollDistance;if (event.getAction() == KeyEvent.ACTION_DOWN && !isScrolling) {switch (event.getKeyCode()) {case KeyEvent.KEYCODE_DPAD_UP:scrollBy(0 - viewHeight2ImageHeight(scrollDistance));break;case KeyEvent.KEYCODE_DPAD_DOWN:scrollBy(viewHeight2ImageHeight(scrollDistance));break;}}return false;}
​});//响应鼠标拖拽(手指也可以)setOnTouchListener(new OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:Log.e(TAG, " touch down");startY = event.getRawY();mStartTargetY = mTargetY;break;case MotionEvent.ACTION_MOVE:float currentY = event.getRawY();mTargetY = mStartTargetY + (int) (viewHeight2ImageHeight((int) (startY - currentY)) * 1f);mTargetY = Math.max(0, Math.min(mTargetY, imgHeight - viewHeight2ImageHeight(getHeight())));Log.e(TAG, " touch move " + mTargetY);invalidate();break;case MotionEvent.ACTION_UP:Log.e(TAG, " touch up");startY = -1;break;}return true;}});}
​private void startScroll(int targetY) {targetY = Math.max(0, Math.min(targetY, imgHeight - viewHeight2ImageHeight(getHeight())));ValueAnimator valueAnimator = ValueAnimator.ofInt(mTargetY, targetY);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mTargetY = (int) animation.getAnimatedValue();invalidate();}});valueAnimator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {isScrolling = true;}
​@Override
​public void onAnimationEnd(Animator animation) {isScrolling = false;}
​@Override
​public void onAnimationCancel(Animator animation) {isScrolling = false;}
​@Override
​public void onAnimationRepeat(Animator animation) {}});valueAnimator.setInterpolator(new LinearInterpolator());//设置滑动速度valueAnimator.setDuration(10);valueAnimator.start();}
​
​/*** 根据InputStream 生成 BitmapRegionDecoder** @param imgStream*/public void setImageStream(InputStream imgStream) {try {BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeStream(imgStream, new Rect(0, 0, 0, 0), options);imgWidth = options.outWidth;imgHeight = options.outHeight;//寻找最佳的缩放比例int viewHeight2ImageHeight = viewHeight2ImageHeight(getHeight());//设置缩放比例int scale = getScaleValue(imgWidth, viewHeight2ImageHeight, 1);scaleOptions.inSampleSize = scale;} catch (Exception e) {e.printStackTrace();}try {bitmapRegionDecoder = BitmapRegionDecoder.newInstance(imgStream, false);} catch (Exception e) {e.printStackTrace();}}
​/*** 根据图片文件 生成 BitmapRegionDecoder** @param imgFile*/public void setImageFile(File imgFile) {try {BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFile(imgFile.getAbsolutePath(), options);imgWidth = options.outWidth;imgHeight = options.outHeight;//寻找最佳的缩放比例int viewHeight2ImageHeight = viewHeight2ImageHeight(getHeight());int scale = getScaleValue(imgWidth, viewHeight2ImageHeight, 1);scaleOptions.inSampleSize = scale;} catch (Exception e) {e.printStackTrace();}try {bitmapRegionDecoder = BitmapRegionDecoder.newInstance(imgFile.getAbsolutePath(), false);} catch (Exception e) {e.printStackTrace();}}
​private int getScaleValue(int imgWidth, int imgHeight, int scaleValue) {long memory = Runtime.getRuntime().maxMemory() / 4;if (memory > 0) {if (imgWidth * imgHeight * 4 > memory) {scaleValue += 1;return getScaleValue(imgWidth, imgHeight, scaleValue);}}return scaleValue;}
​/*** 根据图片Id 生成 BitmapRegionDecoder** @param resourceId*/@SuppressLint("ResourceType")public void setImageResource(int resourceId) {InputStream imgStream = getResources().openRawResource(resourceId);setImageStream(imgStream);}
​/*** 设置占位图** @param holderId*/public void setPlaceHolder(int holderId) {holderBitmap = BitmapFactory.decodeResource(getResources(), holderId);}
​/*** 滑动到具体的位置** @param targetY*/private void scrollTo(int targetY) {startScroll(targetY);}
​/*** 设置相对于当前,继续滑动的距离。小于0 向上滑动,大于0向下滑动** @param distance*/private void scrollBy(int distance) {startScroll(mTargetY + distance);}
​/*** 设置每次滑动的距离** @param scrollDistance*/public void setScrollDistance(int scrollDistance) {this.scrollDistance = scrollDistance;}
​@Overrideprotected void onDraw(Canvas canvas) {Log.e(getClass().getSimpleName(), "draw start " + getWidth() + " " + getHeight());canvas.save();int sr = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);Paint paint = new Paint();paint.setAntiAlias(true);if (bitmapRegionDecoder != null) {int targetHeight = viewHeight2ImageHeight(getHeight());//根据控件的高度获取需要在原始图片上截取的高度Log.e(getClass().getSimpleName(), "targetHeight " + targetHeight);Log.e(getClass().getSimpleName(), "draw resource "+ " " + imgWidth + " " + imgHeight+ " " + mTargetY + " " + targetHeight);imgBitmap = null;if (imgHeight - mTargetY >= targetHeight) {//剩余区域大于 当前控件高度imgBitmap = bitmapRegionDecoder.decodeRegion(new Rect(0, mTargetY, imgWidth, mTargetY + targetHeight), scaleOptions);} else {//剩余区域小于 当前控件高度imgBitmap = bitmapRegionDecoder.decodeRegion(new Rect(0, imgHeight - targetHeight, imgWidth, imgHeight), scaleOptions);}if (imgBitmap != null) {//绘制需要展示的图片canvas.drawBitmap(imgBitmap, new Rect(0, 0, imgBitmap.getWidth(), imgBitmap.getHeight()), new Rect(0, 0, getWidth(), getHeight()), paint);}imgBitmap = null;holderBitmap = null;} else {if (holderBitmap != null) {//绘制占位图canvas.drawBitmap(holderBitmap, new Rect(0, 0, holderBitmap.getWidth(), holderBitmap.getHeight()), new Rect(0, 0, getWidth(), getHeight()), paint);}}canvas.restoreToCount(sr);canvas.restore();Log.e(getClass().getSimpleName(), "draw end");}
​/*** 图片高度转为相对于控件的高度** @param imgHeight* @return*/private int imageHeight2ViewHeight(int imgHeight) {if (this.imgHeight <= 0) {return 0;}return (int) (imgHeight / ((float) getWidth() / imgWidth * imgHeight) * getHeight());}
​/*** 控件高度转为相对于图片高度** @param viewHeight* @return*/private int viewHeight2ImageHeight(int viewHeight) {if (getHeight() <= 0) {return 0;}return (int) (viewHeight / ((float) getWidth() / imgWidth * imgHeight) * imgHeight);}
​@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();//回收资源和释放内存release();}
​public void release() {if (imgBitmap != null && !imgBitmap.isRecycled()) {imgBitmap.recycle();imgBitmap = null;}if (holderBitmap != null && !holderBitmap.isRecycled()) {holderBitmap.recycle();holderBitmap = null;}System.gc();}
}

5.在MainActivity中的使用:

/*** @auth: njb* @date: 2022/11/7 0:11* @desc:*/
public class MainActivity extends AppCompatActivity {public String url = "https://qcloudimg-moss.cp47.ott.cibntv.net/data_center/files/2022/10/26/67a66d35-3f7c-4de8-9dfe-c706e42f44f2.jpg";
​@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}
​private void initView() {LongImageView mImageView = findViewById(R.id.imageView);mImageView.setScrollDistance((int) ((float) ScreenUtils.getScreenHeight(this) / 3 * 2));mImageView.setFocusable(true);try {Glide.with(this).load(url).downloadOnly(new SimpleTarget<File>() {@Overridepublic void onResourceReady(@NonNull File resource, @Nullable Transition<? super File> transition) {mImageView.setImageFile(resource);
​}});} catch (Exception e) {e.printStackTrace();}}
}

6.布局文件代码:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"xmlns:app="http://schemas.android.com/apk/res-auto"><com.example.longimageView.view.LongImageViewandroid:id="@+id/imageView"android:layout_width="0dp"android:layout_height="0dp"android:focusable="true"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

7.实现的效果如下:

AndroidTV开发13——实现TV端大图浏览相关推荐

  1. TV盒子大图浏览优化

    TV盒子在高分辨率时会有马赛克.图片加载缓慢及模糊情况,特别注意的是修改盒子的分辨率仅仅是修改输出的视频流的分辨率,对盒子系统本身的分辨率时没有任何影响,其他的参考优化方案如下: glide.加载缓冲 ...

  2. AndroidTV开发14优雅地实现超长大图加载

    AndroidTV开发14优雅地实现超长大图加载 1.文章前言 之前在Android和Vue端都实现过长图加载,虽然实现需求,但是有很多问题没有解决,效果也不尽人意今天就各种问题来分析一下: 图片加载 ...

  3. AndroidTV开发知识总结(机顶盒端 )

    前言 金三银四之际被离职,入职了一家TV开发的公司.入职一月,简单总结下TV开发需要注意的一些东西. 知识点 适配 试用了一下sw 即最小宽度适配(推荐此适配方法) 1280*720 对应的分辨率 m ...

  4. 电视TV端使用RecyclerView开发遇到的问题

    最近在做一个TV端使用的相册. 偷偷展示一下,相册大概长这个样子: 相册也支持多选删除: 非删除模式下点击单一图片会进入单一图片展示界面.单一图片可以左右翻页. 从展示界面返回相册界面,光标定位到展示 ...

  5. 瑜伽教学TV端APP开发搭建教程IPTV系统

    瑜伽作为一种非常受欢迎的锻炼方式,受到越来越多人的喜爱.但因为时间和距离等因素的影响,很多人也喜欢自己在家里做一些瑜伽锻炼,比如饭后或者休息时间打开电视进行瑜伽锻炼,不仅仅可以很好的学习动作,还可以一 ...

  6. Android tv开发px,【Android】TV端项目开发挖坑总结

    最近完成了一个TV端的项目,大致包括影视.直播观看,手机投屏操控,内容分类推荐等功能,解决了一些坑,但同时也挖了好多(逃(●'◡'●),在此简单记录一下 1.foucus焦点问题: 简单基础的view ...

  7. 移动端开发(学好移动端这一篇就够啦)

    文章目录 前言 一.移动端的一些概念 (⼀).屏幕相关 1.屏幕⼤⼩: 2.屏幕分辨率 3.屏幕密度(ppi) (⼆).像素相关 2.css像素 3.设备独⽴像素 4.像素⽐ 5.像素之间的关系 (三 ...

  8. 媲美掘金App大图浏览效果,你确定不看一下?

    前言 大图浏览几乎是App必备功能,因为一般列表展示的图片,考虑到性能,速度以及服务器带宽的问题,都是压缩过的缩略图.如果用户想看详细的大图,则需要点击图片进入大图浏览界面. 大图浏览界面怎么做? 粗 ...

  9. 针对Android TV端使用的自定义RecyclerView

    在Android TV中开发, 需要处理焦点的移动, 为了达到醒目的作用一般都会添加焦点移动动画和焦点选中放大效果, 为了方便在TV端使用RecyclerView, 故自定义TvRecyclerVie ...

最新文章

  1. php内核探索方法与资源
  2. 必须掌握的八个DOS命令 [转]
  3. Vue入坑——vue-router如何传递参数
  4. C# in depth 阅读笔记-- 委托
  5. 不止面试题,笔记源码统统都有
  6. 『ACM-算法-二分法』信息竞赛进阶指南--二分法
  7. MongoDB高可用集群搭建
  8. this全面解析, 如何定位this指向,一文总结,再也不怕面试官追问啦
  9. c语言除法的编译,怎样代替除法指令
  10. JAVA 基础8-面向对象3
  11. java循环遍历map集合_Java中遍历Map集合的四种方法
  12. 文件另存为GBK编码格式
  13. 网易数帆、云音乐、Intel、有赞最新大数据实践(PPT下载+视频回放)
  14. 海思HI3536 H265解码处理器简介
  15. matlab 堆图,堆积条形图Matlab
  16. 防止电脑自动锁屏(Windows系统)
  17. DLP3010EVM-LC and DLP2010EVM-LC常见问题答疑
  18. lua 函数 默认值_简明lua教程[转]
  19. sql查询当天交易总额最大的用户信息_如何分析交易记录? 因为后面要分析“每种类型用户的总交易金额”,所以保留左表(用户交易记录表)中的全部用户数据。 【题目】 某商场为了分析用... - 雪球...
  20. 计算机共享账户密码删除吗,win10系统删除已记住的访问共享的账户与密码的详细技巧...

热门文章

  1. 学习D3D游戏编程的资源
  2. java画乌龟(略丑)
  3. 儿时回忆—— 淘气的童年
  4. 我国医疗公平性全球排名倒数第4 体制期待变革
  5. “互联网+”时代 餐饮业如何变局
  6. Ubuntu关机后开机输入密码返回黑框
  7. LayUI下拉框默认选中方法
  8. 一个简单的银行、账户演示程序
  9. ESP32在Adruion IDE上的环境搭建,以及疑难杂症的解决办法(亲测有效)
  10. 做Shopee需要用到ERP系统吗?