最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android實(shí)現(xiàn)TV端大圖瀏覽效果的全過(guò)程

 更新時(shí)間:2023年01月06日 16:21:33   作者:一笑的小酒館  
最近的開(kāi)發(fā)中遇到了個(gè)需求,需要在tv端加載很長(zhǎng)的圖片,發(fā)現(xiàn)網(wǎng)上沒(méi)有相關(guān)的資料,所以跟大家分享下,這篇文章主要給大家介紹了關(guān)于A(yíng)ndroid實(shí)現(xiàn)TV端大圖瀏覽效果的相關(guān)資料,需要的朋友可以參考下

前言

最近TV開(kāi)發(fā)需要加載的圖片很長(zhǎng),大小也很大,并且還不允許壓縮。比如顯示:世界地圖、清明上河圖、微博長(zhǎng)圖、海報(bào)、活動(dòng)照片等。

那么對(duì)于這種需求,該如何做呢?

首先不壓縮,按照原圖尺寸加載,那么屏幕肯定是不夠大的,并且考慮到內(nèi)存的情況,不可能一次性整圖加載到內(nèi)存中,所以肯定是局部加載,那么就需要用到一個(gè)類(lèi):

  • BitmapRegionDecoder

其次,既然屏幕顯示不完,那么最起碼要添加一個(gè)上下左右拖動(dòng)的手勢(shì),讓用戶(hù)可以拖動(dòng)查看。

實(shí)現(xiàn)方式有很多:

1.BitmapRegionDecoder:

分片加載,使用系統(tǒng)BitmapRegionDecoder去加載本地的圖片,調(diào)用bitmapRegionDecoder.decodeRegion解析圖片的矩形區(qū)域,返回bitmap,最終顯示在ImageView上。這種方案需要手動(dòng)處理滑動(dòng)、縮放手勢(shì),網(wǎng)絡(luò)圖片還要處理緩存策略等問(wèn)題。實(shí)現(xiàn)方式比較繁瑣也不是很推薦。

2.SubsamplingScaleImageView

一款封裝 BitmapRegionDecoder的三方庫(kù),已經(jīng)處理了滑動(dòng),縮放手勢(shì)。我們可以考慮選擇這個(gè)庫(kù)來(lái)進(jìn)行加載長(zhǎng)圖,但是官方上的Demo示例加載的長(zhǎng)圖均為本地圖片。這可能并不符合我們的網(wǎng)絡(luò)場(chǎng)景需求,所以對(duì)于網(wǎng)絡(luò)圖片,我們還要考慮不同的加載框架,

3.Glide+SubsamplingScaleImageView混合加載渲染

對(duì)于圖片加載框架,Glide當(dāng)然是首選,我們使用Glide進(jìn)行網(wǎng)絡(luò)圖片的下載和緩存管理,F(xiàn)ileTarget作為橋梁,SubsamplingScaleImageView進(jìn)行本地資源圖片的分片加載,看起來(lái)很靠譜,那么一起來(lái)實(shí)現(xiàn)吧。

4.自定義LongImageView,結(jié)合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() {
            @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;
            }
?
        });
        //響應(yīng)鼠標(biāo)拖拽(手指也可以)
        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());
        //設(shè)置滑動(dòng)速度
        valueAnimator.setDuration(10);
        valueAnimator.start();
    }
?
?
    /**
     * 根據(jù)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());
            //設(shè)置縮放比例
            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();
        }
    }
?
    /**
     * 根據(jù)圖片文件 生成 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;
    }
?
    /**
     * 根據(jù)圖片Id 生成 BitmapRegionDecoder
     *
     * @param resourceId
     */
    @SuppressLint("ResourceType")
    public void setImageResource(int resourceId) {
        InputStream imgStream = getResources().openRawResource(resourceId);
        setImageStream(imgStream);
    }
?
    /**
     * 設(shè)置占位圖
     *
     * @param holderId
     */
    public void setPlaceHolder(int holderId) {
        holderBitmap = BitmapFactory.decodeResource(getResources(), holderId);
    }
?
    /**
     * 滑動(dòng)到具體的位置
     *
     * @param targetY
     */
    private void scrollTo(int targetY) {
        startScroll(targetY);
    }
?
    /**
     * 設(shè)置相對(duì)于當(dāng)前,繼續(xù)滑動(dòng)的距離。小于0 向上滑動(dòng),大于0向下滑動(dòng)
     *
     * @param distance
     */
    private void scrollBy(int distance) {
        startScroll(mTargetY + distance);
    }
?
    /**
     * 設(shè)置每次滑動(dòng)的距離
     *
     * @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());//根據(jù)控件的高度獲取需要在原始圖片上截取的高度
            Log.e(getClass().getSimpleName(), "targetHeight " + targetHeight);
            Log.e(getClass().getSimpleName(), "draw resource "
                    + " " + imgWidth + " " + imgHeight
                    + " " + mTargetY + " " + targetHeight);
            imgBitmap = null;
            if (imgHeight - mTargetY >= targetHeight) {//剩余區(qū)域大于 當(dāng)前控件高度
                imgBitmap = bitmapRegionDecoder.decodeRegion(new Rect(0, mTargetY, imgWidth, mTargetY + targetHeight)
                        , scaleOptions);
            } else {//剩余區(qū)域小于 當(dāng)前控件高度
                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");
    }
?
    /**
     * 圖片高度轉(zhuǎn)為相對(duì)于控件的高度
     *
     * @param imgHeight
     * @return
     */
    private int imageHeight2ViewHeight(int imgHeight) {
        if (this.imgHeight <= 0) {
            return 0;
        }
        return (int) (imgHeight / ((float) getWidth() / imgWidth * imgHeight) * getHeight());
    }
