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

android OS & iPadOS の記録。

基礎。フラグメントを使わず少数ページの利用を考える。

2021-10-26 14:16:02 | Android studio 日記

色々と試験を繰り返し、出来そうな事をまとめる。
【activity_main.xml】でページごとにLayoutでまとめて、対応したクラスを作る。共通データはメインデータクラスでまとめる。優先度の高いページを下へ記述する。切り替えはページのOn()、Off()で行う。表示されているページのonTouchEvent()を振り分けて実行する。

 

【activity_main.xml】

< ?xml version="1.0" encoding="utf-8"?>
< androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- main image view - - >
    <ViewSwitcher
        android:id="@+id/viewSwitcher"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />


    <!-- thumbnail page -->
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/thumbnail_page"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent" >

・・・省略・・・

    </androidx.constraintlayout.widget.ConstraintLayout>

    <!-- directory page -->
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/directory_page"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone">

・・・省略・・・

    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

 

【MainActivity.java】

public class MainActivity extends AppCompatActivity {

    private MyMainData mainData;
    private MyMainPage mainPage;
    private MyThumbnailPage thumbnailPage;
    private MyDirectoryPage directoryPage;
・・・省略・・・

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

        //// 変数初期化
        mainData = new MyMainData( this );
        mainPage = new MyMainPage( mainData );
        thumbnailPage = new MyThumbnailPage( mainData );
        directoryPage = new MyDirectoryPage( mainData );

        thumbnailPage.setPageLayout( (ConstraintLayout)findViewById( R.id.thumbnail_page) );
・・・省略・・・

        directoryPage.setPageLayout( (ConstraintLayout)findViewById( R.id.directory_page) );
・・・省略・・・

        initThumbnailPage();
        initDirectoryPage();
    }

    private void initThumbnailPage() {
・・・省略・・・
    }
    private void initDirectoryPage() {
・・・省略・・・
    }
・・・省略・・・


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if ( directoryPage.isDisplay() ) {
            if ( directoryOnTouchEvent(event) ) return true;
        } else if ( thumbnailPage.isDisplay() ) {
            if ( thumbnailOnTouchEvent(event) ) return true;
        } else {
            if ( imageOnTouchEvent(event) ) return true;
        }
        return super.onTouchEvent(event);
    }

    private boolean directoryOnTouchEvent(MotionEvent event) {
        switch ( event.getActionMasked() ) {
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                break;
        }
        return false;
    }

    private boolean thumbnailOnTouchEvent(MotionEvent event) {
        switch ( event.getActionMasked() ) {
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                break;
        }
        return false;
    }

    private boolean imageOnTouchEvent(MotionEvent event) {

        switch ( event.getActionMasked() ) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                return true;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                break;
        }
        return false;
    }

・・・省略・・・
}

【MyMainData.java】

public class MyMainData {
    private Context mContext;
    private MyDirectory mFolderItemList;
・・・省略・・・

    MyMainData( Context context ) {
        mContext = context;
    }
    public Context getContext() {
        return mContext;
    }
・・・省略・・・
}


【MyMainPage.java】

public class MyMainPage {
    private MyMainData mainData;
・・・省略・・・

    MyMainPage( MyMainData mainData ) {
        this.mainData = mainData;
    }
・・・省略・・・
}


【MyThumbnailPage.java】

public class MyThumbnailPage {
    private MyMainData mainData;
    private ConstraintLayout mPageLayout;
・・・省略・・・

    MyThumbnailPage(MyMainData mainData ) {
        this.mainData = mainData;
    }

    public ConstraintLayout getPageLayout() {
        return mPageLayout;
    }
    public void setPageLayout( ConstraintLayout constraintLayout ) {
        mPageLayout = constraintLayout;
    }
    public void off() {
        mPageLayout.setVisibility(View.GONE);
    }
    public void on() {
        mPageLayout.setVisibility(View.VISIBLE);
    }
    public boolean isDisplay()
    {
        return  ( mPageLayout.getVisibility() == View.VISIBLE );
    }

・・・省略・・・
}


【MyDirectoryPage.java】

public class MyDirectoryPage {
    private MyMainData mainData;
    private ConstraintLayout mPageLayout;
・・・省略・・・

