タブレット用プログラムの書き止め

android OS & iPadOS の記録。

基礎。RecyclerView サムネイル画像の設定。反応速度を考察。(前編)

2022-02-18 20:36:10 | Android studio 日記

前回の作業ディレクトリ、画像ファイルだけのRecyclerView 使用は、1つのディレクトリに数十枚の画像であれば、十分の処理速度でストレスは感じられない。
別スレッドでのサムネイル作成では、1枚完了ごとにRecyclerViewのサムネイル画像を更新するから作業が止まって見える事はない。

今回は、1つのディレクトリに子ディレクトリとファイルを混在したサムネイルを表示するRecyclerViewの構築。
ファイル部分は前回のを利用。ディレクトリとファイルのリストを別に用意して、先にディレクトリ、後にファイルのリストで合成。
本体リストと別に識別用リストの2本を用意。

ディレクトリのアイコンはリソースからの設定が一般的だと思う。しかし、ディレクトリの中に画像が有ったら、それを使いたい。
これが意外と面倒で時間がかかる。だいぶ前のキャッシュディレクトリにディレクトリの構造をそのまま構築するのは利用価値が大きく有用。ただ、ストレージの領域を浪費する。
作業ディレクトリで1つのディレクトリのキャッシュ情報を使い捨てするともったいない。使用頻度で再利用とかできたらいいんだけれど。
ディレクトリのアイコンだけも特別に保存する方法を考えないと時間短縮は望めなさそう。今後の課題。

とりあえず、ディレクトリ内の画像をアイコンに使用する草案。


RecyclerViewのアダプターとRunnableで他は省略。

public class MyDirectoryListAdapter extends RecyclerView.Adapter< MyDirectoryListAdapter.ViewHolder> {

    public static class MyRunnableData { // Runnable データ引き渡し用クラス
        private File mTargetDir = null;
        private File mCacheDir = null;
        private List< String> mListType = null;
        private List< String> mListName = null;
        private RecyclerView myRecyclerView = null;

        MyRunnableData( File tDir, File cDir, List< String> lt, List< String> ln ) {
            mTargetDir = tDir;
            mCacheDir = cDir;
            mListType = new ArrayList<>(lt);
            mListName = new ArrayList<>(ln);
        }
        public File getTargetDir() { return mTargetDir; }
        public File getCacheDir() { return mCacheDir; }
        public List< String> getListType() { return mListType; }
        public List< String> getListName() { return mListName; }

        public void setNotifyItemChanged( int val ) {
            if ( myRecyclerView != null ) {
                myRecyclerView.post(new Runnable() {
                    @Override
                    public void run() {
                        MyDirectoryListAdapter adapter = (MyDirectoryListAdapter) myRecyclerView.getAdapter();
                        if (adapter != null) adapter.notifyItemChanged(val);
                    }
                });
            }
        }

        public void setRecyclerView(RecyclerView myRecyclerView) {
            this.myRecyclerView = myRecyclerView;
        }

        public void clear() {
            mListType.clear();
            mListType = null;
            mListName.clear();
            mListName = null;
        }
    }
    private MyRunnableData myRunnableData = null;

// アダプター本体

    private MyMainData mMainData = null;
    private LayoutInflater myInflater = null;
    private RecyclerView myRecyclerView = null;
    private View.OnClickListener myListener = null;

    private List< String> listType = null;
    private List< String> listName = null;
    private File myCacheDir = null;

    MyDirectoryListAdapter( MyMainData mainData ) {
        mMainData = mainData;
        myInflater = LayoutInflater.from( mainData.getContext() );
        myRecyclerView = null;
        listName = new ArrayList<>();
        listType = new ArrayList<>();

        setTempBufferDir( new File( mainData.getContext().getCacheDir(), "tempThumb" ) );
    }

  省略

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

        Bitmap bitmap = loadImage( position );
        if (bitmap != null)
            holder.imageView.setImageBitmap(bitmap);

        holder.textView.setText( listName.get(position) );
        holder.linearLayout.setId( holder.getAdapterPosition() );
        holder.linearLayout.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myListener.onClick(v);
            }
        });
    }

    public Bitmap loadImage( int position ) {
        Bitmap bitmap = null;
        if ( listType.get(position).equals("D") ) { // 識別:ディレクトリ
            String name = listName.get(position) + ".jpg";
            File cacheFile = new File( myCacheDir, name );
            if (cacheFile.exists()) {
                bitmap = BitmapFactory.decodeFile( cacheFile.getAbsolutePath() );
            }
            return ( bitmap != null )? bitmap:
                    BitmapFactory.decodeResource(mMainData.getContext().getResources(), R.drawable.folder );
        }

        if ( listType.get(position).equals("F") ) { // 識別:ファイル
            File file = new File( myCacheDir, listName.get(position) );
            if (file.exists())
                bitmap = BitmapFactory.decodeFile( file.getAbsolutePath() );

            return ( bitmap != null )? bitmap:
                    BitmapFactory.decodeResource(mMainData.getContext().getResources(), R.drawable.not_find);
        }
        return null;
    }

    public void addAll( MyDirData dirData ) {
        clear();
        listType.addAll( dirData.getDirTypeListForAdapter() );
        listName.addAll( dirData.getDirNameListForAdapter() );

        if ( myRunnableData != null ) myRunnableData.clear();

        myRunnableData = new MyRunnableData( dirData.getTargetDir(), myCacheDir, listType, listName );
        myRunnableData.setRecyclerView(myRecyclerView);

        new Thread( new MyDirectoryIconRunnable( myRunnableData ) ).start();
        notifyDataSetChanged();
    }

    public void setNotifyItemChanged( int position ) {
        if ( position < 0 || position >= listName.size() ) return;
        notifyItemChanged( position );
    }
    public void setTempBufferDir( File dir ) {
        if ( !dir.exists() && !dir.mkdirs() ) return;
        myCacheDir = dir;
    }

  省略

}


