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

android OS & iPadOS の記録。

基礎。サムネイル画像キャッシュ(テスト) 残りのメソッド

2022-01-27 02:11:07 | Android studio 日記

文字数制限のため分割。

実際に組み込んで recyclerView で30枚くらいのサムネイル画像表示テストは、エミュレーター上では満足いく速さだった。
実機テストでどの程度になるか楽しみ。
 

【元画像をサムネイルサイズ付近に縮小処理】
    public Bitmap thumbnaileImage(String fName ) {
        File file = new File( mTargetDir, fName );
        if ( !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, 96, 96 );
            op.inJustDecodeBounds = false;
            istream.close();
            istream = new FileInputStream( file );
            scaleBitmap = BitmapFactory.decodeStream( istream, null, op );
            istream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return scaleBitmap;
    }

    public int calculateInSampleSize(BitmapFactory.Options op, int reqWidth, int reqHeight ) {
        int height = op.outHeight;
        int width = op.outWidth;
        int inSamplSize = 1;

        if ( height > reqHeight || width > reqWidth ) {
            int halfHeight = height / 2;
            int halfWidth = width / 2;

            while ( ( halfHeight / inSamplSize ) >= reqHeight &&
                    ( halfWidth / inSamplSize ) >= reqWidth
            ) {
                inSamplSize *= 2;
            }
        }
        return inSamplSize;
    }


【後始末】
    public void finish() {
        mFileNameList.clear();
        mFileNameList = null;
        mTargetDir = null;
        mMainData = null;
    }
}

MyThumbnailRunnable クラス 終わり

ディレクトリ移動のサムネイル画像表示とブックマーク機能のサムネイル画像表示をキャッシュ利用に変更したら実機テスト。

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

基礎。サムネイル画像キャッシュ(テスト)

2022-01-27 02:00:09 | Android studio 日記

キャッシュフォルダに元画像ファイルのディレクトリ階層を構築して、サムネイル画像を保存する。

recyclerView のアダプタの imageView セットでファイル読み出しかサムネイル作成か。その部分。

    File file = new File( fName ); // フルパス画像ファイル名
    if ( !file.exists() || !file.isFile() ) return null; // 存在を確認
    File cDir = mContext.getCacheDir(); // getCacheDir() が使えないときは、Contextを引っ張る
    String text = cDir.getAbsolutePath() + file.getAbsolutePath(); // スレッドで構築されているはずのフルパス
    File cf = new File( text );
    Bitmap scaleBitmap = null;

    if ( cf.exists() ) { // キャッシュにサムネイルが存在するか確認
        scaleBitmap = BitmapFactory.decodeFile(cf.getAbsolutePath());
    } else {
        // サムネイル作成プログラム
    }


File cf = new File( text );が上手く機能しない場合は、 new File( dir, child  ); で試す。
理由は分からないが、File情報が取得できない事があった。画像ファイルはそこにあるのに。

スレッド実行のタイミングは試行を繰り返して良い場所を探す?
ディレクトリ内のファイルリスト作成場所に組み込んでみた。

    MyThumbnailRunnable mtr = new MyThumbnailRunnable( mMainData, dir );
    Thread thread = new Thread( mtr );
    thread.start();

 

【MyThumbnailRunnable クラス】

public class MyThumbnailRunnable implements Runnable {
    private MyMainData mMainData;
    private File mTargetDir = null;
    private ArrayList< String> mFileNameList = new ArrayList< >();

    public MyThumbnailRunnable( MyMainData mainData, File dir ) {
        mMainData = mainData; // Context 他を受け渡す
        mTargetDir = dir; // 対象ディレクトリ
    }