    MyDirectoryPage(MyMainData mainData ) {
        this.mainData = mainData;
    }
    public ConstraintLayout getPageLayout() {
    }
    public void setPageLayout( ConstraintLayout constraintLayout ) {
    }
    public void on() {
    }
    public void off() {
    }
    public boolean isDisplay()
    {
    }

・・・省略・・・
}

 

試験的にはページ切り替えも問題ない。メモリ消費を考慮しつつ、隠れたページの切り離せるオブジェクトは切り離した方がいいかもしれない。


基礎。RecyclerView、サムネイルのオフセットスタート&エンド。

2021-10-18 01:55:24 | Android studio 日記

とりあえず、最初と最後の余白を…どうとかという記事を思い出して細工。

【MyAdapter.java】

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

        if ( iImagePaths != null ) {
            Bitmap bitmap = loadImage(iImagePaths.get(position));
            if (bitmap != null) {
                holder.imageView.setImageBitmap(bitmap);
            }
        }
        if ( iNames != null ) {
            holder.textView.setText(iNames.get(position));
        }

        if ( holder.getAdapterPosition() == 0 ) { // 最初の時
            int le = holder.linearLayout.getPaddingLeft(); // 元々の余白を保存
            int to = holder.linearLayout.getPaddingTop();
            int bo = holder.linearLayout.getPaddingBottom();
            holder.linearLayout.setPadding( le,to,500,bo ); // 余白を再設定
        }
        if ( holder.getAdapterPosition() == ( iImagePaths.size() - 1 ) ) { // 最後の時
            int ri = holder.linearLayout.getPaddingRight();
            int to = holder.linearLayout.getPaddingTop();
            int bo = holder.linearLayout.getPaddingBottom();
            holder.linearLayout.setPadding( 500,to,ri,bo );
        }

        holder.linearLayout.setId( holder.getAdapterPosition() );
        holder.linearLayout.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.onClick(v);
            }
        });
    }

 

これが正しいのか分からないが、デバイス上でパフォーマンスに問題なければOKかな…。

 


基礎。RecyclerView の続き。直線配置、単純クリック処理。

2021-10-17 22:03:03 | Android studio 日記

RecyclerView で複数選択など複雑操作するのに recyclerview-selection を利用します。理解が大変。
で、色々と調べたところ、onClickListener() を紐付けるというか縫い付けるというか…。

単純にonClickListener() を組み付けるとアダプタークラスの中でしか、onClick() が呼ばれない。
MainActivity で onClick() の処理をさせる為には、

MainActivity で onClick() 処理をさせるonClickListenerを用意する。
アダプタークラスにMainActivity で用意したonClickListener を保存するListenerを準備。
MainActivityのListenerを受け取るメソッドを作り、受け取ったListenerを保存。
ViewHolder にデータの他にLayoutViewを設定。
そのLayoutViewに setOnClickListener() で onClick() を組み付ける。
onClick() の中で、保存してある MainActivity の Listener.onClick() を実行。

これでアダプタークラスで発生するクリックイベントがメインアクティビティーに伝搬した。
自力では解決できなかった。imageview にMainActivity で用意したonClickListenerを組み付けたりしたけど上手く動かず、最小セットのLinearLayout にonClickListenerを設定するのは分からなかった。

 

【MainActivity.java】抜粋

        RecyclerView.LayoutManager rLayoutManager = new LinearLayoutManager(this, RecyclerView.HORIZONTAL, true );
        mRecyclerView = findViewById( R.id.recyclerview );
        mRecyclerView.setLayoutManager( rLayoutManager );

        List< String> aPaths = mFolderItemList.getAbsolutePathList();
        List< String> fNames = mFolderItemList.getNameList();

        MyAdapter adapter = new MyAdapter(this, aPaths, fNames );
        mRecyclerView.setAdapter( adapter );
        adapter.setOnItemClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int index = v.getId(); // バインド時にposition と Id は同期している
                String path = aPaths.get( index ); // フルパス名を取得できる
                // 処理を書く
             }
        });

 

