Androidプログラミング

【Android】Fragment内でのタッチ座標取得方法【onTouchEvent】

Android

本記事では、Fragment内でタッチ操作が発生した際の座標を取得する方法とそれをActivityで扱う方法を紹介します。コードは一番下にあります。

画面上でのタッチ座標取得そのものをまだやったことない方はこちらを先に読むことをオススメします(非常にシンプルなコードとなっております)。

スポンサーリンク

画面上タッチ座標の取得方法(onTouchEventの使い方)

Androidにて画面のタッチ座標取得をやったことない方は、まず以下記事を見てください。
非常にシンプルなサンプルコードを載せています。

上記事の内容を意識して、応用編の本記事をどうぞ。

スポンサーリンク

作成したアプリの動作の様子(動画)

以下のような動作をするアプリを作成しました。

スポンサーリンク

作成したアプリの仕様(Fragment+onTouchEvent)

メインの機能として以下を実装しています。

  • 一定範囲(Fragment)内でのタッチ座標の取得
  • Fragmentのサイズを取得し、タッチ座標を範囲に対する割合(0~100%)に変換して表示
  • 上記一定範囲(Fragment)上のタッチ座標をMainActivityに返し表示

おまけの機能として以下を実装しています(動作確認と見やすさ向上の都合)。

  • 画面全体のタッチ座標を取得・表示
  • Fragment上タッチ操作時には四角形(Rect)を描画

Fragment上タッチ座標が0未満、もしくはFragmentのサイズ以上の場合は処理しないようにしてタッチ範囲を制限します。

機能の分担

  • タッチ操作の検出・座標の取得:OnTouchEvent()
  • Fragmentの描画サイズの取得:ViewTreeObserver.OnGlobalLayoutListenerのonGlobalLayout()
  • Fragment上タッチ座標の範囲判定:Fragmentの描画サイズとの比較
  • Fragmentで取得した座標をMainActivityに返す:インターフェースSampleFragmentListeneのonGetValue
  • 図形の描画:onDraw()のdrawRect

画面に描画されるまでFragmentの描画サイズは取得できないので工夫が必要でした。

スポンサーリンク

サンプルコード

以下がMainActivityのXMLです。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="@style/MyTextStyle"
        android:text="◆タッチされた座標"/>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/MyTextStyle"
            android:text=" X:"/>
        <TextView
            android:id="@+id/textview_x"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/MyTextStyle"
            android:text=""/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/MyTextStyle"
            android:text=" Y:"/>
        <TextView
            android:id="@+id/textview_y"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/MyTextStyle"
            android:text=""/>
    </LinearLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        style="@style/MyTextStyle"
        android:text="◇タッチされた座標(Fragment内)"/>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/MyTextStyle"
            android:text=" X:"/>
        <TextView
            android:id="@+id/textview_fg_x"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/MyTextStyle"
            android:text=""/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/MyTextStyle"
            android:text=" Y:"/>
        <TextView
            android:id="@+id/textview_fg_y"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/MyTextStyle"
            android:text=""/>
    </LinearLayout>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <fragment
            android:id="@+id/fragment"
            android:name="com.example.shin.getcoordinates04.SampleFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="30dp"/>
    </FrameLayout>

</LinearLayout>

以下がMainActivityのJavaファイルです。

package com.example.shin.getcoordinates04;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity implements SampleFragment.SampleFragmentListener {

    private TextView tv_x;
    private TextView tv_y;
    private TextView tv_fg_x;
    private TextView tv_fg_y;


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

        tv_x = findViewById(R.id.textview_x);
        tv_y = findViewById(R.id.textview_y);
        tv_fg_x = findViewById(R.id.textview_fg_x);
        tv_fg_y = findViewById(R.id.textview_fg_y);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //XY座標の取得
        float x_coor = event.getX();
        float y_coor = event.getY();

        //TextViewに表示
        tv_x.setText(String.valueOf(x_coor));
        tv_y.setText(String.valueOf(y_coor));

        return true;
    }

    //インターフェース関連(Fragmentからの値取得)
    @Override
    public void onGetValue(float fg_x,float fg_y){
        Log.d("Fragment info from onGetValue:",String.valueOf(fg_x));
        tv_fg_x.setText(String.valueOf(fg_x));
        tv_fg_y.setText(String.valueOf(fg_y));
    }

}

以下がFragmentのXMLです。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#505050"
    android:id="@+id/fragment_toplayout">


    <TextView
        android:text="@string/fragment_textview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"/>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="right">
        <TextView
            android:id="@+id/fragment_textview_x"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/MyTextStyle"
            />
        <TextView
            android:id="@+id/fragment_textview_y"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/MyTextStyle"
            />
    </LinearLayout>
    
</FrameLayout>

以下がFragmentのJavaファイルです。

package com.example.shin.getcoordinates04;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;

public class SampleFragment extends Fragment {

    //表示用
    private TextView fg_tv_x;
    private TextView fg_tv_y;
    //図形描画用
    Paint paint;


    //Fragmentのサイズ取得関連
    private FrameLayout topContainer;
    private ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener;
    private float fragment_w = 0;
    private float fragment_h = 0;


    //MainActivityに呼ばれるインターフェース(値をMainActivityに返すため)
    public interface SampleFragmentListener{
        void onGetValue(float fg_x,float fg_y);
    }
    //上記インターフェースのフィールド
    private SampleFragmentListener listener;
    @Override
    public void onAttach(@NonNull Context context){
        super.onAttach(context);

        //contextをlisnerに代入
        if(context instanceof SampleFragmentListener){
            listener = (SampleFragmentListener) context;
        }
    }

    public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState){

        paint = new Paint();

        View view = inflater.inflate(R.layout.fragment_main,container, false);

        //Fragment上UIのid取得
        fg_tv_x = view.findViewById(R.id.fragment_textview_x);
        fg_tv_y = view.findViewById(R.id.fragment_textview_y);
        topContainer = view.findViewById(R.id.fragment_toplayout);

        //図形描画用
        topContainer.addView(new AnimView(this.getActivity()));


        //Fragmentのサイズ取得用
        globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener(){
            @Override
            public void onGlobalLayout(){
                //Fragmentのサイズ取得
                fragment_w = topContainer.getWidth();
                fragment_h = topContainer.getHeight();

                //removeOnGlobalLayoutListenerの削除
                topContainer.getViewTreeObserver().removeOnGlobalLayoutListener(globalLayoutListener);

            }
        };
        topContainer.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);

        return view;
    }

    //Fragment上図形描画用
    class AnimView extends View{
        Paint paint;

        //XY座標の取得
        float cx;
        float cy;
        //XY座標(割合表示用)
        double cx_ratio;
        double cy_ratio;

        public AnimView(Context context){
            super(context);
            paint = new Paint();
        }
        @Override
        protected void onDraw(Canvas canvas){

            paint.setStrokeWidth(10);
            paint.setStyle(Paint.Style.STROKE);

            canvas.drawRect(cx-20,cy-20,cx+20,cy+20, paint);
        }
        @Override
        public boolean onTouchEvent(MotionEvent event) {

            //XY座標取得
            cx = event.getX();
            cy = event.getY();
            //XY座標を「Fragmentサイズに対しての割合」に変換+小数点切り捨て
            cx_ratio = Math.floor(cx/fragment_w*100);
            cy_ratio = Math.floor(cy/fragment_h*100);

            //タッチした座標がFragmentの範囲内なら処理
            if(((cx>=0) &amp;&amp; (cx <= fragment_w)) &amp;&amp; ((cy>=0) &amp;&amp; (cy <= fragment_h))){
                fg_tv_x.setText("X :"+String.valueOf(cx_ratio)+"%");
                fg_tv_y.setText("Y :"+String.valueOf(cy_ratio)+"%");

                //MainActivityに値を返す
                if(listener != null){
                    listener.onGetValue(cx,cy);
                }

                //タッチ動作の種類に応じて分岐(描画する図の色を分岐)
                switch(event.getAction()){
                    case MotionEvent.ACTION_MOVE:
                        paint.setColor(Color.argb(255,255,255,0));
                        break;
                    case MotionEvent.ACTION_UP:
                        paint.setColor(Color.argb(255,255,0,0));
                        break;
                }

                //再描画
                invalidate();
            }
            return true;
        }
    }
}

もっといい方法はあるかもしれませんが、作りたいものが作れたのでとりあえず満足しています。

コメント

タイトルとURLをコピーしました