android tv 长布局,Android TV控件--长图片展示控件

如何在TV中展示一张长长的图片,考虑到内存问题,肯定不能把图片一次性加载到内存中,这个时候就要用到BitmapRegionDecoder,借助这个类可以实现只截取图片中需要的区域生成Bitmap来展示。BitmapRegionDecoder是实现这个UI控件的基础,接下来的实现过程都是围绕它来完成的。

最终效果演示:

e7160cbd18c1?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

VerticalScrollImageView.gif

BitmapRegionDecoder的基础用法

BitmapRegionDecoder是通过 newInstance方法来实例化的。

public static BitmapRegionDecoder newInstance(InputStream is,

boolean isShareable) throws IOException {

if (is instanceof AssetManager.AssetInputStream) {

return nativeNewInstance(

((AssetManager.AssetInputStream) is).getNativeAsset(),

isShareable);

} else {

// pass some temp storage down to the native code. 1024 is made up,

// but should be large enough to avoid too many small calls back

// into is.read(...).

byte [] tempStorage = new byte[16 * 1024];

return nativeNewInstance(is, tempStorage, isShareable);

}

}

还有与之类似的几个多态

public static BitmapRegionDecoder newInstance(FileDescriptor fd, 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

截取部分区域生成Bitmap的方法

/**

* Decodes a rectangle region in the image specified by rect.

*

* @param rect The rectangle that specified the region to be decode.

* @param options null-ok; Options that control downsampling.

* inPurgeable is not supported.

* @return The decoded bitmap, or null if the image data could not be

* decoded.

*/

public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {

synchronized (mNativeLock) {

checkRecycled("decodeRegion called on recycled region decoder");

if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= getWidth()

|| rect.top >= getHeight())

throw new IllegalArgumentException("rectangle is outside the image");

return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,

rect.right - rect.left, rect.bottom - rect.top, options);

}

}

两个参数,rect是截取图片的目标区域,options可用配置生成的Bitmap

长图片展示控件代码实现

新建一个控件类VerticalScrollImageView继承自View,覆写其onDraw方法,在此方法中实现绘制图片

@Override

protected 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");

}

最最关键的代码就是这里了。如果要实现图片的滑动效果,只需要一个简单的属性动画来逐渐修改mTargetY的值即可

/**

* targetY 为滚动的目标位置

*/

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() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

mTargetY = (int) animation.getAnimatedValue();

invalidate();

}

});

valueAnimator.addListener(new Animator.AnimatorListener() {

@Override

public 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(250);

valueAnimator.start();

}

再接着只需要监听遥控器的按键,完成滑动即可

private void init() {

//响应遥控器事件

setOnKeyListener(new OnKeyListener() {

@Override

public 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() {

@Override

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

}

});

}

/**

* 滑动到具体的位置

* @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;

}

完整代码

package com.hpplay.happyott.view;

import android.animation.Animator;

import android.animation.ValueAnimator;

import android.content.Context;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.BitmapRegionDecoder;

import android.graphics.Canvas;

import android.graphics.Paint;

import android.graphics.Rect;

import android.support.annotation.Nullable;

import android.util.AttributeSet;

import android.util.Log;

import android.view.KeyEvent;

import android.view.MotionEvent;

import android.view.View;

import android.view.animation.LinearInterpolator;

import java.io.File;

import java.io.InputStream;

/**

* Created by DON on 2017/6/19.

*/

public class VerticalScrollImageView extends View {

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 VerticalScrollImageView(Context context) {

super(context);

init();

}

public VerticalScrollImageView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

init();

}

public VerticalScrollImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init();

}

private void init() {

//相应遥控器事件

setOnKeyListener(new OnKeyListener() {

@Override

public 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() {

@Override

public 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() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

mTargetY = (int) animation.getAnimatedValue();

invalidate();

}

});

valueAnimator.addListener(new Animator.AnimatorListener() {

@Override

public 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(250);

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

*/

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;

}

@Override

protected 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);

}

@Override

protected void onDetachedFromWindow() {

super.onDetachedFromWindow();

imgBitmap = null;

holderBitmap = null;

System.gc();

}

}

毕其功于一类,做到简单好用,不依赖其他文件

用法

布局文件

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:id="@+id/scrollImageView"

android:layout_width="match_parent"

android:layout_height="match_parent"/>

VerticalScrollImageView mImageView = (VerticalScrollImageView) view.findViewById(R.id.scrollImageView);

mImageView.setScrollDistance((int) ((float) Utils.getScreenHeight(getActivity()) / 3 * 2));

mImageView.setFocusable(true);

mImageView.setFocusableInTouchMode(true);

mImageView.requestFocus();

Glide.with(getActivity())

.load(mImgUrl)

.downloadOnly(new SimpleTarget() {

@Override

public void onResourceReady(File resource, GlideAnimation super File> glideAnimation) {

mImageView.setImageFile(resource);

}

@Override

public void onLoadFailed(Exception e, Drawable errorDrawable) {

super.onLoadFailed(e, errorDrawable);

}

});

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值