【MyAdapter.java】

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

    private Context mContext;
    private LayoutInflater mInflater;
    private RecyclerView mRecyclerView;
    private final List< String> iImagePaths;
    private final List< String> iNames;

    private View.OnClickListener listener; // MainActivityのOnClickListenerを保存

    public MyAdapter( Context context) {
        mContext = context;
        mInflater = LayoutInflater.from( context );
        iImagePaths = new ArrayList<>();
        iNames = new ArrayList<>();
    }
    public MyAdapter( Context context, List< String> itemImagePaths, List< String> itemNames ) {
        mContext = context;
        mInflater = LayoutInflater.from( context );
        iImagePaths = itemImagePaths;
        iNames = itemNames;
    }
    @Override
    public void onAttachedToRecyclerView( @NonNull RecyclerView recyclerView ) {
        super.onAttachedToRecyclerView( recyclerView );
        mRecyclerView = recyclerView;
    }
    @Override
    public void onDetachedFromRecyclerView( @NonNull RecyclerView recyclerView ) {
        mRecyclerView = null;
        super.onDetachedFromRecyclerView( recyclerView );
    }
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = mInflater.inflate( R.layout.recyclerview, parent, false );
        return new ViewHolder( view );
    }
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        if ( iImagePaths != null ) {
            Bitmap bitmap = loadImage(iImagePaths.get(position));
            if (bitmap != null) holder.imageView.setImageBitmap(bitmap);
        }
        if ( iNames != null ) holder.textView.setText(iNames.get(position));

        holder.linearLayout.setId( holder.getAdapterPosition() ); // view.getId() で利用
        holder.linearLayout.setOnClickListener( new View.OnClickListener() { // バインド時に設定
            @Override
            public void onClick(View v) {
                listener.onClick(v); // MainActivity の onClick() を呼ぶ
            }
        });
    }
    public void setOnItemClickListener( View.OnClickListener listener ) { // MainActivity と繋ぐパイプ
        this.listener = listener;
    }

    public Bitmap loadImage( String fName ) {
        File file = new File( fName );
        if ( !file.exists() || !file.isFile() ) return null;
        Bitmap scaleBitmap = null;
        try {
            InputStream istream = new FileInputStream( file );
            Bitmap bitmap = BitmapFactory.decodeStream(istream);
            float mag = 160f / (float) bitmap.getHeight();
            int width = (int) ((float) bitmap.getWidth() * mag );
            int height = (int) ((float) bitmap.getHeight() * mag );
            scaleBitmap = Bitmap.createScaledBitmap( bitmap, width, height, true);
            bitmap.recycle();
            istream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return scaleBitmap;
    }
    @Override
    public int getItemCount() {
        return iNames.size();
    }
    public String getAbsolutePath( int position ) {
        if ( position < 0 || position >= iImagePaths.size() ) return null;
        return iImagePaths.get( position );
    }
    public void addAll( List< String> itemImagePaths, List< String> itemNames ) {
        this.iImagePaths.addAll( itemImagePaths );
        this.iNames.addAll( itemNames );
        notifyDataSetChanged();
    }
    public void clear() {
        iImagePaths.clear();
        iNames.clear();
        listener = null;
        mRecyclerView = null;
    }
    static class ViewHolder extends RecyclerView.ViewHolder {
        final LinearLayout linearLayout; // 最小セットごとに View.setOnClickListener() を設定するため
        final ImageView imageView;
        final TextView textView;

        ViewHolder(View v) {
            super(v);
            linearLayout = ( LinearLayout ) v.findViewById( R.id.Linear_layout );
            imageView = v.findViewById( R.id.imageview );
            textView = v.findViewById( R.id.textview );
        }
    }
}

【recyclerview.xml】

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/Linear_layout" // 追加
    略

</LinearLayout>

 

次は、RecyclerViewのスクロール開始オフセットについてかな。中央からスタートさせたい。

 


基礎。サムネイル表示、メモリ消費を考える。RecyclerView

2021-10-16 15:49:43 | Android studio 日記

ファイル整理が滞るとディレクトリに画像千枚を超えてしまうことも。それをサムネイル表示処理をしてしまうとメモリ不足でダウンする。

画面上に配置する個数を2画面分用意して、ViewSwitcherで切り替えて分割処理をすれば、メモリ節約になる。ただ、デザイン的に気持ちがすっきりしない。メモリ効率で検索して調べると LinearLayoutやGridLayout などをメモリ効率的にまとめたものが RecyclerView のようだ。viewに割り当てられたものを上手に使い回すらしい。