基礎。RecyclerView サムネイル画像の設定。反応速度を考察。(後編)

2022-02-18 20:31:16 | Android studio 日記

// Runnable 本体

MyDirectoryListAdapter.MyRunnableDataクラスをデータ受け渡しに使っている。
全てが有効に使えるか分からない。テストを繰り返して使える事を探す。

public class MyDirectoryIconRunnable implements Runnable {
    private File mTargetDir = null;
    private File mCacheDir = null;
    private List< mString> mItemeNames = null;
    private List< String> mItemType = null;
    private static final int DIR_SAVE_SIZE = 96; // ディレクトリとファイルのサイズを変えている
    private static final int FILE_SAVE_SIZE = 160;
    private int saveSize;
    private MyDirectoryListAdapter.MyRunnableData mDirectoryListAdapter = null;

    MyDirectoryIconRunnable( MyDirectoryListAdapter.MyRunnableData rData ) {
        mTargetDir = rData.getTargetDir();
        mItemType = rData.getListType();
        mItemeNames = rData.getListName();
        mCacheDir = rData.getCacheDir();
        mDirectoryListAdapter = rData;
    }

    @Override
    public void run() {
        if (mCacheDir == null || !mCacheDir.exists() || !mCacheDir.isDirectory()) {
            finish();
            return;
        }

        File[] files =  mCacheDir.listFiles(); // 作業ディレクトリ内のファイル削除
        if ( files != null && files.length != 0 ) {
            boolean b;
            for ( File file: files ) {
                b = file.delete();
            }
        }

        for (int i = 0; i < mItemeNames.size(); i++) {
            switch ( mItemType.get(i) ) {
                case "D":
                    dirThumbnaile(i);
                    break;
                case "F":
                    fileThumbnaile(i);
                    break;
                default:
            }
            mDirectoryListAdapter.setNotifyItemChanged(i); // 何かの加減で問題が発生するかも
        }

        finish();
    }

    private void dirThumbnaile(int index) {
        String name = mItemeNames.get(index) + ".jpg";
        File cacheFile = new File( mCacheDir, name );
        if (cacheFile.exists()) return; // キャッシュファイルが有る

        File childDir = new File( mTargetDir, mItemeNames.get(index) );
        MyDirectory_FileCheck childDirData = new MyDirectory_FileCheck( childDir );// 画像ファイル1枚だけ取り出す

        File imageFile = childDirData.getImageFile(); // 取り出した1枚のFile
        if ( imageFile == null )
            makeThumbnailToCache( null, cacheFile ); // dummy を作る
        else {
            saveSize = DIR_SAVE_SIZE;
            makeThumbnailToCache(imageFile, cacheFile);// アイコン用サムネイル画像の変換と保存
        }
    }

    private void fileThumbnaile(int index ) {
        String name = mItemeNames.get(index);
        File cacheFile = new File( mCacheDir, name ); // create cache file
        if ( cacheFile.exists() ) return;

        File souDir = new File( mTargetDir, name );
        saveSize = FILE_SAVE_SIZE;
        makeThumbnailToCache( souDir, cacheFile ); // 子ディレクトリ内の画像ファイル & キャッシュファイル
    }

    private void makeThumbnailToCache( File souFile, File cacheFile ) {
        if ( souFile == null ) {
            try {
                boolean flag = cacheFile.createNewFile();
            } catch (Exception ignored) {
            }
            return;
        }
        Bitmap bitmap = thumbnaileImage( souFile ); // ソース画像ファイルからサムネイル画像を作成
        if ( bitmap != null && cacheFile != null ) {// サムネイル画像をキャッシュに保存
            try {
                FileOutputStream fos = new FileOutputStream( cacheFile );
                bitmap.compress( Bitmap.CompressFormat.JPEG, 100, fos );
                fos.close();
            } catch (IOException e) {
                return;
            }
            if ( !bitmap.isRecycled() ) bitmap.recycle();
        }
    }

    public Bitmap thumbnaileImage( File file ) {
        if ( file == null || !file.exists() || !file.isFile() ) return null;

        Bitmap scaleBitmap = null;
        try {
            BitmapFactory.Options op = new BitmapFactory.Options();
            op.inJustDecodeBounds = true;

            InputStream istream = new FileInputStream( file );
            BitmapFactory.decodeStream( istream, null, op );

            op.inSampleSize = calculateInSampleSize( op, saveSize, saveSize );
            op.inJustDecodeBounds = false;
            istream.close();
            istream = new FileInputStream( file );
            scaleBitmap = BitmapFactory.decodeStream( istream, null, op );
            istream.close();
            if ( scaleBitmap == null ) return null;

            Matrix matrix = new Matrix();
            float mag = calcMag( scaleBitmap.getWidth(), scaleBitmap.getHeight(),saveSize, saveSize );
            matrix.postScale( mag, mag );
            scaleBitmap = Bitmap.createBitmap( scaleBitmap, 0, 0, scaleBitmap.getWidth(), scaleBitmap.getHeight(), matrix, true);
        } catch (IOException e) {
            return null;
        }
        return scaleBitmap;
    }

    public float calcMag( int souX, int souY, int desX, int desY ) {
       省略

    }

    public int calculateInSampleSize(BitmapFactory.Options op, int reqWidth, int reqHeight ) {
        省略

    }

    public void finish() {
        mItemType.clear();
        mItemType = null;
        mItemeNames.clear();
        mItemeNames = null;
        mTargetDir = null;
        mCacheDir = null;
        mDirectoryListAdapter = null;
    }
}