美文网首页
Android TV控件--长图片展示控件

Android TV控件--长图片展示控件

作者: DON_1007 | 来源:发表于2017-07-22 16:59 被阅读0次

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

    最终效果演示:


    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();
        }
    }
    

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

    • 用法

    布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <com.hpplay.happyott.view.VerticalScrollImageView
            android:id="@+id/scrollImageView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
    </LinearLayout>
    
            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<File>() {
                        @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);
                        }
                    });
    

    相关文章

      网友评论

          本文标题:Android TV控件--长图片展示控件

          本文链接:https://www.haomeiwen.com/subject/fgxvcxtx.html