なのでRecyclerViewのLinearLayoutManagerを試してみる。

手順は、
① activity_main.xmlにRecycleViewを設置。
② recyclerview.xmlへ最小単位のレイアウトを構築。
③ MyAdapter.java アダプターを作る。
④ MainActivity.java に実体を作る。

【activity_main.xml】

        < androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:scrollbars="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

【recyclerview.xml】

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/Linear_layout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="?android:attr/selectableItemBackground"
    android:baselineAligned="false" >

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/textview"
        android:layout_gravity="center_horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

 

【MyAdapter.java】

public class MyAdapter extends RecyclerView.Adapter< MyAdapter.ViewHolder >
       implements View.OnClickListener  {

    private Context mContext;
    private LayoutInflater mInflater;
    private RecyclerView mRecyclerView;

    private final List iImagePaths;
    private final List iNames;

    public MyAdapter( Context context ) {
        mContext = context;
        mInflater = LayoutInflater.from( context );
        iImagePaths = new ArrayList<>();
        iNames = new ArrayList<>();
    }

    public MyAdapter( Context context, List< String > itemImagePaths, List< String > itemNames ) {
        mContext = context;
        mInflater = LayoutInflater.from( context );
        iImagePaths = itemImagePaths;
        iNames = itemNames;
    }

    @Override
    public void onAttachedToRecyclerView( @NonNull RecyclerView recyclerView ) {
        super.onAttachedToRecyclerView( recyclerView );
        mRecyclerView = recyclerView;
    }
    @Override
    public void onDetachedFromRecyclerView( @NonNull RecyclerView recyclerView ) {
        mRecyclerView = null;
        super.onDetachedFromRecyclerView( recyclerView );
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = mInflater.inflate( R.layout.recyclerview, parent, false );
        view.setOnClickListener( (View.OnClickListener) this );

        return new ViewHolder( view );
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        if ( iImagePaths != null ) {
            Bitmap bitmap = loadImage(iImagePaths.get(position));
            if (bitmap != null) holder.imageView.setImageBitmap(bitmap);
        }
        if ( iNames != null ) holder.textView.setText(iNames.get(position));
    }
    public Bitmap loadImage( String fName ) {
        File file = new File( fName );
        if ( !file.exists() || !file.isFile() ) return null;
        Bitmap scaleBitmap = null;
        try {
            InputStream istream = new FileInputStream( file );
            Bitmap bitmap = BitmapFactory.decodeStream(istream);

            float mag = 160f / (float) bitmap.getHeight(); // サムネイル縦160ピクセル

            int width = (int) ((float) bitmap.getWidth() * mag );
            int height = (int) ((float) bitmap.getHeight() * mag );
            scaleBitmap = Bitmap.createScaledBitmap( bitmap, width, height, true);
            bitmap.recycle();
            istream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return scaleBitmap;
    }

    @Override
    public int getItemCount() {
        return iNames.size();
    }

    @Override
    public void onClick(View v) {

    }

    public String getAbsolutePath( int position ) {

        return iImagePaths.get( position );
    }

    public void addAll( List< String > itemImagePaths, List< String > itemNames ) {
        this.iImagePaths.addAll( itemImagePaths );
        this.iNames.addAll( itemNames );
        notifyDataSetChanged();
    }
    public void clear() {
        iImagePaths.clear();
        iNames.clear();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        ImageView imageView;
        TextView textView;
        ViewHolder(View v) {
            super(v);
            imageView = v.findViewById( R.id.imageview );
            textView = v.findViewById( R.id.textview );
        }
    }
}

【MainActivity.java】

    private MyDirectory mFolderItemList = null;
    private RecyclerView mRecyclerView = null;

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

        File dir = new File( getFilesDir(),"image1" );
        mFolderItemList = new MyDirectory( dir );

        mRecyclerView = findViewById( R.id.recyclerview );
        mRecyclerView.setHasFixedSize( true );

        RecyclerView.LayoutManager rLayoutManager =new LinearLayoutManager(this, RecyclerView.HORIZONTAL, true );
        mRecyclerView.setLayoutManager( rLayoutManager );

        List aPaths = mFolderItemList.getAbsolutePathList();
        List fNames = mFolderItemList.getNameList();

        MyAdapter adapter = new MyAdapter( this, paths, fNames );
        mRecyclerView.setAdapter( adapter );

    }