?
    /**
     * 控件高度轉(zhuǎn)為相對(duì)于圖片高度
     *
     * @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();
        //回收資源和釋放內(nèi)存
        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";
?
    @Override
    protected 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>() {
                        @Override
                        public 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.LongImageView
        android: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.實(shí)現(xiàn)的效果如下:

總結(jié)

到此這篇關(guān)于A(yíng)ndroid實(shí)現(xiàn)TV端大圖瀏覽效果的文章就介紹到這了,更多相關(guān)Android TV端大圖瀏覽內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Android設(shè)備上非root的抓包實(shí)現(xiàn)方法(Tcpdump方法)

    Android設(shè)備上非root的抓包實(shí)現(xiàn)方法(Tcpdump方法)

    通常我們?cè)贏(yíng)ndroid應(yīng)用中執(zhí)行某個(gè)命令時(shí)會(huì)使用“Runtime.getRuntime().exec("命令路徑")”這種方式,但是當(dāng)我們執(zhí)行抓包操作時(shí),使用這條命令無(wú)論如何都不行,通過(guò)下面代碼打印結(jié)果發(fā)現(xiàn),該命令一定要在root權(quán)限下才能執(zhí)行,具體實(shí)現(xiàn)思路,請(qǐng)參考本教程
    2016-11-11
  • Android使用xml文件資源定義菜單實(shí)現(xiàn)方法示例

    Android使用xml文件資源定義菜單實(shí)現(xiàn)方法示例

    這篇文章主要介紹了Android使用xml文件資源定義菜單實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了Android資源文件管理及xml配置自定義菜單相關(guān)操作技巧,需要的朋友可以參考下
    2019-03-03
  • Android開(kāi)發(fā)圖片水平旋轉(zhuǎn)180度方法

    Android開(kāi)發(fā)圖片水平旋轉(zhuǎn)180度方法

    今天小編就為大家分享一篇Android開(kāi)發(fā)圖片水平旋轉(zhuǎn)180度方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • Android仿IOS底部彈出對(duì)話(huà)框

    Android仿IOS底部彈出對(duì)話(huà)框

    這篇文章主要為大家詳細(xì)介紹了Android仿IOS底部彈出對(duì)話(huà)框的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • Android TextView(圓?。┻吙蚝捅尘皩?shí)例詳解

    Android TextView(圓弧)邊框和背景實(shí)例詳解

    這篇文章主要介紹了 Android TextView(圓?。┻吙蚝捅尘皩?shí)例詳解的相關(guān)資料,這里提供了實(shí)現(xiàn)代碼和實(shí)現(xiàn)效果圖,需要的朋友可以參考下
    2016-11-11
  • Android編程之手機(jī)壁紙WallPaper設(shè)置方法示例

    Android編程之手機(jī)壁紙WallPaper設(shè)置方法示例

    這篇文章主要介紹了Android編程之手機(jī)壁紙WallPaper設(shè)置方法,結(jié)合實(shí)例形式分析了Android手機(jī)壁紙WallPaper的相關(guān)設(shè)置與使用技巧,需要的朋友可以參考下
    2017-08-08
  • 為Android應(yīng)用增加渠道信息 自動(dòng)化不同渠道的打包過(guò)程的使用詳解

    為Android應(yīng)用增加渠道信息 自動(dòng)化不同渠道的打包過(guò)程的使用詳解

    為了統(tǒng)計(jì)渠道信息,就不得不在程序的某個(gè)地方加入渠道的信息,然后針對(duì)不同的渠道打不同的包。一般可以在Manifest文件中加入渠道編號(hào),而不直接寫(xiě)在代碼中。這樣做的好處是,可以針對(duì)不同渠道,自動(dòng)化去修改Manifest文件中的渠道編號(hào),然后自動(dòng)為該渠道打包
    2013-05-05
  • Android仿百度外賣(mài)自定義下拉刷新效果

    Android仿百度外賣(mài)自定義下拉刷新效果

    大家在使用百度外賣(mài)的訂餐的時(shí)候,會(huì)看到有個(gè)下拉刷新功能非常不錯(cuò),今天小編就通過(guò)代碼給大家介紹android仿百度外賣(mài)自定義下拉刷新,感興趣的朋友一起學(xué)習(xí)吧
    2016-04-04
  • Android自定義View實(shí)現(xiàn)折線(xiàn)圖效果

    Android自定義View實(shí)現(xiàn)折線(xiàn)圖效果

    這篇文章介紹的是一個(gè)折線(xiàn)圖控件,用來(lái)顯示一系列的狀態(tài),并可以進(jìn)行滑動(dòng)。有需要的可以參考借鑒。
    2016-08-08
  • RxJava和Retrofit2的統(tǒng)一處理單個(gè)請(qǐng)求示例詳解

    RxJava和Retrofit2的統(tǒng)一處理單個(gè)請(qǐng)求示例詳解

    這篇文章主要給大家介紹了關(guān)于RxJava和Retrofit2的統(tǒng)一處理單個(gè)請(qǐng)求的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11

最新評(píng)論

望奎县| 麻城市| 嘉鱼县| 临桂县| 定南县| 济源市| 辉南县| 海城市| 金乡县| 黑水县| 客服| 荥阳市| 毕节市| 永州市| 临江市| 灌阳县| 永顺县| 沧源| 夏邑县| 卫辉市| 柞水县| 广元市| 沂水县| 金昌市| 阜平县| 曲阳县| 乳山市| 娄烦县| 阿尔山市| 元朗区| 商都县| 宕昌县| 无极县| 交城县| 淳化县| 额尔古纳市| 城固县| 阿合奇县| 开平市| 从化市| 青神县|