    @Override
    public void run() {
        if ( mMainData.getMakingThumbnailesDirPath().equals( mTargetDir.getAbsolutePath() ) )
            return;

        mMainData.setMakingThumbnailesDirPath( mTargetDir.getAbsolutePath() ); // フラグ
        fileNameList( mTargetDir );
        createThumbnail();
        mMainData.setMakingThumbnailesDirPath( "" ); // フラグ初期化
        finish(); // run() が終了で消滅するらしいが、念のため明確に参照は切っておく
    }

///// 
MyMainData クラスは、Context とフラグを受け渡すため。スレッド終了時には参照を切る。
引数の dir は、対象ディレクトリ。この中の画像ファイルをサムネイルにしてキャッシュに保存する。
処理が長時間になる場合は、アクティビティー上の参照はしない事。最悪、値をコピーして参照は切る。
スレッドは注意が必要らしい。
/////

【画像ファイル名をリストアップ】
    private void fileNameList( File f ) {
        String fName;
        String[] mList = f.list();
        if ( mList != null ) {
            mFileNameList.clear();
            for (String s : mList) {
                fName = s.toLowerCase(); //小文字に変換;
                if (fName.endsWith(".jpg") || fName.endsWith(".png")) { //画像ファイル選択
                    mFileNameList.add(s);
                }
            }
        }
    }