サムネイル表示はOK。ここまでは簡単。
クリックで本画像を開くのは少し面倒。クリック処理は次回へ続く。

 


基礎。レイアウトとフラグメント、別アクティビティー。小規模ならレイアウトオンリー。

2021-10-11 12:16:59 | Android studio 日記

PCのウィンドウのように種類別に開いて作業をしたい。調べるとなんか、フラグメント利用や別のアクティビティーを開く文献が最初に目に付く。大規模なプロジェクト、大人数での開発では、その通りだわ。
だけど、小規模単独開発にはどうなの?
共通データには、どこからでもアクセスできた方が楽。構造体データとしてまとめておけば、改変するのもある程度は対応できる。大規模多人数開発ではデータの流れを絞り通過点を固定した方が安全安心、問題部分の発見も早いのでプログラムの性質で使い分けだ。

では、小規模開発でページレイアウトを切り替えて表示を変えるにはどうするか。

activity_main.xml で3枚のページを作る。順番に置かれるみたいなので、一枚目が下、三枚目が一番上になる。


    <FrameLayout ◎一枚目◎
        android:id="@+id/frame_Layout"
        …>
        <ImageView
            android:id="@+id/image_view1"
            … />
    </FrameLayout>

    <RelativeLayout ◎二枚目◎
        android:id="@+id/relative_layout1"
       …>
        <ImageView
            android:id="@+id/image_view2"
            … />
    </RelativeLayout>

    <RelativeLayout ◎三枚目◎
        android:id="@+id/relative_layout2"
        …>

        <ImageView
            android:id="@+id/image_view3"
            … />

    </RelativeLayout>

 

プログラムで
    public void layoutInvisible() {
        RelativeLayout layout = (RelativeLayout)findViewById( R.id.relative_layout1 );
        layout.setVisibility(View.GONE);
    }
か、
    public void layoutVisible() {
        RelativeLayout layout = (RelativeLayout)findViewById( R.id.relative_layout1 );
        layout.setVisibility(View.VISIBLE);
    }
で、ページレイアウト単位で非表示、表示が切り替えられる。表示状態か調べるには、

    public boolean isLayoutVisible() {
        RelativeLayout layout = (RelativeLayout)findViewById( R.id.relative_layout1 );
        return  ( layout.getVisibility() == View.VISIBLE );
    }
戻り値がtrueなら表示状態。
(ウィジェットの表示状態はページレイアウトの表示設定で一緒には変更されないので個々に調べるとページは非表示だけど、ウィジェットは、View.VISIBLE 状態になっている。だから注意が必要。それと、View.GONEは存在しないものとして扱われるモード。ページ上のウィジェットも存在しない領域へ…)

タッチイベントでサブレイアウトが表示されているか調べて処理を振り分ける。

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if ( isLayoutVisible() ) {
            ; // サブページレイアウトが開いてる時のタッチ処理を書く
        } else {
            if ( imageOnTouchEvent(event) ) // メインレイアウトのタッチ処理
                return true;
        }

        return super.onTouchEvent(event);
    }

    private boolean imageOnTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:

                break;

            case MotionEvent.ACTION_MOVE:

                return true;

            case MotionEvent.ACTION_UP:

                return true;

        }
        return false;
    }

imageOnTouchEvent(event) この中でページレイアウトを開くアクションが見つかったら、layoutVisible() を実行。そのページを開く、そこでの処理が終わったら、layoutInvisible() でページレイアウトを非表示にしてメインに戻る。

それと、ページレイアウトを切り替えるときアニメーション表示させたい。まだ試験はしてないけどViewSwicter を使えばできそう。それよりも RelativeLayout が非推奨になってるらしい。
ConstraintLayout へ移行処理してるけど、activity_main.xml で、この場所にConstraintLayoutを使います。と、ID を宣言するのは同じ。プログラムでウィジェット(imageView等)を追加するわけだけど、位置や空白を記述するのは直感的で理解しやすい。オブジェクトのセットも流れで行えるので良い感じ。
ただ、使い方が特殊?なのでこちらの望む動作にならない部分が出てくる。ひと手間加えると動き出すので試行錯誤が続く。