    public void createThumbnail() {
        if ( mFileNameList == null || mFileNameList.size() == 0 ) return;
        List< String> dirList = disassemblyPath( mTargetDir );
        File mCacheDir = assemblyCachePath( dirList );
        if ( mCacheDir == null ) return;
        makeCacheDirectory( dirList );
        for ( int i = mFileNameList.size()-1; i >= 0 ; i-- ) {
            bitmapToThumbnaile( new File( mCacheDir, mFileNameList.get(i) ) );
        }
    }

【サムネイル画像を保存】
    private void bitmapToThumbnaile( File cfile ) {
        Bitmap bitmap;
        if ( !cfile.exists() ) {
            bitmap = thumbnaileImage( cfile.getName() );
            if ( bitmap != null ) {
                try {
                    FileOutputStream fos = new FileOutputStream( cfile );
                    Bitmap.CompressFormat bcf = getCompressFormat( cfile.getName() );
                    if ( bcf != null ) bitmap.compress( bcf, 100, fos );
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if ( !bitmap.isRecycled() ) bitmap.recycle();
            }
            bitmap = null;
        }
    }

【拡張子から形式を割り出す】
    public Bitmap.CompressFormat getCompressFormat(String name ) {
        String t = name.toLowerCase();
        if ( t.endsWith( ".jpg" ) ) return Bitmap.CompressFormat.JPEG;
        if ( t.endsWith( ".png" ) ) return Bitmap.CompressFormat.PNG;
        return null;
    }


【キャッシュフォルダにディレクトリ階層を構築】
    public void makeCacheDirectory( List< String> list ) {
        int len;
        if ( list == null || ( len = list.size() ) == 0 ) return;
        File cacheDir = mMainData.getContext().getCacheDir();
        for ( int i = list.size()-1; i >= 0; i-- ) {
            cacheDir = new File( cacheDir, list.get(i) );
            if ( !cacheDir.exists() ) {
                boolean b = cacheDir.mkdir();
            }
        }
    }

【フルパスを分解、文字配列作成】
    public List< String> disassemblyPath(File f ) {
        List list = new ArrayList< String>();
        File path = f;
        do {
            list.add(path.getName());
            path = path.getParentFile();
        } while ( path != null && !path.getName().equals(""));

        return list;
    }

【文字配列をキャッシュフォルダ上に組み立て階層を作る】
    public File assemblyCachePath(List< String> list ) {
        int len;
        if ( list == null || ( len = list.size() ) == 0 ) return null;
        File cacheDir = mMainData.getContext().getCacheDir();
        String t;
        for ( len-=1; len >= 0; len-- ) {
            t = list.get(len);
            cacheDir = new File( cacheDir, t );
        }
        return cacheDir;
    }

残りのメソッドは次へ続く。


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

基礎。サムネイル画像キャッシュ処理草案

2022-01-24 22:01:51 | Android studio 日記

相関をデータベースで管理するのもあると思う。が…。
直感的で楽なのでキャッシュフォルダに実際の画像ファイルのディレクトリ階層を再現して、サムネイル画像ファイルを配置するでいいか。

キャッシュフォルダパス + 画像ファイル絶対パス
パス合成は、こんな感じのイメージ。


・画像ファイルの絶対パス取得。
・キャッシュフォルダパスと画像ファイルの絶対パスを合成。
・キャッシュフォルダに合成パスのファイルが存在するか調べる。
・存在しなければ、サムネイルを作成。
・作成データを所定の場所に保存する。

今回はテスト用の内容で実際の組み込みは変更する。

 

現実的なのは、Web掲載記事で説明があったメインスレッドと別スレッドで分けるもの。

メインUIスレッドではキャッシュを確認して、有無で利用か作成か。アダプター内で処理?。
別スレッドでは、あるトリガーでディレクトリ内のサムネイル画像を作成する。

トリガーの説明は見てないから、キャッシュ内になかった時に別スレッドをスタートさせるでいいのかな。

キャッシュフォルダで絶対パスを文字列で作って、new File() でFileデータを取ろうとしたけど、上手くいかないので、new File( dir, child ); を繰り返して階層を掘り下げた。一般のデータフォルダと違うという事かな?


<かなり省略>


    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

 

// 画像ファイル取得

        File f = getExternalStoragePublicDirectory(DIRECTORY_PICTURES); // 画像ファイルがあるディレクトリパス
        File f1 = new File(f.getAbsolutePath() + "/image1/182.jpg"); // 画像のフルパス


// キャッシュ情報取得&作成

        File cacheDir = getCacheDir();

        List<String> list = disassemblyPath( f1 ); // フルパスをディレクトリ名、ファイル名に分解
        List<String> dirList = list.subList( 1,list.size()-1 ); // 画像ファイル名を除く

        makeCacheDirectory( dirList ); // キャッシュフォルダにサムネイル画像ファイルを保存するディレクトリを作成

        Bitmap bitmap;

        File cDir = assemblyCachePath( dirList ); // キャッシュフォルダにディレクトリ階層を再現
        File cf = new File( cDir, f1.getName() ); // サムネイル画像ファイル存在チェック用

// キャッシュフォルダにサムネイル画像の有無。無ければ作って保存

        if ( !cf.exists() ) { // サムネイル画像ファイルが存在していない

            bitmap = thumbnaileImage(f1.getAbsolutePath());

            if ( bitmap != null ) {
                try {
                    File ff = new File(cDir, f1.getName());

                    FileOutputStream fos = new FileOutputStream( ff );
                    Bitmap.CompressFormat bcf = getCompressFormat( f1.getName() ); // 
                    if ( bcf != null )
                        bitmap.compress( bcf, 100, fos ); // bitmap を圧縮して出力

                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

        list.clear();
        list = null;
    }

    public Bitmap.CompressFormat getCompressFormat( String name ) {
        String t = name.toLowerCase();
        if ( t.endsWith( ".jpg" ) ) return Bitmap.CompressFormat.JPEG;
        if ( t.endsWith( ".png" ) ) return Bitmap.CompressFormat.PNG;
        return null;
    }

    public void makeCacheDirectory( List< String> list ) {
        int len;
        if ( list == null || ( len = list.size() ) == 0 ) return;
        File cacheDir = getCacheDir();
        do {
            cacheDir = new File( cacheDir, list.get(--len) );
            if (!cacheDir.exists() && !cacheDir.mkdirs())
                break;
        } while ( len > 0 );
    }

    public List< String> disassemblyPath( File f ) {
        List< String> list = new ArrayList<String>();
        File path = f;
        do {
            list.add(path.getName());
            path = path.getParentFile();
        } while ( path != null && !path.getName().equals(""));

        return list;
    }

    public boolean existsCacheDirectory( List< String> list ) {
        int len;
        if ( list == null || ( len = list.size() ) == 0 ) return false;

        File cacheDir = getCacheDir();
        String t;

        for ( len-=1; len >= 0; len-- ) {
            t = list.get(len);
            cacheDir = new File( cacheDir, t );
        }

        return cacheDir.exists();
    }

    public File assemblyCachePath( List< String> list ) {
        int len;
        if ( list == null || ( len = list.size() ) == 0 ) return null;

        File cacheDir = getCacheDir();
        String t;

        for ( len-=1; len >= 0; len-- ) {
            t = list.get(len);
            cacheDir = new File( cacheDir, t );
        }

        return cacheDir;
    }


    public Bitmap thumbnaileImage(String fName ) {
        File file = new File( fName );
        if ( !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, 144, 144 );
            op.inJustDecodeBounds = false;
            istream.close();
            istream = new FileInputStream( file );
            scaleBitmap = BitmapFactory.decodeStream( istream, null, op );
            istream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return scaleBitmap;
    }

    public int calculateInSampleSize(BitmapFactory.Options op, int reqWidth, int reqHeight ) {
        int height = op.outHeight;
        int width = op.outWidth;
        int inSamplSize = 1;

        if ( height > reqHeight || width > reqWidth ) {
            int halfHeight = height / 2;
            int halfWidth = width / 2;

            while ( ( halfHeight / inSamplSize ) >= reqHeight &&
                    ( halfWidth / inSamplSize ) >= reqWidth
            ) {
                inSamplSize *= 2;
            }
        }

        return inSamplSize;
    }

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

独り言。画像表示の進むと戻る。

2022-01-23 01:42:51 | Android studio 日記

フリンクが面白くて、つい、2画像並び表示の時に上下フリンクで2枚同時に進む、戻る。そして、左右フリンクの時は左右の片側1枚を弾き出して、次か前かの画像1枚を入れる。左右スライドで画像交換…。色々と大変度が増すので左右スライドは無しだ。

楽勝と思って始めたら、条件判断の甘さが災いして思い通りに切り替わらない。とんでも症状…。最初から動作を細分化して専用メソッド多用で何とか思い通りに動いた。

・基準の番号。
・現在表示している画像番号と画像形状の記憶配列。
・進む、戻る。次の画像番号と形状の記憶配列。

1枚2枚の表示モード。形状で1枚2枚の表示条件。2枚表示で片側1枚を交換。
条件分岐で仮配列に番号をセット。あぁ面倒くさい。

 

かなり時間を取られたけど、次はキャッシュだ。全然見当がつかない。
分かりやすいのは、画像のあるフォルダ内にサムネイルフォルダを作って保存して利用する。
ただ、ストレージを圧迫した時にクリーン削除ができない。なのでキャッシュフォルダにサムネイル保存するんだろう。
分かりやすいのはフォルダ階層をキャッシュフォルダに再現する方法かな。
キャッシュフォルダパス+画像ファイル絶対パス
とりあえず、作ってテストか。

検索の方法が悪いのか、サンプルにたどり着かないんだよなぁ…。


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

基礎。OverScroller 【 GestureDetectorCompat 、ImageSwitcher と OverScroller 】

2022-01-13 23:27:19 | Android studio 日記

スクローラーは苦しんだ。今もよく分からない。とりあえず、動く。

スクロールは、スクロール範囲を考える事。
スクリーンサイズが、200x100。
画像が400x200。だったとすると、
画像の(0,0)から(200,100)まで。x= 0~200、y= 0~100。
このx、yの組み合わせで元画像がピッタリとスクリーン上をスクロールできる。

スクリーン
sW = 200
sH = 100

画像
iW = 400
iH = 200

scrollMax
sMaxX = iW - sW
sMaxY = iH - sH

0 <= scrX <= sMaxX
0 <= scrY <= sMaxY
スライドの移動量を計算してスクロール。

画像が小さかったら、scrollMaxは負になる。
その時はスクロールさせない。

onScroll()は、その制限でスクロールさせる。
imageView に padding 設定をするとスクロールオフセットは切り離せる。

scrW=200/ imageW=100 の時、view.setPadding( 50, paddingY, 50, paddingY);
と設定すると、view.scrollTo( 0, y );は画面中央、両端空白あり。
view.setPadding( 0, 0, 0, 0 );(未設定状態のつもり)だと
view.scrollTo( 0, y );は画面左端寄り、右側空白あり。
状況により使い分け。

スクロール前提なら画像をセットした時に padding も設定かな。


面倒なのがフリンク。

デベロッパーのリファレンスが分からん。文献も分からん。
思いつくことを試して結果データを分析して多分?こうかな?

・スクローラーとアニメーターを定義。
・アニメーターを実行。(別スレッドで動いているみたい)
・アニメーター内でスクローラーに計算させて、値を取り出し、view.scrollTo( x, y )にセット。
・スクローラーが完了したら、アニメーターを止める。

みたいな。

mScroller.fling(
    startX,
    startY,
    velocityX,
    velocityY,
    minX, maxX,
    minY, maxY,
    overX,overY
    );


【startX, startY】
タップ位置だと思うよね~。これをDown位置と捉えた時はスクロール値に変換をしないといけない。
最初からスクロールオフセットだと決めれば、後の min、max はスクロールの制限範囲になる。計算もすこぶる楽。
ただし、制限が付く。フルスクリーンフルスクロールのみの設定。
複数のコンテンツが合わさって複数の領域でスクロールさせる場合は使えない。と思う。注意!

自分はフルスクリーンフルスクロールだから楽にいく。Padding設定は必須。
検索での説明は指定領域内の狭い範囲のスクロールばかりだったので、理解に苦しんだ。orz...

別問題も残っている。OverScroll の overX,overY の設定は、画像外にスクロールが飛び出して、正常な位置に戻るという飛び出し距離。
試したが時々、完全に戻らない。調べると本来スクローラーが完了してアニメーターが終了するはずが、スクローラーが完了の信号を受け取らずにアニメーターがうやむやにしている事が時々ある。
謎だ?別ルートで何か動いている…。なのでオーバー仕様は封印。


public class MainActivity extends AppCompatActivity {

    private GestureDetectorCompat mDetector;

    private OverScroller mScroller;
    private ValueAnimator scrollAnimator = ValueAnimator.ofFloat(0,1);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mDetector = new GestureDetectorCompat(this, new MyGestureListener() );

        mScroller = new OverScroller( this,null );
        scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) { // アニメーター実行で呼ばれる
                if ( !mScroller.isFinished() ) {
                    mScroller.computeScrollOffset(); // 計算
                    mImageSwitcher.getCurrentView().scrollTo( mScroller.getCurrX(), mScroller.getCurrY() ); // スクロール実行
                } else
                    scrollAnimator.cancel();
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if ( mDetector.onTouchEvent(event) ) return true;
        return super.onTouchEvent(event);
    }

    class MyGestureListener extends GestureDetector.SimpleOnGestureListener {

        @ Override
        public boolean onDown(MotionEvent event) {
            if ( !mScroller.isFinished() ) mScroller.abortAnimation();
            scrollAnimator.cancel();
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) {
            scrollImage( (ImageView)mImageSwitcher.getCurrentView(), distanceX, distanceY );
            return true;
        }

        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) {
            ImageView view = (ImageView)mImageSwitcher.getCurrentView();
            int w = view.getDrawable().getIntrinsicWidth();
            int h = view.getDrawable().getIntrinsicHeight();

            int startX = view.getScrollX();
            int startY = view.getScrollY();

            mScroller.fling(
                    startX,
                    startY,
                    (int) -velocityX,
                    (int) -velocityY,
                    0, Math.max(0,w-getScreenWidth()),
                    0, Math.max(0,h-getScreenHeight())
            );
            scrollAnimator.start(); // 実行
            return true;
        }
        @Override
        public boolean onSingleTapConfirmed(MotionEvent event) {
            loadImage((int)event.getY());
            return true;
        }
    };

    public void scrollImage ( ImageView view, float x, float y ) {

        int cX = (int) x;
        int cY = (int) y;
        int scrX = view.getScrollX();
        int scrY = view.getScrollY();
        int dx = ( view.getDrawable().getIntrinsicWidth() - getScreenWidth() );
        int dy = ( view.getDrawable().getIntrinsicHeight() - getScreenHeight() );
        if ( dx > 0 ) {
            if ( x > 0 ) {
                if ( scrX == dx ) cX = 0;
                else {
                    int xx = dx - scrX;
                    if ( x >= xx ) cX = xx;
                }
            } else if ( x < 0 ){
                if ( scrX == 0 ) cX = 0;
                else if ( scrX > 0 && -scrX >= x ) cX = -scrX;
            } else cX = 0;
        } else
            cX = 0;

        if ( dy > 0 ) {
            if ( y > 0 ) {
                if ( scrY == dy ) cY = 0;
                else {
                    int yy = dy - scrY;
                    if ( y >= yy ) cY = yy;
                }
            } else if ( y < 0 ){
                if ( scrY == 0 ) cY = 0;
                else if ( scrY > 0 && -scrY >= y ) cY = -scrY;
            } else cY = 0;
        } else
            cY = 0;

        view.scrollBy( cX, cY );
    }

}

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする